diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 2e5674874ccc..562d4893717a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -623,7 +623,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_outstanding_invoice: function(frm) { + get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) { const today = frappe.datetime.get_today(); const fields = [ {fieldtype:"Section Break", label: __("Posting Date")}, @@ -653,12 +653,29 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, ]; + let btn_text = ""; + + if (get_outstanding_invoices) { + btn_text = "Get Outstanding Invoices"; + } + else if (get_orders_to_be_billed) { + btn_text = "Get Outstanding Orders"; + } + frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); frm.doc.cost_center = filters.cost_center; - frm.events.get_outstanding_documents(frm, filters); - }, __("Filters"), __("Get Outstanding Documents")); + frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed); + }, __("Filters"), __(btn_text)); + }, + + get_outstanding_invoices: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, true, false); + }, + + get_outstanding_orders: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, false, true); }, validate_filters_data: function(frm, filters) { @@ -684,7 +701,7 @@ frappe.ui.form.on('Payment Entry', { } }, - get_outstanding_documents: function(frm, filters) { + get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) { frm.clear_table("references"); if(!frm.doc.party) { @@ -708,6 +725,13 @@ frappe.ui.form.on('Payment Entry', { args[key] = filters[key]; } + if (get_outstanding_invoices) { + args["get_outstanding_invoices"] = true; + } + else if (get_orders_to_be_billed) { + args["get_orders_to_be_billed"] = true; + } + frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; return frappe.call({ diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3fc1adff2d37..cb04c1c66b5e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -48,7 +48,8 @@ "base_received_amount", "base_received_amount_after_tax", "section_break_14", - "get_outstanding_invoice", + "get_outstanding_invoices", + "get_outstanding_orders", "references", "section_break_34", "total_allocated_amount", @@ -353,12 +354,6 @@ "fieldtype": "Section Break", "label": "Reference" }, - { - "depends_on": "eval:doc.docstatus==0", - "fieldname": "get_outstanding_invoice", - "fieldtype": "Button", - "label": "Get Outstanding Invoice" - }, { "fieldname": "references", "fieldtype": "Table", @@ -726,12 +721,24 @@ "fieldname": "section_break_60", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_invoices", + "fieldtype": "Button", + "label": "Get Outstanding Invoices" + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_orders", + "fieldtype": "Button", + "label": "Get Outstanding Orders" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-02-23 20:08:39.559814", + "modified": "2023-06-19 11:38:04.387219", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9db4c45dae08..6cb514e45d2a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -174,6 +174,8 @@ def validate_allocated_amount_with_latest_data(self): "payment_type": self.payment_type, "party": self.party, "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + "get_outstanding_invoices": True, + "get_orders_to_be_billed": True, } ) @@ -198,7 +200,7 @@ def validate_allocated_amount_with_latest_data(self): ): frappe.throw( _( - "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." ).format(d.reference_doctype, d.reference_name) ) @@ -1365,32 +1367,48 @@ def get_outstanding_reference_documents(args): if args.get("company"): condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) - outstanding_invoices = get_outstanding_invoices( - args.get("party_type"), - args.get("party"), - args.get("party_account"), - args.get("company"), - filters=args, - condition=condition, - ) + outstanding_invoices = [] + negative_outstanding_invoices = [] + + if args.get("get_outstanding_invoices"): + outstanding_invoices = get_outstanding_invoices( + args.get("party_type"), + args.get("party"), + args.get("party_account"), + args.get("company"), + filters=args, + condition=condition, + ) - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) - for d in outstanding_invoices: - d["exchange_rate"] = 1 - if party_account_currency != company_currency: - if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"): - d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") - elif d.voucher_type == "Journal Entry": - d["exchange_rate"] = get_exchange_rate( - party_account_currency, company_currency, d.posting_date - ) - if d.voucher_type in ("Purchase Invoice"): - d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") + for d in outstanding_invoices: + d["exchange_rate"] = 1 + if party_account_currency != company_currency: + if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"): + d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") + elif d.voucher_type == "Journal Entry": + d["exchange_rate"] = get_exchange_rate( + party_account_currency, company_currency, d.posting_date + ) + if d.voucher_type in ("Purchase Invoice"): + d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") + + # Get negative outstanding sales /purchase invoices + negative_outstanding_invoices = [] + if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices( + args.get("party_type"), + args.get("party"), + args.get("party_account"), + party_account_currency, + company_currency, + condition=condition, + ) # Get all SO / PO which are not fully billed or against which full advance not paid orders_to_be_billed = [] - if args.get("party_type") != "Student": + if args.get("get_orders_to_be_billed") and args.get("party_type") != "Student": orders_to_be_billed = get_orders_to_be_billed( args.get("posting_date"), args.get("party_type"), @@ -1401,25 +1419,22 @@ def get_outstanding_reference_documents(args): filters=args, ) - # Get negative outstanding sales /purchase invoices - negative_outstanding_invoices = [] - if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices( - args.get("party_type"), - args.get("party"), - args.get("party_account"), - party_account_currency, - company_currency, - condition=condition, - ) - data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: + if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"): + ref_document_type = "invoices or orders" + elif args.get("get_outstanding_invoices"): + ref_document_type = "invoices" + elif args.get("get_orders_to_be_billed"): + ref_document_type = "orders" + frappe.msgprint( _( - "No outstanding invoices found for the {0} {1} which qualify the filters you have specified." - ).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))) + "No outstanding {0} found for the {1} {2} which qualify the filters you have specified." + ).format( + ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) + ) ) return data diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index cd5f36670715..f0ca405401d3 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -125,12 +125,14 @@ def get_revenue(data, period_list, include_in_gross=1): data_to_be_removed = True while data_to_be_removed: - revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list) - revenue = adjust_account(revenue, period_list) + revenue, data_to_be_removed = remove_parent_with_no_child(revenue) + + adjust_account_totals(revenue, period_list) + return copy.deepcopy(revenue) -def remove_parent_with_no_child(data, period_list): +def remove_parent_with_no_child(data): data_to_be_removed = False for parent in data: if "is_group" in parent and parent.get("is_group") == 1: @@ -147,16 +149,19 @@ def remove_parent_with_no_child(data, period_list): return data, data_to_be_removed -def adjust_account(data, period_list, consolidated=False): - leaf_nodes = [item for item in data if item["is_group"] == 0] +def adjust_account_totals(data, period_list): totals = {} - for node in leaf_nodes: - set_total(node, node["total"], data, totals) - for d in data: - for period in period_list: - key = period if consolidated else period.key - d["total"] = totals[d["account"]] - return data + for d in reversed(data): + if d.get("is_group"): + for period in period_list: + # reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check + d[period.key] = sum( + item[period.key] for item in data if item.get("parent_account") == d.get("account") + ) + else: + set_total(d, d["total"], data, totals) + + d["total"] = totals[d["account"]] def set_total(node, value, complete_list, totals): @@ -191,6 +196,9 @@ def get_profit( if profit_loss[key]: has_value = True + if not profit_loss.get("total"): + profit_loss["total"] = 0 + profit_loss["total"] += profit_loss[key] if has_value: return profit_loss @@ -229,6 +237,9 @@ def get_net_profit( if profit_loss[key]: has_value = True + if not profit_loss.get("total"): + profit_loss["total"] = 0 + profit_loss["total"] += profit_loss[key] if has_value: return profit_loss diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 4f79ae847caf..d086f180d3a8 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -466,15 +466,19 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None, "cost_center": depreciation_cost_center, "posting_date": date, }, - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - "posting_date": date, - }, ] + if accumulated_depr_amount: + gl_entries.append( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": date, + }, + ) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: get_profit_gl_entries( diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 4f7b83610777..b788a32d6abd 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -19,13 +19,49 @@ frappe.query_reports["Fixed Asset Register"] = { options: "\nIn Location\nDisposed", default: 'In Location' }, + { + fieldname:"asset_category", + label: __("Asset Category"), + fieldtype: "Link", + options: "Asset Category" + }, + { + fieldname:"cost_center", + label: __("Cost Center"), + fieldtype: "Link", + options: "Cost Center" + }, + { + fieldname:"group_by", + label: __("Group By"), + fieldtype: "Select", + options: ["--Select a group--", "Asset Category", "Location"], + default: "--Select a group--", + reqd: 1 + }, + { + fieldname:"only_existing_assets", + label: __("Only existing assets"), + fieldtype: "Check" + }, + { + fieldname:"finance_book", + label: __("Finance Book"), + fieldtype: "Link", + options: "Finance Book", + }, + { + "fieldname": "include_default_book_assets", + "label": __("Include Default Book Assets"), + "fieldtype": "Check", + "default": 1 + }, { "fieldname":"filter_based_on", "label": __("Period Based On"), "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": "Fiscal Year", - "reqd": 1 + "options": ["--Select a period--", "Fiscal Year", "Date Range"], + "default": "--Select a period--", }, { "fieldname":"from_date", @@ -33,7 +69,6 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Date", "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12), "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 }, { "fieldname":"to_date", @@ -41,7 +76,6 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Date", "default": frappe.datetime.nowdate(), "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 }, { "fieldname":"from_fiscal_year", @@ -50,7 +84,6 @@ frappe.query_reports["Fixed Asset Register"] = { "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 }, { "fieldname":"to_fiscal_year", @@ -59,7 +92,6 @@ frappe.query_reports["Fixed Asset Register"] = { "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 }, { "fieldname":"date_based_on", @@ -67,44 +99,7 @@ frappe.query_reports["Fixed Asset Register"] = { "fieldtype": "Select", "options": ["Purchase Date", "Available For Use Date"], "default": "Purchase Date", - "reqd": 1 - }, - { - fieldname:"asset_category", - label: __("Asset Category"), - fieldtype: "Link", - options: "Asset Category" - }, - { - fieldname:"cost_center", - label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center" - }, - { - fieldname:"group_by", - label: __("Group By"), - fieldtype: "Select", - options: ["--Select a group--", "Asset Category", "Location"], - default: "--Select a group--", - reqd: 1 - }, - { - fieldname:"finance_book", - label: __("Finance Book"), - fieldtype: "Link", - options: "Finance Book", - depends_on: "eval: doc.filter_by_finance_book == 1", - }, - { - fieldname:"filter_by_finance_book", - label: __("Filter by Finance Book"), - fieldtype: "Check" - }, - { - fieldname:"only_existing_assets", - label: __("Only existing assets"), - fieldtype: "Check" + "depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'", }, ] }; diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index c6fbc6c58e87..f810819b4fc7 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -2,9 +2,11 @@ # For license information, please see license.txt +from itertools import chain + import frappe from frappe import _ -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cstr, flt, formatdate, getdate from erpnext.accounts.report.financial_statements import ( @@ -13,7 +15,6 @@ validate_fiscal_year, ) from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation -from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts def execute(filters=None): @@ -64,11 +65,9 @@ def get_conditions(filters): def get_data(filters): - data = [] conditions = get_conditions(filters) - depreciation_amount_map = get_finance_book_value_map(filters) pr_supplier_map = get_purchase_receipt_supplier_map() pi_supplier_map = get_purchase_invoice_supplier_map() @@ -102,20 +101,27 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = None + assets_linked_to_fb = get_assets_linked_to_fb(filters) - if filters.filter_by_finance_book: - assets_linked_to_fb = frappe.db.get_all( - doctype="Asset Finance Book", - filters={"finance_book": filters.finance_book or ("is", "not set")}, - pluck="parent", - ) + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") + + if filters.include_default_book_assets and company_fb: + finance_book = company_fb + elif filters.finance_book: + finance_book = filters.finance_book + else: + finance_book = None + + depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book) for asset in assets_record: if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) + asset_value = get_asset_value_after_depreciation( + asset.asset_id, finance_book + ) or get_asset_value_after_depreciation(asset.asset_id) + row = { "asset_id": asset.asset_id, "asset_name": asset.asset_name, @@ -126,7 +132,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters), + "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map), "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -140,14 +146,23 @@ def get_data(filters): def prepare_chart_data(data, filters): labels_values_map = {} - date_field = frappe.scrub(filters.date_based_on) + if filters.filter_based_on not in ("Date Range", "Fiscal Year"): + filters_filter_based_on = "Date Range" + date_field = "purchase_date" + filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field) + filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field) + else: + filters_filter_based_on = filters.filter_based_on + date_field = frappe.scrub(filters.date_based_on) + filters_from_date = filters.from_date + filters_to_date = filters.to_date period_list = get_period_list( filters.from_fiscal_year, filters.to_fiscal_year, - filters.from_date, - filters.to_date, - filters.filter_based_on, + filters_from_date, + filters_to_date, + filters_filter_based_on, "Monthly", company=filters.company, ignore_fiscal_year=True, @@ -184,57 +199,76 @@ def prepare_chart_data(data, filters): } -def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters): - if asset.calculate_depreciation: - depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 - else: - depr_amount = get_manual_depreciation_amount_of_asset(asset, filters) +def get_assets_linked_to_fb(filters): + afb = frappe.qb.DocType("Asset Finance Book") - return flt(depr_amount, 2) + query = frappe.qb.from_(afb).select( + afb.parent, + ) + if filters.include_default_book_assets: + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") -def get_finance_book_value_map(filters): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): + frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'")) - return frappe._dict( - frappe.db.sql( - """ Select - parent, SUM(depreciation_amount) - FROM `tabDepreciation Schedule` - WHERE - parentfield='schedules' - AND schedule_date<=%s - AND journal_entry IS NOT NULL - AND ifnull(finance_book, '')=%s - GROUP BY parent""", - (date, cstr(filters.finance_book or "")), + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) + | (afb.finance_book.isnull()) + ) + else: + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull()) ) - ) + assets_linked_to_fb = list(chain(*query.run(as_list=1))) -def get_manual_depreciation_amount_of_asset(asset, filters): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + return assets_linked_to_fb + + +def get_depreciation_amount_of_asset(asset, depreciation_amount_map): + return depreciation_amount_map.get(asset.asset_id) or 0.0 - (_, _, depreciation_expense_account) = get_depreciation_accounts(asset) +def get_asset_depreciation_amount_map(filters, finance_book): + date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date + + asset = frappe.qb.DocType("Asset") gle = frappe.qb.DocType("GL Entry") + aca = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") - result = ( + query = ( frappe.qb.from_(gle) - .select(Sum(gle.debit)) - .where(gle.against_voucher == asset.asset_id) - .where(gle.account == depreciation_expense_account) + .join(asset) + .on(gle.against_voucher == asset.name) + .join(aca) + .on((aca.parent == asset.asset_category) & (aca.company_name == asset.company)) + .join(company) + .on(company.name == asset.company) + .select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount")) + .where( + gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account) + ) .where(gle.debit != 0) .where(gle.is_cancelled == 0) - .where(gle.posting_date <= date) - ).run() + .where(asset.docstatus == 1) + .groupby(asset.name) + ) - if result and result[0] and result[0][0]: - depr_amount = result[0][0] + if finance_book: + query = query.where( + (gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull()) + ) else: - depr_amount = 0 + query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull())) + + if filters.filter_based_on in ("Date Range", "Fiscal Year"): + query = query.where(gle.posting_date <= date) + + asset_depr_amount_map = query.run() - return depr_amount + return dict(asset_depr_amount_map) def get_purchase_receipt_supplier_map(): diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 8adbd72b6db4..a6b91806e78c 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -293,8 +293,8 @@ def get_last_accrual_date(loan, posting_date): # interest for last interest accrual date is already booked, so add 1 day last_disbursement_date = get_last_disbursement_date(loan, posting_date) - if last_disbursement_date and getdate(last_disbursement_date) > getdate( - last_interest_accrual_date + if last_disbursement_date and getdate(last_disbursement_date) > add_days( + getdate(last_interest_accrual_date), 1 ): last_interest_accrual_date = last_disbursement_date diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0f4606d39472..bccaade5737c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -113,6 +113,7 @@ def validate(self): self.set_status() self.po_required() + self.validate_items_quality_inspection() self.validate_with_previous_doc() self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") @@ -183,6 +184,26 @@ def po_required(self): if not d.purchase_order: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) + def validate_items_quality_inspection(self): + for item in self.get("items"): + if item.quality_inspection: + qi = frappe.db.get_value( + "Quality Inspection", + item.quality_inspection, + ["reference_type", "reference_name", "item_code"], + as_dict=True, + ) + + if qi.reference_type != self.doctype or qi.reference_name != self.name: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type + {frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}.""" + frappe.throw(_(msg)) + + if qi.item_code != item.item_code: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code + {frappe.bold(item.item_code)}.""" + frappe.throw(_(msg)) + def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql( """select sum(qty) from `tabPurchase Receipt Item` diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index f56e059f81c3..76a8d3da901f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -122,7 +122,8 @@ "oldfieldname": "purpose", "oldfieldtype": "Select", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "company", @@ -619,7 +620,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-05-02 05:21:39.060501", + "modified": "2023-06-19 18:23:40.748114", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry",