diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 8ae90ceb383..d537adfcbfd 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = { accounts = nodes; } - const get_balances = frappe.call({ - method: 'erpnext.accounts.utils.get_account_balances', - args: { - accounts: accounts, - company: cur_tree.args.company - }, - }); + frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { + if(value) { - get_balances.then(r => { - if (!r.message || r.message.length == 0) return; + const get_balances = frappe.call({ + method: 'erpnext.accounts.utils.get_account_balances', + args: { + accounts: accounts, + company: cur_tree.args.company + }, + }); - for (let account of r.message) { + get_balances.then(r => { + if (!r.message || r.message.length == 0) return; - const node = cur_tree.nodes && cur_tree.nodes[account.value]; - if (!node || node.is_root) continue; + for (let account of r.message) { - // show Dr if positive since balance is calculated as debit - credit else show Cr - const balance = account.balance_in_account_currency || account.balance; - const dr_or_cr = balance > 0 ? "Dr": "Cr"; - const format = (value, currency) => format_currency(Math.abs(value), currency); + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; - if (account.balance!==undefined) { - node.parent && node.parent.find('.balance-area').remove(); - $('' - + (account.balance_in_account_currency ? - (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") - + format(account.balance, account.company_currency) - + " " + dr_or_cr - + '').insertBefore(node.$ul); - } + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? "Dr": "Cr"; + const format = (value, currency) => format_currency(Math.abs(value), currency); + + if (account.balance!==undefined) { + node.parent && node.parent.find('.balance-area').remove(); + $('' + + (account.balance_in_account_currency ? + (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") + + format(account.balance, account.company_currency) + + " " + dr_or_cr + + '').insertBefore(node.$ul); + } + } + }); } }); }, diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 467c68f1021..3ab9d2b60d5 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -66,7 +66,9 @@ "report_settings_sb", "banking_tab", "enable_party_matching", - "enable_fuzzy_matching" + "enable_fuzzy_matching", + "tab_break_dpet", + "show_balance_in_coa" ], "fields": [ { @@ -416,6 +418,17 @@ "fieldname": "ignore_account_closing_balance", "fieldtype": "Check", "label": "Ignore Account Closing Balance" + }, + { + "fieldname": "tab_break_dpet", + "fieldtype": "Tab Break", + "label": "Chart Of Accounts" + }, + { + "default": "1", + "fieldname": "show_balance_in_coa", + "fieldtype": "Check", + "label": "Show Balances in Chart Of Accounts" } ], "icon": "icon-cog", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 1c6c99ed3e5..0e2de4ac395 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -809,6 +809,11 @@ class PaymentEntry(AccountsController): flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount") ) + # on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated + # for base currency transactions + if d.exchange_rate is None: + d.exchange_rate = 1 + allocated_amount_in_pe_exchange_rate = flt( flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount") ) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index e57c3259d1f..7d294e873d4 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -634,6 +634,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company): create_gain_loss_journal( company, + today(), inv.party_type, inv.party, inv.account, diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index ad3a70a862f..f2e4c2f894c 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -45,6 +45,7 @@ class POSInvoice(SalesInvoice): self.validate_debit_to_acc() self.validate_write_off_account() self.validate_change_amount() + self.validate_duplicate_serial_and_batch_no() self.validate_change_account() self.validate_item_cost_centers() self.validate_warehouse() diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 45373741f39..e711ae0de2b 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -49,6 +49,7 @@ "column_break_21", "start_date", "section_break_33", + "pdf_name", "subject", "column_break_28", "cc_to", @@ -273,7 +274,7 @@ "fieldname": "help_text", "fieldtype": "HTML", "label": "Help Text", - "options": "
\n

Note

\n\n

Examples

\n\n\n" + "options": "
\n

Note

\n\n

Examples

\n\n\n" }, { "fieldname": "subject", @@ -368,10 +369,15 @@ "fieldname": "based_on_payment_terms", "fieldtype": "Check", "label": "Based On Payment Terms" + }, + { + "fieldname": "pdf_name", + "fieldtype": "Data", + "label": "PDF Name" } ], "links": [], - "modified": "2023-06-23 10:13:15.051950", + "modified": "2023-08-28 12:59:53.071334", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 6cd601f663d..e1f32952205 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -26,7 +26,13 @@ class ProcessStatementOfAccounts(Document): if not self.subject: self.subject = "Statement Of Accounts for {{ customer.customer_name }}" if not self.body: - self.body = "Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}." + if self.report == "General Ledger": + body_str = " from {{ doc.from_date }} to {{ doc.to_date }}." + else: + body_str = " until {{ doc.posting_date }}." + self.body = "Hello {{ customer.customer_name }},
PFA your Statement Of Accounts" + body_str + if not self.pdf_name: + self.pdf_name = "{{ customer.customer_name }}" validate_template(self.subject) validate_template(self.body) @@ -57,11 +63,6 @@ def get_report_pdf(doc, consolidated=True): filters = get_common_filters(doc) - if doc.report == "General Ledger": - filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) - else: - filters.update(get_ar_filters(doc, entry)) - if doc.report == "General Ledger": col, res = get_soa(filters) for x in [0, -2, -1]: @@ -69,8 +70,11 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue else: + filters.update(get_ar_filters(doc, entry)) ar_res = get_ar_soa(filters) col, res = ar_res[0], ar_res[1] + if not res: + continue statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing) @@ -139,6 +143,7 @@ def get_ar_filters(doc, entry): return { "report_date": doc.posting_date if doc.posting_date else None, "customer": entry.customer, + "customer_name": entry.customer_name if entry.customer_name else None, "payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None, "sales_partner": doc.sales_partner if doc.sales_partner else None, "sales_person": doc.sales_person if doc.sales_person else None, @@ -362,16 +367,20 @@ def download_statements(document_name): @frappe.whitelist() -def send_emails(document_name, from_scheduler=False): +def send_emails(document_name, from_scheduler=False, posting_date=None): doc = frappe.get_doc("Process Statement Of Accounts", document_name) report = get_report_pdf(doc, consolidated=False) if report: for customer, report_pdf in report.items(): - attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}] + context = get_context(customer, doc) + filename = frappe.render_template(doc.pdf_name, context) + attachments = [{"fname": filename + ".pdf", "fcontent": report_pdf}] recipients, cc = get_recipients_and_cc(customer, doc) - context = get_context(customer, doc) + if not recipients: + continue + subject = frappe.render_template(doc.subject, context) message = frappe.render_template(doc.body, context) @@ -390,7 +399,7 @@ def send_emails(document_name, from_scheduler=False): ) if doc.enable_auto_email and from_scheduler: - new_to_date = getdate(today()) + new_to_date = getdate(posting_date or today()) if doc.frequency == "Weekly": new_to_date = add_days(new_to_date, 7) else: @@ -399,8 +408,11 @@ def send_emails(document_name, from_scheduler=False): doc.add_comment( "Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now()) ) - doc.db_set("to_date", new_to_date, commit=True) - doc.db_set("from_date", new_from_date, commit=True) + if doc.report == "General Ledger": + doc.db_set("to_date", new_to_date, commit=True) + doc.db_set("from_date", new_from_date, commit=True) + else: + doc.db_set("posting_date", new_to_date, commit=True) return True else: return False @@ -410,7 +422,8 @@ def send_emails(document_name, from_scheduler=False): def send_auto_email(): selected = frappe.get_list( "Process Statement Of Accounts", - filters={"to_date": format_date(today()), "enable_auto_email": 1}, + filters={"enable_auto_email": 1}, + or_filters={"to_date": format_date(today()), "posting_date": format_date(today())}, ) for entry in selected: send_emails(entry.name, from_scheduler=True) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 259526f8c43..647600a9fea 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -8,9 +8,24 @@ } +
+ {% if letter_head.content %} +
{{ letter_head.content }}
+
+ {% endif %} +
+ +

{{ _(report.report_name) }}

- {{ filters.customer }} + {{ filters.customer_name }}

{% if (filters.tax_id) %} @@ -341,4 +356,9 @@ {% endif %} + {% if terms_and_conditions %} +
+ {{ terms_and_conditions }} +
+ {% endif %}

{{ _("Printed On ") }}{{ frappe.utils.now() }}

diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index c281040aaf2..fb0d8d152f0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -1,9 +1,42 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import add_days, getdate, today + +from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + send_emails, +) +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + class TestProcessStatementOfAccounts(unittest.TestCase): - pass + def setUp(self): + self.si = create_sales_invoice() + self.process_soa = create_process_soa() + + def test_auto_email_for_process_soa_ar(self): + send_emails(self.process_soa.name, from_scheduler=True) + self.process_soa.load_from_db() + self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + + def tearDown(self): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + + +def create_process_soa(): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + process_soa = frappe.new_doc("Process Statement Of Accounts") + soa_dict = { + "name": "Test Process SOA", + "company": "_Test Company", + } + process_soa.update(soa_dict) + process_soa.set("customers", [{"customer": "_Test Customer"}]) + process_soa.enable_auto_email = 1 + process_soa.frequency = "Weekly" + process_soa.report = "Accounts Receivable" + process_soa.save() + return process_soa diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 2340f486f14..9737ee2c53e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -269,9 +269,7 @@ class PurchaseInvoice(BuyingController): stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") stock_items = self.get_stock_items() - asset_items = [d.is_fixed_asset for d in self.items if d.is_fixed_asset] - if len(asset_items) > 0: - asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") + asset_received_but_not_billed = None if self.update_stock: self.validate_item_code() @@ -365,6 +363,8 @@ class PurchaseInvoice(BuyingController): ) item.expense_account = asset_category_account elif item.is_fixed_asset and item.pr_detail: + if not asset_received_but_not_billed: + asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -978,8 +978,9 @@ class PurchaseInvoice(BuyingController): ) def get_asset_gl_entry(self, gl_entries): - arbnb_account = self.get_company_default("asset_received_but_not_billed") - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") + arbnb_account = None + eiiav_account = None + asset_eiiav_currency = None for item in self.get("items"): if item.is_fixed_asset: @@ -991,6 +992,8 @@ class PurchaseInvoice(BuyingController): "Asset Received But Not Billed", "Fixed Asset", ]: + if not arbnb_account: + arbnb_account = self.get_company_default("asset_received_but_not_billed") item.expense_account = arbnb_account if not self.update_stock: @@ -1013,7 +1016,10 @@ class PurchaseInvoice(BuyingController): ) if item.item_tax_amount: - asset_eiiav_currency = get_account_currency(eiiav_account) + if not eiiav_account or not asset_eiiav_currency: + eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") + asset_eiiav_currency = get_account_currency(eiiav_account) + gl_entries.append( self.get_gl_dict( { @@ -1056,7 +1062,10 @@ class PurchaseInvoice(BuyingController): ) if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): - asset_eiiav_currency = get_account_currency(eiiav_account) + if not eiiav_account or not asset_eiiav_currency: + eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") + asset_eiiav_currency = get_account_currency(eiiav_account) + gl_entries.append( self.get_gl_dict( { @@ -1076,47 +1085,46 @@ class PurchaseInvoice(BuyingController): ) ) - # When update stock is checked # Assets are bought through this document then it will be linked to this document - if self.update_stock: - if flt(item.landed_cost_voucher_amount): - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": cwip_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) + if flt(item.landed_cost_voucher_amount): + if not eiiav_account: + eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - gl_entries.append( - self.get_gl_dict( - { - "account": cwip_account, - "against": eiiav_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) + gl_entries.append( + self.get_gl_dict( + { + "account": eiiav_account, + "against": cwip_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "credit": flt(item.landed_cost_voucher_amount), + "project": item.project or self.project, + }, + item=item, ) - - # update gross amount of assets bought through this document - assets = frappe.db.get_all( - "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} ) - for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) - frappe.db.set_value( - "Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) + + gl_entries.append( + self.get_gl_dict( + { + "account": cwip_account, + "against": eiiav_account, + "cost_center": item.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": flt(item.landed_cost_voucher_amount), + "project": item.project or self.project, + }, + item=item, ) + ) + + # update gross amount of assets bought through this document + assets = frappe.db.get_all( + "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) return gl_entries diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0f8e77952cf..30265aeb50e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1153,7 +1153,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True) item.enable_deferred_expense = 1 - item.deferred_expense_account = deferred_account + item.item_defaults[0].deferred_expense_account = deferred_account item.save() pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 6fdcf263a55..fd95c1fe0e5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -15,9 +15,11 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "sales_order"], - "Delivery Note": ["items", "delivery_note"], "Timesheet": ["timesheets", "time_sheet"], }, + "internal_and_external_links": { + "Delivery Note": ["items", "delivery_note"], + }, "transactions": [ { "label": _("Payment"), diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index eee99dcfde0..378be113e7c 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2322,7 +2322,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_revenue = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.no_of_months = 12 item.save() @@ -3102,7 +3102,7 @@ class TestSalesInvoice(unittest.TestCase): item = create_item("_Test Item for Deferred Accounting") item.enable_deferred_expense = 1 - item.deferred_revenue_account = deferred_account + item.item_defaults[0].deferred_revenue_account = deferred_account item.save() si = create_sales_invoice( diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index eb17daa282f..c911e7fe12d 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -694,3 +694,23 @@ class TestSubscription(unittest.TestCase): # Check the currency of the created invoice currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].invoice, "currency") self.assertEqual(currency, "USD") + + def test_plan_rate_for_midmonth_start_date(self): + subscription = frappe.new_doc("Subscription") + subscription.party_type = "Supplier" + subscription.party = "_Test Supplier" + subscription.generate_invoice_at_period_start = 1 + subscription.follow_calendar_months = 1 + subscription.generate_new_invoices_past_due_date = 1 + subscription.start_date = "2023-04-08" + subscription.end_date = "2024-02-27" + subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1}) + subscription.save() + + subscription.process() + + self.assertEqual(len(subscription.invoices), 1) + pi = frappe.get_doc("Purchase Invoice", subscription.invoices[0].invoice) + self.assertEqual(pi.total, 55333.33) + + subscription.delete() diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index f3acdc5aa87..75223c2ccca 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -57,18 +57,17 @@ def get_plan_rate( prorate = frappe.db.get_single_value("Subscription Settings", "prorate") if prorate: - prorate_factor = flt( - date_diff(start_date, get_first_day(start_date)) - / date_diff(get_last_day(start_date), get_first_day(start_date)), - 1, - ) - - prorate_factor += flt( - date_diff(get_last_day(end_date), end_date) - / date_diff(get_last_day(end_date), get_first_day(end_date)), - 1, - ) - - cost -= plan.cost * prorate_factor - + cost -= plan.cost * get_prorate_factor(start_date, end_date) return cost + + +def get_prorate_factor(start_date, end_date): + total_days_to_skip = date_diff(start_date, get_first_day(start_date)) + total_days_in_month = int(get_last_day(start_date).strftime("%d")) + prorate_factor = flt(total_days_to_skip / total_days_in_month) + + total_days_to_skip = date_diff(get_last_day(end_date), end_date) + total_days_in_month = int(get_last_day(end_date).strftime("%d")) + prorate_factor += flt(total_days_to_skip / total_days_in_month) + + return prorate_factor diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 3700f00ee22..14f8993727a 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -467,6 +467,10 @@ class ReceivablePayableReport(object): original_row = frappe._dict(row) row.payment_terms = [] + # Cr Note's don't have Payment Terms + if not payment_terms_details: + return + # Advance allocated during invoicing is not considered in payment terms # Deduct that from paid amount pre allocation row.paid -= flt(payment_terms_details[0].total_advance) diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py index 28d0c20a918..7b1a9027780 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py @@ -81,7 +81,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_revenue = 1 - item.deferred_revenue_account = self.deferred_revenue_account + item.item_defaults[0].deferred_revenue_account = self.deferred_revenue_account item.no_of_months = 3 item.save() @@ -150,7 +150,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin): self.create_item("_Test Office Desk", 0, self.warehouse, self.company) item = frappe.get_doc("Item", self.item) item.enable_deferred_expense = 1 - item.deferred_expense_account = self.deferred_expense_account + item.item_defaults[0].deferred_expense_account = self.deferred_expense_account item.no_of_months_exp = 3 item.save() diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 23403a4b15b..d670a356975 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -272,20 +272,19 @@ def get_conditions(filters): if match_conditions: conditions.append(match_conditions) - if filters.get("include_dimensions"): - accounting_dimensions = get_accounting_dimensions(as_list=False) + accounting_dimensions = get_accounting_dimensions(as_list=False) - if accounting_dimensions: - for dimension in accounting_dimensions: - if not dimension.disabled: - if filters.get(dimension.fieldname): - if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): - filters[dimension.fieldname] = get_dimension_with_children( - dimension.document_type, filters.get(dimension.fieldname) - ) - conditions.append("{0} in %({0})s".format(dimension.fieldname)) - else: - conditions.append("{0} in %({0})s".format(dimension.fieldname)) + if accounting_dimensions: + for dimension in accounting_dimensions: + if not dimension.disabled: + if filters.get(dimension.fieldname): + if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): + filters[dimension.fieldname] = get_dimension_with_children( + dimension.document_type, filters.get(dimension.fieldname) + ) + conditions.append("{0} in %({0})s".format(dimension.fieldname)) + else: + conditions.append("{0} in %({0})s".format(dimension.fieldname)) return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 050e6bc5d2f..ce1a62d0065 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -287,7 +287,7 @@ def get_conditions(filters): conditions = "" for opts in ( - ("company", " and company=%(company)s"), + ("company", " and `tabPurchase Invoice`.company=%(company)s"), ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 4d24dd90762..19bb449cd94 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -332,7 +332,7 @@ def get_conditions(filters, additional_conditions=None): conditions = "" for opts in ( - ("company", " and company=%(company)s"), + ("company", " and `tabSales Invoice`.company=%(company)s"), ("customer", " and `tabSales Invoice`.customer = %(customer)s"), ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 7191720c57e..91ad3d6873a 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -324,12 +324,22 @@ def get_journal_entry_party_map(journal_entries): def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): - common_fields = ["name", "tax_withholding_category"] + common_fields = ["name"] fields_dict = { - "Purchase Invoice": ["base_tax_withholding_net_total", "grand_total", "base_total"], + "Purchase Invoice": [ + "tax_withholding_category", + "base_tax_withholding_net_total", + "grand_total", + "base_total", + ], "Sales Invoice": ["base_net_total", "grand_total", "base_total"], - "Payment Entry": ["paid_amount", "paid_amount_after_tax", "base_paid_amount"], - "Journal Entry": ["total_amount"], + "Payment Entry": [ + "tax_withholding_category", + "paid_amount", + "paid_amount_after_tax", + "base_paid_amount", + ], + "Journal Entry": ["tax_withholding_category", "total_amount"], } entries = frappe.get_all( diff --git a/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py new file mode 100644 index 00000000000..89ecef1904c --- /dev/null +++ b/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py @@ -0,0 +1,111 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import today + +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center +from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_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.accounts.doctype.tax_withholding_category.test_tax_withholding_category import ( + create_tax_withholding_category, +) +from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import execute +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.accounts.utils import get_fiscal_year + + +class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.clear_old_entries() + create_tax_accounts() + create_tcs_category() + + def test_tax_withholding_for_customers(self): + si = create_sales_invoice(rate=1000) + pe = create_tcs_payment_entry() + filters = frappe._dict( + company="_Test Company", party_type="Customer", from_date=today(), to_date=today() + ) + result = execute(filters)[1] + expected_values = [ + [pe.name, "TCS", 0.075, 2550, 0.53, 2550.53], + [si.name, "TCS", 0.075, 1000, 0.53, 1000.53], + ] + self.check_expected_values(result, expected_values) + + def check_expected_values(self, result, expected_values): + for i in range(len(result)): + voucher = frappe._dict(result[i]) + voucher_expected_values = expected_values[i] + self.assertEqual(voucher.ref_no, voucher_expected_values[0]) + self.assertEqual(voucher.section_code, voucher_expected_values[1]) + self.assertEqual(voucher.rate, voucher_expected_values[2]) + self.assertEqual(voucher.base_total, voucher_expected_values[3]) + self.assertEqual(voucher.tax_amount, voucher_expected_values[4]) + self.assertEqual(voucher.grand_total, voucher_expected_values[5]) + + def tearDown(self): + self.clear_old_entries() + + +def create_tax_accounts(): + account_names = ["TCS", "TDS"] + for account in account_names: + frappe.get_doc( + { + "doctype": "Account", + "company": "_Test Company", + "account_name": account, + "parent_account": "Duties and Taxes - _TC", + "report_type": "Balance Sheet", + "root_type": "Liability", + } + ).insert(ignore_if_duplicate=True) + + +def create_tcs_category(): + fiscal_year = get_fiscal_year(today(), company="_Test Company") + from_date = fiscal_year[1] + to_date = fiscal_year[2] + + tax_category = create_tax_withholding_category( + category_name="TCS", + rate=0.075, + from_date=from_date, + to_date=to_date, + account="TCS - _TC", + cumulative_threshold=300, + ) + + customer = frappe.get_doc("Customer", "_Test Customer") + customer.tax_withholding_category = "TCS" + customer.save() + + +def create_tcs_payment_entry(): + payment_entry = create_payment_entry( + payment_type="Receive", + party_type="Customer", + party="_Test Customer", + paid_from="Debtors - _TC", + paid_to="Cash - _TC", + paid_amount=2550, + ) + + payment_entry.append( + "taxes", + { + "account_head": "TCS - _TC", + "charge_type": "Actual", + "tax_amount": 0.53, + "add_deduct_tax": "Add", + "description": "Test", + "cost_center": "Main - _TC", + }, + ) + payment_entry.submit() + return payment_entry diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 267f22d119b..8f0ef869ad3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1858,6 +1858,7 @@ class QueryPaymentLedger(object): def create_gain_loss_journal( company, + posting_date, party_type, party, party_account, @@ -1876,7 +1877,7 @@ def create_gain_loss_journal( journal_entry = frappe.new_doc("Journal Entry") journal_entry.voucher_type = "Exchange Gain Or Loss" journal_entry.company = company - journal_entry.posting_date = nowdate() + journal_entry.posting_date = posting_date or nowdate() journal_entry.multi_currency = 1 journal_entry.is_system_generated = True diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 34d5430210c..e72ddcb12a0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -40,6 +40,7 @@ class Asset(AccountsController): self.validate_item() self.validate_cost_center() self.set_missing_values() + self.validate_finance_books() if not self.split_from: self.prepare_depreciation_data() self.validate_gross_and_purchase_amount() @@ -206,6 +207,27 @@ class Asset(AccountsController): finance_books = get_item_details(self.item_code, self.asset_category) self.set("finance_books", finance_books) + def validate_finance_books(self): + if not self.calculate_depreciation or len(self.finance_books) == 1: + return + + finance_books = set() + + for d in self.finance_books: + if d.finance_book in finance_books: + frappe.throw( + _("Row #{}: Please use a different Finance Book.").format(d.idx), + title=_("Duplicate Finance Book"), + ) + else: + finance_books.add(d.finance_book) + + if not d.finance_book: + frappe.throw( + _("Row #{}: Finance Book should not be empty since you're using multiple.").format(d.idx), + title=_("Missing Finance Book"), + ) + def validate_asset_values(self): if not self.asset_category: self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") @@ -1363,26 +1385,41 @@ def get_straight_line_or_manual_depr_amount( daily_depr_amount = ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) - * row.frequency_of_depreciation, - ), - add_months( - row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, ) - * row.frequency_of_depreciation, + ), + add_days( + get_last_day( + add_months( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.number_of_depreciations_booked + - number_of_pending_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + 1, ), ) - to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - from_date = add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation + + to_date = get_last_day( + add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) ) - return daily_depr_amount * date_diff(to_date, from_date) + from_date = add_days( + get_last_day( + add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation) + ), + 1, + ) + + return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) @@ -1395,18 +1432,29 @@ def get_straight_line_or_manual_depr_amount( - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) - * row.frequency_of_depreciation, + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 ), - row.depreciation_start_date, ) - to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - from_date = add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation + + to_date = get_last_day( + add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) ) - return daily_depr_amount * date_diff(to_date, from_date) + from_date = add_days( + get_last_day( + add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation) + ), + 1, + ) + + return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(asset.gross_purchase_amount) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a2826d929b8..1adbeed65a5 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -743,18 +743,18 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2023-01-31", 1019.18, 1019.18], - ["2023-02-28", 920.55, 1939.73], - ["2023-03-31", 1019.18, 2958.91], - ["2023-04-30", 986.3, 3945.21], - ["2023-05-31", 1019.18, 4964.39], - ["2023-06-30", 986.3, 5950.69], - ["2023-07-31", 1019.18, 6969.87], - ["2023-08-31", 1019.18, 7989.05], - ["2023-09-30", 986.3, 8975.35], - ["2023-10-31", 1019.18, 9994.53], - ["2023-11-30", 986.3, 10980.83], - ["2023-12-31", 1019.17, 12000.0], + ["2023-01-31", 1021.98, 1021.98], + ["2023-02-28", 923.08, 1945.06], + ["2023-03-31", 1021.98, 2967.04], + ["2023-04-30", 989.01, 3956.05], + ["2023-05-31", 1021.98, 4978.03], + ["2023-06-30", 989.01, 5967.04], + ["2023-07-31", 1021.98, 6989.02], + ["2023-08-31", 1021.98, 8011.0], + ["2023-09-30", 989.01, 9000.01], + ["2023-10-31", 1021.98, 10021.99], + ["2023-11-30", 989.01, 11011.0], + ["2023-12-31", 989.0, 12000.0], ] schedules = [ @@ -1332,6 +1332,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 1", "depreciation_method": "Straight Line", "frequency_of_depreciation": 1, "total_number_of_depreciations": 3, @@ -1342,6 +1343,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 2", "depreciation_method": "Straight Line", "frequency_of_depreciation": 1, "total_number_of_depreciations": 6, @@ -1352,6 +1354,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 3", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, @@ -1381,6 +1384,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 1", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, @@ -1391,6 +1395,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 2", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 6, @@ -1647,6 +1652,15 @@ def create_asset_data(): if not frappe.db.exists("Location", "Test Location"): frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() + if not frappe.db.exists("Finance Book", "Test Finance Book 1"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 1"}).insert() + + if not frappe.db.exists("Finance Book", "Test Finance Book 2"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 2"}).insert() + + if not frappe.db.exists("Finance Book", "Test Finance Book 3"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 3"}).insert() + def create_asset(**args): args = frappe._dict(args) diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 83031415ec3..5c40072086e 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -80,14 +80,16 @@ def calculate_next_due_date( next_due_date = add_days(start_date, 7) if periodicity == "Monthly": next_due_date = add_months(start_date, 1) + if periodicity == "Quarterly": + next_due_date = add_months(start_date, 3) + if periodicity == "Half-yearly": + next_due_date = add_months(start_date, 6) if periodicity == "Yearly": next_due_date = add_years(start_date, 1) if periodicity == "2 Yearly": next_due_date = add_years(start_date, 2) if periodicity == "3 Yearly": next_due_date = add_years(start_date, 3) - if periodicity == "Quarterly": - next_due_date = add_months(start_date, 3) if end_date and ( (start_date and start_date >= end_date) or (last_completion_date and last_completion_date >= end_date) diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json index b7cb23e6687..80d90c63473 100644 --- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json +++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json @@ -71,7 +71,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Periodicity", - "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly", + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly\n2 Yearly\n3 Yearly", "reqd": 1 }, { @@ -153,4 +153,4 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/location/location.json b/erpnext/assets/doctype/location/location.json index f56fd05d98c..9202fb9d95f 100644 --- a/erpnext/assets/doctype/location/location.json +++ b/erpnext/assets/doctype/location/location.json @@ -39,6 +39,7 @@ { "fieldname": "parent_location", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Parent Location", "options": "Location", "search_index": 1 @@ -141,11 +142,11 @@ ], "is_tree": 1, "links": [], - "modified": "2020-05-08 16:11:11.375701", + "modified": "2023-08-29 12:49:33.290527", "modified_by": "Administrator", "module": "Assets", "name": "Location", - "name_case": "Title Case", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_location", "owner": "Administrator", "permissions": [ @@ -224,5 +225,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 645abf25a8f..bacd98bea77 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1171,6 +1171,7 @@ "depends_on": "is_internal_supplier", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set From Warehouse", "options": "Warehouse" }, @@ -1271,7 +1272,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-05-24 11:16:41.195340", + "modified": "2023-09-13 16:21:07.361700", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index c645b04e129..fe1b9702539 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -878,6 +878,7 @@ "depends_on": "eval:parent.is_internal_supplier", "fieldname": "from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "From Warehouse", "options": "Warehouse" }, @@ -902,7 +903,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-11-29 16:47:41.364387", + "modified": "2023-09-13 16:22:40.825092", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index b9fc344647b..7c7467e6f64 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -195,6 +195,9 @@ class TestSupplier(FrappeTestCase): def create_supplier(**args): args = frappe._dict(args) + if not args.supplier_name: + args.supplier_name = frappe.generate_hash() + if frappe.db.exists("Supplier", args.supplier_name): return frappe.get_doc("Supplier", args.supplier_name) @@ -202,6 +205,7 @@ def create_supplier(**args): { "doctype": "Supplier", "supplier_name": args.supplier_name, + "default_currency": args.default_currency, "supplier_group": args.supplier_group or "Services", "supplier_type": args.supplier_type or "Company", "tax_withholding_category": args.tax_withholding_category, diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py index 21241e08603..07187352eb7 100644 --- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py +++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py @@ -7,7 +7,7 @@ import copy import frappe from frappe import _ from frappe.query_builder.functions import Coalesce, Sum -from frappe.utils import date_diff, flt, getdate +from frappe.utils import cint, date_diff, flt, getdate def execute(filters=None): @@ -47,8 +47,10 @@ def get_data(filters): mr.transaction_date.as_("date"), mr_item.schedule_date.as_("required_date"), mr_item.item_code.as_("item_code"), - Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"), - Coalesce(mr_item.stock_uom, "").as_("uom"), + Sum(Coalesce(mr_item.qty, 0)).as_("qty"), + Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"), + Coalesce(mr_item.uom, "").as_("uom"), + Coalesce(mr_item.stock_uom, "").as_("stock_uom"), Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"), Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"), (Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_( @@ -96,7 +98,7 @@ def get_conditions(filters, query, mr, mr_item): def update_qty_columns(row_to_update, data_row): - fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] + fields = ["qty", "stock_qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"] for field in fields: row_to_update[field] += flt(data_row[field]) @@ -104,16 +106,20 @@ def update_qty_columns(row_to_update, data_row): def prepare_data(data, filters): """Prepare consolidated Report data and Chart data""" material_request_map, item_qty_map = {}, {} + precision = cint(frappe.db.get_default("float_precision")) or 2 for row in data: # item wise map for charts if not row["item_code"] in item_qty_map: item_qty_map[row["item_code"]] = { - "qty": row["qty"], - "ordered_qty": row["ordered_qty"], - "received_qty": row["received_qty"], - "qty_to_receive": row["qty_to_receive"], - "qty_to_order": row["qty_to_order"], + "qty": flt(row["stock_qty"], precision), + "stock_qty": flt(row["stock_qty"], precision), + "stock_uom": row["stock_uom"], + "uom": row["uom"], + "ordered_qty": flt(row["ordered_qty"], precision), + "received_qty": flt(row["received_qty"], precision), + "qty_to_receive": flt(row["qty_to_receive"], precision), + "qty_to_order": flt(row["qty_to_order"], precision), } else: item_entry = item_qty_map[row["item_code"]] @@ -200,21 +206,34 @@ def get_columns(filters): {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100}, {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200}, { - "label": _("Stock UOM"), + "label": _("UOM"), "fieldname": "uom", "fieldtype": "Data", "width": 100, }, + { + "label": _("Stock UOM"), + "fieldname": "stock_uom", + "fieldtype": "Data", + "width": 100, + }, ] ) columns.extend( [ { - "label": _("Stock Qty"), + "label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", - "width": 120, + "width": 140, + "convertible": "qty", + }, + { + "label": _("Qty in Stock UOM"), + "fieldname": "stock_qty", + "fieldtype": "Float", + "width": 140, "convertible": "qty", }, { diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b476d0ffb48..df77e5260f5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1042,8 +1042,10 @@ class AccountsController(TransactionBase): self.name, arg.get("referenced_row"), ): + posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date") je = create_gain_loss_journal( self.company, + posting_date, arg.get("party_type"), arg.get("party"), party_account, @@ -1123,6 +1125,7 @@ class AccountsController(TransactionBase): je = create_gain_loss_journal( self.company, + self.posting_date, self.party_type, self.party, party_account, diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1de39967730..01990a3a268 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -162,10 +162,13 @@ class BuyingController(SubcontractingController): purchase_doc_field = ( "purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice" ) - not_cancelled_asset = [ - d.name - for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) - ] + not_cancelled_asset = [] + if self.return_against: + not_cancelled_asset = [ + d.name + for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1}) + ] + if self.is_return and len(not_cancelled_asset): frappe.throw( _( diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 4f9088e8c08..57746a234bc 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -634,7 +634,6 @@ def get_applicable_shipping_rules(party=None, quotation=None): shipping_rules = get_shipping_rules(quotation) if shipping_rules: - rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page return [[rule, rule] for rule in shipping_rules] diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 76dc8b462e3..4d2c45d029f 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -263,12 +263,13 @@ "label": "Accounting Details" }, { + "depends_on": "eval:!doc.repay_from_salary", "fetch_from": "against_loan.payment_account", + "fetch_if_empty": 1, "fieldname": "payment_account", "fieldtype": "Link", "label": "Repayment Account", - "options": "Account", - "read_only": 1 + "options": "Account" }, { "fieldname": "column_break_36", @@ -294,7 +295,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-21 10:10:07.742298", + "modified": "2023-09-04 15:44:29.148766", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 48086dde93f..f0e113b0e7b 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -408,6 +408,16 @@ class LoanRepayment(AccountsController): else: payment_account = self.payment_account + payment_party_type = "" + payment_party = "" + + if ( + hasattr(self, "process_payroll_accounting_entry_based_on_employee") + and self.process_payroll_accounting_entry_based_on_employee + ): + payment_party_type = "Employee" + payment_party = self.applicant + if self.total_penalty_paid: gle_map.append( self.get_gl_dict( @@ -455,6 +465,8 @@ class LoanRepayment(AccountsController): "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), + "party_type": payment_party_type, + "party": payment_party, } ) ) @@ -493,6 +505,7 @@ def create_repayment_entry( amount_paid, penalty_amount=None, payroll_payable_account=None, + process_payroll_accounting_entry_based_on_employee=0, ): lr = frappe.get_doc( @@ -509,6 +522,7 @@ def create_repayment_entry( "amount_paid": amount_paid, "loan_type": loan_type, "payroll_payable_account": payroll_payable_account, + "process_payroll_accounting_entry_based_on_employee": process_payroll_accounting_entry_based_on_employee, } ).insert() diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 09bf1d8a736..d07bf0fa66b 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -10,22 +10,25 @@ "warehouse", "item_name", "material_request_type", - "actual_qty", - "ordered_qty", + "quantity", "required_bom_qty", "column_break_4", - "quantity", + "schedule_date", "uom", "conversion_factor", - "projected_qty", - "reserved_qty_for_production", - "safety_stock", "item_details", "description", "min_order_qty", "section_break_8", "sales_order", - "requested_qty" + "bin_qty_section", + "actual_qty", + "requested_qty", + "reserved_qty_for_production", + "column_break_yhelv", + "ordered_qty", + "projected_qty", + "safety_stock" ], "fields": [ { @@ -65,7 +68,7 @@ "fieldtype": "Column Break" }, { - "columns": 1, + "columns": 2, "fieldname": "quantity", "fieldtype": "Float", "in_list_view": 1, @@ -80,12 +83,12 @@ "read_only": 1 }, { - "columns": 2, + "columns": 1, "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Available Qty", + "label": "Qty In Stock", "no_copy": 1, "read_only": 1 }, @@ -176,11 +179,27 @@ "fieldtype": "Float", "label": "Conversion Factor", "read_only": 1 + }, + { + "columns": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Required By" + }, + { + "fieldname": "bin_qty_section", + "fieldtype": "Section Break", + "label": "BIN Qty" + }, + { + "fieldname": "column_break_yhelv", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2023-05-03 12:43:29.895754", + "modified": "2023-09-12 12:09:08.358326", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a494550423f..795cb97fffa 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -725,7 +725,7 @@ class ProductionPlan(Document): # key for Sales Order:Material Request Type:Customer key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") - schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) + schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) if not key in material_request_map: # make a new MR for the combination diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2871a29d768..f5778847b9c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2,7 +2,7 @@ # See license.txt import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_to_date, flt, now_datetime, nowdate +from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( @@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase): pln = create_production_plan(item_code="Test Production Item 1") self.assertTrue(len(pln.mr_items), 2) + for row in pln.mr_items: + row.schedule_date = add_to_date(nowdate(), days=10) + pln.make_material_request() pln.reload() self.assertTrue(pln.status, "Material Requested") @@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase): self.assertTrue(len(material_requests), 2) + for row in material_requests: + mr_schedule_date = getdate(frappe.db.get_value("Material Request", row[0], "schedule_date")) + + expected_date = getdate(add_to_date(nowdate(), days=10)) + + self.assertEqual(mr_schedule_date, expected_date) + pln.make_work_order() work_orders = frappe.get_all( "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cde484eb0fa..19f8dab9a17 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -339,5 +339,6 @@ erpnext.patches.v14_0.update_closing_balances #15-07-2023 execute:frappe.defaults.clear_default("fiscal_year") execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0) erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow +erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults # below migration patch should always run last erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/delete_education_doctypes.py b/erpnext/patches/v14_0/delete_education_doctypes.py index 76b2300fd2a..55b64eaabd8 100644 --- a/erpnext/patches/v14_0/delete_education_doctypes.py +++ b/erpnext/patches/v14_0/delete_education_doctypes.py @@ -46,6 +46,17 @@ def execute(): for doctype in doctypes: frappe.delete_doc("DocType", doctype, ignore_missing=True) + titles = [ + "Fees", + "Student Admission", + "Grant Application", + "Chapter", + "Certification Application", + ] + items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") + for item in items: + frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) + frappe.delete_doc("Module Def", "Education", ignore_missing=True, force=True) click.secho( diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py index 2c699e4a9f2..896a4409507 100644 --- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -41,7 +41,7 @@ def execute(): for card in cards: frappe.delete_doc("Number Card", card, ignore_missing=True, force=True) - titles = ["Lab Test", "Prescription", "Patient Appointment"] + titles = ["Lab Test", "Prescription", "Patient Appointment", "Patient"] items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name") for item in items: frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) diff --git a/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py new file mode 100644 index 00000000000..44b830babb2 --- /dev/null +++ b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py @@ -0,0 +1,39 @@ +import frappe + + +def execute(): + try: + item_dict = get_deferred_accounts() + add_to_item_defaults(item_dict) + except Exception: + frappe.db.rollback() + frappe.log_error("Failed to migrate deferred accounts in Item Defaults.") + + +def get_deferred_accounts(): + item = frappe.qb.DocType("Item") + return ( + frappe.qb.from_(item) + .select(item.name, item.deferred_expense_account, item.deferred_revenue_account) + .where((item.enable_deferred_expense == 1) | (item.enable_deferred_revenue == 1)) + .run(as_dict=True) + ) + + +def add_to_item_defaults(item_dict): + for item in item_dict: + add_company_wise_item_default(item, "deferred_expense_account") + add_company_wise_item_default(item, "deferred_revenue_account") + + +def add_company_wise_item_default(item, account_type): + company = frappe.get_cached_value("Account", item[account_type], "company") + if company and item[account_type]: + item_defaults = frappe.get_cached_value("Item", item["name"], "item_defaults") + for item_row in item_defaults: + if item_row.company == company: + frappe.set_value("Item Default", item_row.name, account_type, item[account_type]) + break + else: + item_defaults.append({"company": company, account_type: item[account_type]}) + frappe.set_value("Item", item["name"], "item_defaults", item_defaults) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d80133c988a..082ba915207 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -84,6 +84,7 @@ class Project(Document): issue=task_details.issue, is_group=task_details.is_group, color=task_details.color, + template_task=task_details.name, ) ).insert() @@ -103,9 +104,13 @@ class Project(Document): return date def dependency_mapping(self, template_tasks, project_tasks): - for template_task in template_tasks: - project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0] - project_task = frappe.get_doc("Task", project_task.name) + for project_task in project_tasks: + if project_task.get("template_task"): + template_task = frappe.get_doc("Task", project_task.template_task) + else: + template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] + template_task = frappe.get_doc("Task", template_task.name) + self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) @@ -117,6 +122,7 @@ class Project(Document): filter(lambda x: x.subject == child_task_subject, project_tasks) ) if len(corresponding_project_task): + project_task.reload() # reload, as it might have been updated in the previous iteration project_task.append("depends_on", {"task": corresponding_project_task[0].name}) project_task.save() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 8a599cef753..e49fecd1f47 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template @@ -15,7 +14,7 @@ test_records = frappe.get_test_records("Project") test_ignore = ["Sales Order"] -class TestProject(unittest.TestCase): +class TestProject(FrappeTestCase): def test_project_with_template_having_no_parent_and_depend_tasks(self): project_name = "Test Project with Template - No Parent and Dependend Tasks" frappe.db.sql(""" delete from tabTask where project = %s """, project_name) @@ -155,6 +154,50 @@ class TestProject(unittest.TestCase): so.reload() self.assertFalse(so.project) + def test_project_with_template_tasks_having_common_name(self): + # Step - 1: Create Template Parent Tasks + template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + template_parent_task2 = create_task(subject="Parent Task - 2", is_template=1, is_group=1) + template_parent_task3 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + + # Step - 2: Create Template Child Tasks + template_task1 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task1.name + ) + template_task2 = create_task( + subject="Task - 2", is_template=1, parent_task=template_parent_task2.name + ) + template_task3 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task3.name + ) + + # Step - 3: Create Project Template + template_tasks = [ + template_parent_task1, + template_task1, + template_parent_task2, + template_task2, + template_parent_task3, + template_task3, + ] + project_template = make_project_template( + "Project template with common Task Subject", template_tasks + ) + + # Step - 4: Create Project against the Project Template + project = get_project("Project with common Task Subject", project_template) + project_tasks = frappe.get_all( + "Task", {"project": project.name}, ["subject", "parent_task", "is_group"] + ) + + # Test - 1: No. of Project Tasks should be equal to No. of Template Tasks + self.assertEquals(len(project_tasks), len(template_tasks)) + + # Test - 2: All child Project Tasks should have Parent Task linked + for pt in project_tasks: + if not pt.is_group: + self.assertIsNotNone(pt.parent_task) + def get_project(name, template): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 141a99e612b..33a8799f96c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -52,13 +52,15 @@ "company", "lft", "rgt", - "old_parent" + "old_parent", + "template_task" ], "fields": [ { "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -138,6 +140,7 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -382,6 +385,12 @@ "fieldtype": "Date", "label": "Completed On", "mandatory_depends_on": "eval: doc.status == \"Completed\"" + }, + { + "fieldname": "template_task", + "fieldtype": "Data", + "hidden": 1, + "label": "Template Task" } ], "icon": "fa fa-check", @@ -389,7 +398,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2022-06-23 16:58:47.005241", + "modified": "2023-09-06 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b6b6e2e5ad6..fe24b18098a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -119,19 +119,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } }); - if(this.frm.fields_dict["items"].grid.get_field('batch_no')) { - this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { return me.set_query_for_batch(doc, cdt, cdn); }); - - let batch_field = this.frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } } if( @@ -196,14 +187,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe }); } - let batch_no_field = this.frm.get_docfield("items", "batch_no"); - if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { - return { - "item": row.doc.item_code - } - }; - } if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) { this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) { @@ -257,6 +240,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } ]); } + + if(this.frm.fields_dict['items'].grid.get_field('batch_no')) { + let batch_field = this.frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } + } } is_return() { diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json index f588f9aea1a..f5d7a6dd0c5 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json @@ -23,6 +23,7 @@ { "fieldname": "parent_quality_procedure", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Parent Procedure", "options": "Quality Procedure" }, @@ -115,7 +116,7 @@ "link_fieldname": "procedure" } ], - "modified": "2020-10-26 15:25:39.316088", + "modified": "2023-08-29 12:49:53.963370", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Procedure", @@ -149,5 +150,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 608e23a8268..799ad555a52 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1998,6 +1998,61 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(len(dn.packed_items), 1) self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2") + @change_settings("Selling Settings", {"editable_bundle_item_rates": 1}) + def test_expired_rate_for_packed_item(self): + bundle = "_Test Product Bundle 1" + packed_item = "_Packed Item 1" + + # test Update Items with product bundle + for product_bundle in [bundle]: + if not frappe.db.exists("Item", product_bundle): + bundle_item = make_item(product_bundle, {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + for product_bundle in [packed_item]: + if not frappe.db.exists("Item", product_bundle): + make_item(product_bundle, {"is_stock_item": 0, "stock_uom": "Nos"}) + + make_product_bundle(bundle, [packed_item], 1) + + for scenario in [ + {"valid_upto": add_days(nowdate(), -1), "expected_rate": 0.0}, + {"valid_upto": add_days(nowdate(), 1), "expected_rate": 111.0}, + ]: + with self.subTest(scenario=scenario): + frappe.get_doc( + { + "doctype": "Item Price", + "item_code": packed_item, + "selling": 1, + "price_list": "_Test Price List", + "valid_from": add_days(nowdate(), -1), + "valid_upto": scenario.get("valid_upto"), + "price_list_rate": 111, + } + ).save() + + so = frappe.new_doc("Sales Order") + so.transaction_date = nowdate() + so.delivery_date = nowdate() + so.set_warehouse = "" + so.company = "_Test Company" + so.customer = "_Test Customer" + so.currency = "INR" + so.selling_price_list = "_Test Price List" + so.append("items", {"item_code": bundle, "qty": 1}) + so.save() + + self.assertEqual(len(so.items), 1) + self.assertEqual(len(so.packed_items), 1) + self.assertEqual(so.items[0].item_code, bundle) + self.assertEqual(so.packed_items[0].item_code, packed_item) + self.assertEqual(so.items[0].rate, scenario.get("expected_rate")) + self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate")) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") diff --git a/erpnext/setup/doctype/department/department.json b/erpnext/setup/doctype/department/department.json index 5a16bae0f2e..99deca5c19d 100644 --- a/erpnext/setup/doctype/department/department.json +++ b/erpnext/setup/doctype/department/department.json @@ -25,18 +25,15 @@ "label": "Department", "oldfieldname": "department_name", "oldfieldtype": "Data", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "parent_department", "fieldtype": "Link", + "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Department", - "options": "Department", - "show_days": 1, - "show_seconds": 1 + "options": "Department" }, { "fieldname": "company", @@ -44,9 +41,7 @@ "in_standard_filter": 1, "label": "Company", "options": "Company", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "bold": 1, @@ -54,17 +49,13 @@ "fieldname": "is_group", "fieldtype": "Check", "in_list_view": 1, - "label": "Is Group", - "show_days": 1, - "show_seconds": 1 + "label": "Is Group" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled", - "show_days": 1, - "show_seconds": 1 + "label": "Disabled" }, { "fieldname": "lft", @@ -72,9 +63,7 @@ "hidden": 1, "label": "lft", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "rgt", @@ -82,9 +71,7 @@ "hidden": 1, "label": "rgt", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "old_parent", @@ -92,22 +79,18 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Old Parent", - "print_hide": 1, - "show_days": 1, - "show_seconds": 1 + "print_hide": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" } ], "icon": "fa fa-sitemap", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-06-10 12:28:00.563272", + "modified": "2023-08-28 17:26:46.826501", "modified_by": "Administrator", "module": "Setup", "name": "Department", @@ -147,12 +130,12 @@ "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 1, "share": 1, "write": 1 } ], "show_name_in_global_search": 1, "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index 071c336326f..5a693c5effb 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -66,5 +66,8 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: - frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active") - return frappe.get_value("Employee", {"employee_name": user}, "name") + employee = frappe.get_doc("Employee", {"employee_name": user}) + employee.update(kwargs) + employee.status = "Active" + employee.save() + return employee.name diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 3a056500b54..ba6e247d2e4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -138,6 +138,7 @@ class DeliveryNote(SellingController): self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("uom", "qty") self.validate_with_previous_doc() + self.validate_duplicate_serial_nos() from erpnext.stock.doctype.packed_item.packed_item import make_packing_list @@ -412,6 +413,21 @@ class DeliveryNote(SellingController): pluck="name", ) + def validate_duplicate_serial_nos(self): + serial_nos = [] + for item in self.items: + if not item.serial_no: + continue + + for serial_no in item.serial_no.split("\n"): + if serial_no in serial_nos: + frappe.throw( + _("Row #{0}: Serial No {1} is already selected.").format(item.idx, serial_no), + title=_("Duplicate Serial No"), + ) + else: + serial_nos.append(serial_no) + def update_billed_amount_based_on_so(so_detail, update_modified=True): from frappe.query_builder.functions import Sum diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index e66c23324da..d4a574da73f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -11,10 +11,12 @@ def get_data(): }, "internal_links": { "Sales Order": ["items", "against_sales_order"], - "Sales Invoice": ["items", "against_sales_invoice"], "Material Request": ["items", "material_request"], "Purchase Order": ["items", "purchase_order"], }, + "internal_and_external_links": { + "Sales Invoice": ["items", "against_sales_invoice"], + }, "transactions": [ {"label": _("Related"), "items": ["Sales Invoice", "Packing Slip", "Delivery Trip"]}, {"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]}, diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 2565d1b76d1..2acfd84d944 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1211,6 +1211,38 @@ class TestDeliveryNote(FrappeTestCase): self.assertTrue(return_dn.docstatus == 1) + def test_duplicate_serial_no_in_delivery_note(self): + # Step - 1: Create Serial Item + serial_item = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": frappe.generate_hash("", 10) + ".###", + } + ).name + + # Step - 2: Inward Stock + se = make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=4) + + # Step - 3: Create Delivery Note with Duplicare Serial Nos + serial_nos = se.items[0].serial_no.split("\n") + dn = create_delivery_note( + item_code=serial_item, + warehouse="_Test Warehouse - _TC", + qty=2, + do_not_save=True, + ) + dn.items[0].serial_no = "\n".join(serial_nos[:2]) + dn.append("items", dn.items[0].as_dict()) + + # Test - 1: ValidationError should be raised + self.assertRaises(frappe.ValidationError, dn.save) + + # Step - 4: Submit Delivery Note with unique Serial Nos + dn.items[1].serial_no = "\n".join(serial_nos[2:]) + dn.save() + dn.submit() + def tearDown(self): frappe.db.rollback() frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 9a9ddf44044..b306a41bb83 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -3,6 +3,9 @@ frappe.provide("erpnext.item"); +const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']; +const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; + frappe.ui.form.on("Item", { setup: function(frm) { frm.add_fetch('attribute', 'numeric_values', 'numeric_values'); @@ -347,18 +350,20 @@ $.extend(erpnext.item, { } } - frm.fields_dict['deferred_revenue_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_revenue_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Liability', "is_group": 0 } } } - frm.fields_dict['deferred_expense_account'].get_query = function() { + frm.fields_dict["item_defaults"].grid.get_field("deferred_expense_account").get_query = function(doc, cdt, cdn) { return { filters: { + "company": locals[cdt][cdn].company, 'root_type': 'Asset', "is_group": 0 } @@ -894,7 +899,13 @@ function open_form(frm, doctype, child_doctype, parentfield) { let new_child_doc = frappe.model.add_child(new_doc, child_doctype, parentfield); new_child_doc.item_code = frm.doc.name; new_child_doc.item_name = frm.doc.item_name; - new_child_doc.uom = frm.doc.stock_uom; + if (in_list(SALES_DOCTYPES, doctype) && frm.doc.sales_uom) { + new_child_doc.uom = frm.doc.sales_uom; + } else if (in_list(PURCHASE_DOCTYPES, doctype) && frm.doc.purchase_uom) { + new_child_doc.uom = frm.doc.purchase_uom; + } else { + new_child_doc.uom = frm.doc.stock_uom; + } new_child_doc.description = frm.doc.description; if (!new_child_doc.qty) { new_child_doc.qty = 1.0; diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 7f4ba032e86..7d0a387f43e 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -70,6 +70,13 @@ "variant_based_on", "attributes", "accounting", + "deferred_accounting_section", + "enable_deferred_expense", + "no_of_months_exp", + "column_break_9s9o", + "enable_deferred_revenue", + "no_of_months", + "section_break_avcp", "item_defaults", "purchasing_tab", "purchase_uom", @@ -85,10 +92,6 @@ "delivered_by_supplier", "column_break2", "supplier_items", - "deferred_expense_section", - "enable_deferred_expense", - "deferred_expense_account", - "no_of_months_exp", "foreign_trade_details", "country_of_origin", "column_break_59", @@ -99,10 +102,6 @@ "is_sales_item", "column_break3", "max_discount", - "deferred_revenue", - "enable_deferred_revenue", - "deferred_revenue_account", - "no_of_months", "customer_details", "customer_items", "item_tax_section_break", @@ -657,20 +656,6 @@ "oldfieldname": "max_discount", "oldfieldtype": "Currency" }, - { - "collapsible": 1, - "fieldname": "deferred_revenue", - "fieldtype": "Section Break", - "label": "Deferred Revenue" - }, - { - "depends_on": "enable_deferred_revenue", - "fieldname": "deferred_revenue_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Revenue Account", - "options": "Account" - }, { "default": "0", "fieldname": "enable_deferred_revenue", @@ -681,21 +666,7 @@ "depends_on": "enable_deferred_revenue", "fieldname": "no_of_months", "fieldtype": "Int", - "label": "No of Months" - }, - { - "collapsible": 1, - "fieldname": "deferred_expense_section", - "fieldtype": "Section Break", - "label": "Deferred Expense" - }, - { - "depends_on": "enable_deferred_expense", - "fieldname": "deferred_expense_account", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Deferred Expense Account", - "options": "Account" + "label": "No of Months (Revenue)" }, { "default": "0", @@ -904,6 +875,20 @@ "fieldname": "accounting", "fieldtype": "Tab Break", "label": "Accounting" + }, + { + "fieldname": "column_break_9s9o", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_avcp", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "deferred_accounting_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting" } ], "icon": "fa fa-tag", @@ -912,7 +897,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2023-07-14 17:18:18.658942", + "modified": "2023-09-11 13:46:32.688051", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json index 042d398256a..28956612762 100644 --- a/erpnext/stock/doctype/item_default/item_default.json +++ b/erpnext/stock/doctype/item_default/item_default.json @@ -19,7 +19,11 @@ "selling_defaults", "selling_cost_center", "column_break_12", - "income_account" + "income_account", + "deferred_accounting_defaults_section", + "deferred_expense_account", + "column_break_kwad", + "deferred_revenue_account" ], "fields": [ { @@ -108,11 +112,34 @@ "fieldtype": "Link", "label": "Default Provisional Account", "options": "Account" + }, + { + "fieldname": "deferred_accounting_defaults_section", + "fieldtype": "Section Break", + "label": "Deferred Accounting Defaults" + }, + { + "depends_on": "eval: parent.enable_deferred_expense", + "fieldname": "deferred_expense_account", + "fieldtype": "Link", + "label": "Deferred Expense Account", + "options": "Account" + }, + { + "depends_on": "eval: parent.enable_deferred_revenue", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "label": "Deferred Revenue Account", + "options": "Account" + }, + { + "fieldname": "column_break_kwad", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2022-04-10 20:18:54.148195", + "modified": "2023-09-04 12:33:14.607267", "modified_by": "Administrator", "module": "Stock", "name": "Item Default", diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index ffec57ca1df..25c765bbced 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -296,6 +296,7 @@ "depends_on": "eval:doc.material_request_type == 'Material Transfer'", "fieldname": "set_from_warehouse", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Set Source Warehouse", "options": "Warehouse" }, @@ -356,7 +357,7 @@ "idx": 70, "is_submittable": 1, "links": [], - "modified": "2023-07-25 17:19:31.662662", + "modified": "2023-09-15 12:07:24.789471", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index bfb0d4e1853..311b0ed8ce9 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -706,6 +706,7 @@ def raise_work_orders(material_request): ) wo_order.set_work_order_operations() + wo_order.flags.ignore_mandatory = True wo_order.save() work_orders.append(wo_order.name) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index dbd8de4fcb0..a9e9ad1a639 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -207,6 +207,9 @@ def update_packed_item_price_data(pi_row, item_data, doc): "conversion_rate": doc.get("conversion_rate"), } ) + if not row_data.get("transaction_date"): + row_data.update({"transaction_date": doc.get("transaction_date")}) + rate = get_price_list_rate(row_data, item_doc).get("price_list_rate") pi_row.rate = rate or item_data.get("valuation_rate") or 0.0 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 4f6c6364a47..1873efc711a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -605,7 +605,7 @@ class PurchaseReceipt(BuyingController): account=provisional_account, cost_center=item.cost_center, debit=0.0, - credit=multiplication_factor * item.amount, + credit=multiplication_factor * item.base_amount, remarks=remarks, against_account=expense_account, account_currency=credit_currency, @@ -619,7 +619,7 @@ class PurchaseReceipt(BuyingController): gl_entries=gl_entries, account=expense_account, cost_center=item.cost_center, - debit=multiplication_factor * item.amount, + debit=multiplication_factor * item.base_amount, credit=0.0, remarks=remarks, against_account=provisional_account, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c9433cf5106..2f46809f49d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2024,6 +2024,49 @@ class TestPurchaseReceipt(FrappeTestCase): ste7.reload() self.assertEqual(ste7.items[0].valuation_rate, valuation_rate) + def test_purchase_receipt_provisional_accounting(self): + # Step - 1: Create Supplier with Default Currency as USD + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + supplier = create_supplier(default_currency="USD") + + # Step - 2: Setup Company for Provisional Accounting + from erpnext.accounts.doctype.account.test_account import create_account + + provisional_account = create_account( + account_name="Provision Account", + parent_account="Current Liabilities - _TC", + company="_Test Company", + ) + company = frappe.get_doc("Company", "_Test Company") + company.enable_provisional_accounting_for_non_stock_items = 1 + company.default_provisional_account = provisional_account + company.save() + + # Step - 3: Create Non-Stock Item + item = make_item(properties={"is_stock_item": 0}) + + # Step - 4: Create Purchase Receipt + pr = make_purchase_receipt( + qty=2, + item_code=item.name, + company=company.name, + supplier=supplier.name, + currency=supplier.default_currency, + ) + + # Test - 1: Total and Base Total should not be the same as the currency is different + self.assertNotEqual(flt(pr.total, 2), flt(pr.base_total, 2)) + self.assertEqual(flt(pr.total * pr.conversion_rate, 2), flt(pr.base_total, 2)) + + # Test - 2: Sum of Debit or Credit should be equal to Purchase Receipt Base Total + amount = frappe.db.get_value("GL Entry", {"docstatus": 1, "voucher_no": pr.name}, ["sum(debit)"]) + expected_amount = pr.base_total + self.assertEqual(amount, expected_amount) + + company.enable_provisional_accounting_for_non_stock_items = 0 + company.save() + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index eea28791a9f..05fa2324dd4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { setup: function(frm) { + frm.set_query("reference_name", function() { + return { + filters: { + "docstatus": ["!=", 2], + } + } + }); + frm.set_query("batch_no", function() { return { filters: { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index dd08ef4d1e3..6dd0a58645c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -101,15 +101,6 @@ frappe.ui.form.on('Stock Entry', { } }); - let batch_field = frm.get_docfield('items', 'batch_no'); - if (batch_field) { - batch_field.get_route_options_for_new_doc = (row) => { - return { - 'item': row.doc.item_code - } - }; - } - frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); @@ -345,6 +336,15 @@ frappe.ui.form.on('Stock Entry', { if(!check_should_not_attach_bom_items(frm.doc.bom_no)) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); } + + let batch_field = frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } }, get_items_from_transit_entry: function(frm) { diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f3adefb3e74..f7eb859f830 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -729,7 +729,11 @@ def get_default_discount_account(args, item): def get_default_deferred_account(args, item, fieldname=None): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): return ( - item.get(fieldname) + frappe.get_cached_value( + "Item Default", + {"parent": args.item_code, "company": args.get("company")}, + fieldname, + ) or args.get(fieldname) or frappe.get_cached_value("Company", args.company, "default_" + fieldname) ) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 7e81a72028f..80bf8508cf3 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -323,8 +323,10 @@ class StockBalanceReport(object): for field in ["item_code", "brand"]: if not self.filters.get(field): continue - - query = query.where(item_table[field] == self.filters.get(field)) + elif field == "item_code": + query = query.where(item_table.name == self.filters.get(field)) + else: + query = query.where(item_table[field] == self.filters.get(field)) return query diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 4bf008ac406..e335c6ba7a0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -75,15 +75,6 @@ frappe.ui.form.on('Subcontracting Receipt', { } } }); - - let batch_no_field = frm.get_docfield('items', 'batch_no'); - if (batch_no_field) { - batch_no_field.get_route_options_for_new_doc = function(row) { - return { - 'item': row.doc.item_code - } - }; - } }, refresh: (frm) => { @@ -148,6 +139,15 @@ frappe.ui.form.on('Subcontracting Receipt', { frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM'); } + + let batch_no_field = frm.get_docfield('items', 'batch_no'); + if (batch_no_field) { + batch_no_field.get_route_options_for_new_doc = function(row) { + return { + 'item': row.doc.item_code + } + }; + } }, set_warehouse: (frm) => { @@ -202,4 +202,4 @@ let set_missing_values = (frm) => { if (!r.exc) frm.refresh(); }, }); -}; \ No newline at end of file +}; diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index cf457dfe82d..130f38fb809 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -176,10 +176,9 @@ class SubcontractingReceipt(SubcontractingController): item.rm_cost_per_qty = item.rm_supp_cost / item.qty rm_supp_cost.pop(item.name) - if item.recalculate_rate: - item.rate = ( - flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) - ) + item.rate = ( + flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty) + ) item.received_qty = item.qty + flt(item.rejected_qty) item.amount = item.qty * item.rate diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index f6cf3402ad2..f7e88d0fb7f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -29,7 +29,6 @@ "rate_and_amount", "rate", "amount", - "recalculate_rate", "column_break_19", "rm_cost_per_qty", "service_cost_per_qty", @@ -196,7 +195,6 @@ "options": "currency", "print_width": "100px", "read_only": 1, - "read_only_depends_on": "eval: doc.recalculate_rate", "width": "100px" }, { @@ -466,18 +464,12 @@ "fieldname": "accounting_details_section", "fieldtype": "Section Break", "label": "Accounting Details" - }, - { - "default": "1", - "fieldname": "recalculate_rate", - "fieldtype": "Check", - "label": "Recalculate Rate" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-06 18:44:45.599761", + "modified": "2023-09-03 17:04:21.214316", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item",