diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 0e238e08f62..305ee72edcc 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -105,7 +105,7 @@ }, { "default": "0", - "description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field", + "description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year", "fieldname": "check_supplier_invoice_uniqueness", "fieldtype": "Check", "label": "Check Supplier Invoice Number Uniqueness" @@ -461,7 +461,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-01-30 14:04:26.553554", + "modified": "2024-03-15 12:11:36.085158", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -490,4 +490,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4de20a17389..402f19ee968 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -454,7 +454,7 @@ class JournalEntry(AccountsController): self.voucher_type == "Depreciation Entry" and d.reference_type == "Asset" and d.reference_name - and d.account_type == "Depreciation" + and frappe.get_cached_value("Account", d.account, "root_type") == "Expense" and d.debit ): asset = frappe.get_doc("Asset", d.reference_name) 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 c73c13eb118..e0ec144e314 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 @@ -158,7 +158,7 @@ def set_ageing(doc, entry): ageing_filters = frappe._dict( { "company": doc.company, - "report_date": doc.to_date, + "report_date": doc.posting_date, "ageing_based_on": doc.ageing_based_on, "range1": 30, "range2": 60, 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 647600a9fea..bf8de073853 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 @@ -340,10 +340,11 @@ - - - - + + + + + @@ -352,6 +353,7 @@ +
30 Days60 Days90 Days120 Days0 - 30 Days30 - 60 Days60 - 90 Days90 - 120 DaysAbove 120 Days
{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }} {{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }} {{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 272a180ca8d..496ffcd2648 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1182,7 +1182,7 @@ class PurchaseInvoice(BuyingController): asset.name, { "gross_purchase_amount": purchase_amount, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, }, ) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 51da848d9da..1b36bf50f86 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2040,7 +2040,7 @@ { "fieldname": "contact_and_address_tab", "fieldtype": "Tab Break", - "label": "Contact & Address" + "label": "Address & Contact" }, { "fieldname": "payments_tab", @@ -2187,7 +2187,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-04-11 11:30:26.272441", + "modified": "2024-05-08 18:02:28.549041", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 70cd8ce1f97..e6015e081d6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -188,7 +188,9 @@ def set_address_details( *, ignore_permissions=False, ): - billing_address_field = "customer_address" if party_type == "Lead" else party_type.lower() + "_address" + billing_address_field = ( + "customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address" + ) party_details[billing_address_field] = party_address or get_default_address(party_type, party.name) if doctype: party_details.update( diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 2bd493cd4d0..64dba0183dc 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1028,20 +1028,6 @@ class ReceivablePayableReport: fieldtype="Link", options="Contact", ) - if self.filters.party_type == "Customer": - self.add_column( - _("Customer Name"), - fieldname="customer_name", - fieldtype="Link", - options="Customer", - ) - elif self.filters.party_type == "Supplier": - self.add_column( - _("Supplier Name"), - fieldname="supplier_name", - fieldtype="Link", - options="Supplier", - ) self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data") self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data") diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 0f71e5d6f60..5ed62cb132f 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -652,7 +652,7 @@ frappe.ui.form.on("Asset", { ); frm.set_value("gross_purchase_amount", purchase_amount); - frm.set_value("purchase_receipt_amount", purchase_amount); + frm.set_value("purchase_amount", purchase_amount); frm.set_value("asset_quantity", asset_quantity); frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center); if (item.asset_location) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 39a0867d984..3a2a942bdf2 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -72,7 +72,7 @@ "status", "booked_fixed_asset", "column_break_51", - "purchase_receipt_amount", + "purchase_amount", "default_finance_book", "depr_entry_posting_status", "amended_from", @@ -408,15 +408,6 @@ "options": "Purchase Receipt", "print_hide": 1 }, - { - "fieldname": "purchase_receipt_amount", - "fieldtype": "Currency", - "hidden": 1, - "label": "Purchase Receipt Amount", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, { "depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset", "fieldname": "purchase_invoice", @@ -546,6 +537,15 @@ "label": "Additional Asset Cost", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "purchase_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Purchase Amount", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 72, @@ -589,7 +589,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-01-15 17:35:49.226603", + "modified": "2024-04-18 16:45:47.306032", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -633,4 +633,4 @@ "states": [], "title_field": "asset_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 3a05a598b1c..4edfcd11c89 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -92,10 +92,10 @@ class Asset(AccountsController): number_of_depreciations_booked: DF.Int opening_accumulated_depreciation: DF.Currency policy_number: DF.Data | None + purchase_amount: DF.Currency purchase_date: DF.Date | None purchase_invoice: DF.Link | None purchase_receipt: DF.Link | None - purchase_receipt_amount: DF.Currency split_from: DF.Link | None status: DF.Literal[ "Draft", @@ -354,7 +354,7 @@ class Asset(AccountsController): if self.is_existing_asset: return - if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: + if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_amount: error_message = _( "Gross Purchase Amount should be equal to purchase amount of one single Asset." ) @@ -696,7 +696,7 @@ class Asset(AccountsController): purchase_document = self.get_purchase_document() fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() - if purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate(): + if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate(): gl_entries.append( self.get_gl_dict( { @@ -704,8 +704,8 @@ class Asset(AccountsController): "against": fixed_asset_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "credit": self.purchase_receipt_amount, - "credit_in_account_currency": self.purchase_receipt_amount, + "credit": self.purchase_amount, + "credit_in_account_currency": self.purchase_amount, "cost_center": self.cost_center, }, item=self, @@ -719,8 +719,8 @@ class Asset(AccountsController): "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "debit": self.purchase_receipt_amount, - "debit_in_account_currency": self.purchase_receipt_amount, + "debit": self.purchase_amount, + "debit_in_account_currency": self.purchase_amount, "cost_center": self.cost_center, }, item=self, @@ -1116,8 +1116,8 @@ def create_new_asset_after_split(asset, split_qty): ) new_asset.gross_purchase_amount = new_gross_purchase_amount - if asset.purchase_receipt_amount: - new_asset.purchase_receipt_amount = new_gross_purchase_amount + if asset.purchase_amount: + new_asset.purchase_amount = new_gross_purchase_amount new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation new_asset.asset_quantity = split_qty new_asset.split_from = asset.name diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 1b3951e86a6..7e0c3ad6888 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1000,7 +1000,7 @@ class TestDepreciationBasics(AssetSetup): asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active") - depreciation_amount = get_depreciation_amount( + depreciation_amount, prev_per_day_depr = get_depreciation_amount( asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0] ) self.assertEqual(depreciation_amount, 30000) @@ -1698,7 +1698,7 @@ def create_asset(**args): "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, "gross_purchase_amount": args.gross_purchase_amount or 100000, - "purchase_receipt_amount": args.purchase_receipt_amount or 100000, + "purchase_amount": args.purchase_amount or 100000, "maintenance_required": args.maintenance_required or 0, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": args.available_for_use_date or "2020-06-06", @@ -1723,6 +1723,7 @@ def create_asset(**args): "depreciation_start_date": args.depreciation_start_date, "daily_prorata_based": args.daily_prorata_based or 0, "shift_based": args.shift_based or 0, + "rate_of_depreciation": args.rate_of_depreciation or 0, }, ) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 8d3bcfc153d..573dd92c585 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -145,7 +145,7 @@ class AssetCapitalization(StockController): def on_trash(self): frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None) - super(AssetCapitalization, self).on_trash() + super().on_trash() def cancel_target_asset(self): if self.entry_type == "Capitalization" and self.target_asset: @@ -616,8 +616,7 @@ class AssetCapitalization(StockController): asset_doc.available_for_use_date = self.posting_date asset_doc.purchase_date = self.posting_date asset_doc.gross_purchase_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.purchase_amount = total_target_asset_value asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.flags.asset_created_via_asset_capitalization = True @@ -653,7 +652,7 @@ class AssetCapitalization(StockController): asset_doc = frappe.get_doc("Asset", self.target_asset) asset_doc.gross_purchase_amount = total_target_asset_value - asset_doc.purchase_receipt_amount = total_target_asset_value + asset_doc.purchase_amount = total_target_asset_value asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.save() diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 86a18c07d1f..31723ef3be3 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -89,7 +89,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test Consumed Asset values self.assertEqual(consumed_asset.db_get("status"), "Capitalized") @@ -179,7 +179,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test Consumed Asset values self.assertEqual(consumed_asset.db_get("status"), "Capitalized") @@ -256,7 +256,7 @@ class TestAssetCapitalization(unittest.TestCase): # Test Target Asset values target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) self.assertEqual(target_asset.gross_purchase_amount, total_amount) - self.assertEqual(target_asset.purchase_receipt_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) # Test General Ledger Entries expected_gle = { @@ -526,7 +526,7 @@ def create_depreciation_asset(**args): asset.available_for_use_date = args.available_for_use_date or asset.purchase_date asset.gross_purchase_amount = args.asset_value or 100000 - asset.purchase_receipt_amount = asset.gross_purchase_amount + asset.purchase_amount = asset.gross_purchase_amount finance_book = asset.append("finance_books") finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31" diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index abbca68fea0..c1ea42ba020 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -285,6 +285,7 @@ class AssetDepreciationSchedule(Document): number_of_pending_depreciations = final_number_of_depreciations - start yearly_opening_wdv = value_after_depreciation current_fiscal_year_end_date = None + prev_per_day_depr = True for n in range(start, final_number_of_depreciations): # If depreciation is already completed (for double declining balance) if skip_row: @@ -301,8 +302,7 @@ class AssetDepreciationSchedule(Document): prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount else: prev_depreciation_amount = 0 - - depreciation_amount = get_depreciation_amount( + depreciation_amount, prev_per_day_depr = get_depreciation_amount( self, asset_doc, value_after_depreciation, @@ -312,6 +312,7 @@ class AssetDepreciationSchedule(Document): prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, number_of_pending_depreciations, + prev_per_day_depr, ) if not has_pro_rata or ( n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2 @@ -599,11 +600,12 @@ def get_depreciation_amount( prev_depreciation_amount=0, has_wdv_or_dd_non_yearly_pro_rata=False, number_of_pending_depreciations=0, + prev_per_day_depr=0, ): if fb_row.depreciation_method in ("Straight Line", "Manual"): return get_straight_line_or_manual_depr_amount( asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations - ) + ), None else: return get_wdv_or_dd_depr_amount( asset, @@ -614,6 +616,7 @@ def get_depreciation_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ) @@ -637,49 +640,14 @@ def get_straight_line_or_manual_depr_amount( elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: if row.daily_prorata_based: amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - total_days = ( - date_diff( - 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, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations - - 1 - ) - * row.frequency_of_depreciation, - ) - ), - 1, - ), - ) - + 1 - ) - daily_depr_amount = amount / total_days - - to_date = get_last_day( - add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) + return get_daily_prorata_based_straight_line_depr( + asset, + row, + schedule_idx, + number_of_pending_depreciations, + amount, ) - 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) @@ -692,40 +660,9 @@ def get_straight_line_or_manual_depr_amount( - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) ) - - total_days = ( - date_diff( - 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, - ), - ) - + 1 + return get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount ) - - daily_depr_amount = amount / total_days - - to_date = get_last_day( - add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - ) - 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) @@ -734,6 +671,23 @@ def get_straight_line_or_manual_depr_amount( ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) +def get_daily_prorata_based_straight_line_depr( + asset, row, schedule_idx, number_of_pending_depreciations, amount +): + total_years = flt(number_of_pending_depreciations * row.frequency_of_depreciation) / 12 + every_year_depr = amount / total_years + + year_start_date = add_years( + row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12 + ) + year_end_date = add_days(add_years(year_start_date, 1), -1) + daily_depr_amount = every_year_depr / (date_diff(year_end_date, year_start_date) + 1) + from_date, total_depreciable_days = _get_total_days( + row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation + ) + return daily_depr_amount * total_depreciable_days + + def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation: return ( @@ -779,6 +733,7 @@ def get_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ): return get_default_wdv_or_dd_depr_amount( asset, @@ -788,6 +743,7 @@ def get_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, ) @@ -799,6 +755,39 @@ def get_default_wdv_or_dd_depr_amount( prev_depreciation_amount, has_wdv_or_dd_non_yearly_pro_rata, asset_depr_schedule, + prev_per_day_depr, +): + if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12: + return _get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + ), None + else: + return _get_daily_prorata_based_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + prev_per_day_depr, + ) + + +def _get_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, ): if cint(fb_row.frequency_of_depreciation) == 12: return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100) @@ -825,6 +814,75 @@ def get_default_wdv_or_dd_depr_amount( return prev_depreciation_amount +def _get_daily_prorata_based_default_wdv_or_dd_depr_amount( + asset, + fb_row, + depreciable_value, + schedule_idx, + prev_depreciation_amount, + has_wdv_or_dd_non_yearly_pro_rata, + asset_depr_schedule, + prev_per_day_depr, +): + if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month + if schedule_idx == 0: + return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None + + elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes + return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value) + else: + return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr) + else: + if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes + return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value) + else: + return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr) + + +def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value): + """ " + Returns monthly depreciation amount when year changes + 1. Calculate per day depr based on new year + 2. Calculate monthly amount based on new per day amount + """ + from_date, days_in_month = _get_total_days( + fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation) + ) + per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date) + return (per_day_depr * days_in_month), per_day_depr + + +def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr): + """ " + Returns monthly depreciation amount based on prev per day depr + Calculate per day depr only for the first month + """ + from_date, days_in_month = _get_total_days( + fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation) + ) + return (prev_per_day_depr * days_in_month), prev_per_day_depr + + +def get_per_day_depr( + fb_row, + depreciable_value, + from_date, +): + to_date = add_days(add_years(from_date, 1), -1) + total_days = date_diff(to_date, from_date) + 1 + per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days + return per_day_depr + + +def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation): + from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation) + to_date = add_months(from_date, frequency_of_depreciation) + if is_last_day_of_the_month(depreciation_start_date): + to_date = get_last_day(to_date) + from_date = add_days(get_last_day(from_date), 1) + return from_date, date_diff(to_date, from_date) + 1 + + def make_draft_asset_depr_schedules_if_not_present(asset_doc): asset_depr_schedules_names = [] diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index c55063f2ebf..6e4966ac6cf 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -3,10 +3,12 @@ import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils import cstr from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, + get_depr_schedule, ) @@ -25,3 +27,136 @@ class TestAssetDepreciationSchedule(FrappeTestCase): ) self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert) + + def test_daily_prorata_based_depr_on_sl_methond(self): + asset = create_asset( + calculate_depreciation=1, + depreciation_method="Straight Line", + daily_prorata_based=1, + available_for_use_date="2020-01-01", + depreciation_start_date="2020-01-31", + frequency_of_depreciation=1, + total_number_of_depreciations=24, + ) + + expected_schedules = [ + ["2020-01-31", 4234.97, 4234.97], + ["2020-02-29", 3961.75, 8196.72], + ["2020-03-31", 4234.97, 12431.69], + ["2020-04-30", 4098.36, 16530.05], + ["2020-05-31", 4234.97, 20765.02], + ["2020-06-30", 4098.36, 24863.38], + ["2020-07-31", 4234.97, 29098.35], + ["2020-08-31", 4234.97, 33333.32], + ["2020-09-30", 4098.36, 37431.68], + ["2020-10-31", 4234.97, 41666.65], + ["2020-11-30", 4098.36, 45765.01], + ["2020-12-31", 4234.97, 49999.98], + ["2021-01-31", 4246.58, 54246.56], + ["2021-02-28", 3835.62, 58082.18], + ["2021-03-31", 4246.58, 62328.76], + ["2021-04-30", 4109.59, 66438.35], + ["2021-05-31", 4246.58, 70684.93], + ["2021-06-30", 4109.59, 74794.52], + ["2021-07-31", 4246.58, 79041.1], + ["2021-08-31", 4246.58, 83287.68], + ["2021-09-30", 4109.59, 87397.27], + ["2021-10-31", 4246.58, 91643.85], + ["2021-11-30", 4109.59, 95753.44], + ["2021-12-31", 4246.56, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Test for Written Down Value Method + # Frequency of deprciation = 3 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_3_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2021-02-20", + depreciation_start_date="2021-03-31", + frequency_of_depreciation=3, + total_number_of_depreciations=6, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2021-03-31", 4383.56, 4383.56], + ["2021-06-30", 9535.45, 13919.01], + ["2021-09-30", 9640.23, 23559.24], + ["2021-12-31", 9640.23, 33199.47], + ["2022-03-31", 9430.66, 42630.13], + ["2022-06-30", 5721.27, 48351.4], + ["2022-08-20", 51648.6, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Frequency of deprciation = 6 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_6_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2020-02-20", + depreciation_start_date="2020-02-29", + frequency_of_depreciation=6, + total_number_of_depreciations=6, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2020-02-29", 1092.90, 1092.90], + ["2020-08-31", 19944.01, 21036.91], + ["2021-02-28", 19618.83, 40655.74], + ["2021-08-31", 11966.4, 52622.14], + ["2022-02-28", 11771.3, 64393.44], + ["2022-08-31", 7179.84, 71573.28], + ["2023-02-20", 28426.72, 100000.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) + + # Frequency of deprciation = 12 + def test_for_daily_prorata_based_depreciation_wdv_method_frequency_12_months(self): + asset = create_asset( + item_code="Macbook Pro", + calculate_depreciation=1, + depreciation_method="Written Down Value", + daily_prorata_based=1, + available_for_use_date="2020-02-20", + depreciation_start_date="2020-03-31", + frequency_of_depreciation=12, + total_number_of_depreciations=4, + rate_of_depreciation=40, + ) + + expected_schedules = [ + ["2020-03-31", 4480.87, 4480.87], + ["2021-03-31", 38207.65, 42688.52], + ["2022-03-31", 22924.59, 65613.11], + ["2023-03-31", 13754.76, 79367.87], + ["2024-02-20", 20632.13, 100000], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedules) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 3dae0442cce..79ba6a721e3 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -406,7 +406,7 @@ { "fieldname": "contact_and_address_tab", "fieldtype": "Tab Break", - "label": "Contact & Address" + "label": "Address & Contact" }, { "fieldname": "accounting_tab", @@ -485,7 +485,7 @@ "link_fieldname": "party" } ], - "modified": "2024-03-13 11:14:06.516519", + "modified": "2024-05-08 18:02:57.342931", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 58bcc8c5fbe..14bf8ad7436 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -787,7 +787,7 @@ class BuyingController(SubcontractingController): "supplier": self.supplier, "purchase_date": self.posting_date, "calculate_depreciation": 0, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, "gross_purchase_amount": purchase_amount, "asset_quantity": asset_quantity, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0465f2ca53e..f2868b90d5c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -363,3 +363,4 @@ erpnext.patches.v14_0.set_maintain_stock_for_bom_item erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset +erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount diff --git a/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py b/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py new file mode 100644 index 00000000000..8af3ed26323 --- /dev/null +++ b/erpnext/patches/v15_0/rename_purchase_receipt_amount_to_purchase_amount.py @@ -0,0 +1,8 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + frappe.reload_doc("assets", "doctype", "asset") + if frappe.db.has_column("Asset", "purchase_receipt_amount"): + rename_field("Asset", "purchase_receipt_amount", "purchase_amount") diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 41c6311553c..6ed42d3b4de 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -482,7 +482,7 @@ { "fieldname": "contact_and_address_tab", "fieldtype": "Tab Break", - "label": "Contact & Address" + "label": "Address & Contact" }, { "fieldname": "defaults_tab", @@ -583,7 +583,7 @@ "link_fieldname": "party" } ], - "modified": "2024-03-16 19:41:47.971815", + "modified": "2024-05-08 18:03:20.716169", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 95cbfd0f32b..3044d865c0c 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -71,6 +71,8 @@ frappe.ui.form.on("Quotation", { frm.trigger("set_label"); frm.trigger("toggle_reqd_lead_customer"); frm.trigger("set_dynamic_field_label"); + frm.set_value("party_name", ""); + frm.set_value("customer_name", ""); }, set_label: function (frm) { @@ -97,7 +99,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. frappe.dynamic_link = { doc: this.frm.doc, fieldname: "party_name", - doctype: doc.quotation_to == "Customer" ? "Customer" : "Lead", + doctype: doc.quotation_to, }; var me = this; @@ -197,6 +199,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. }; } else if (this.frm.doc.quotation_to == "Prospect") { this.frm.set_df_property("party_name", "label", "Prospect"); + this.frm.fields_dict.party_name.get_query = null; } } diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index 77eae534d17..4f8649c0bfa 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -51,7 +51,6 @@ frappe.ui.form.on("Delivery Trip", { frm.add_custom_button( __("Delivery Note"), () => { - frm.clear_table("delivery_stops"); erpnext.utils.map_current_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip", source_doctype: "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2e751ad5251..5f898b94268 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -858,7 +858,7 @@ class PurchaseReceipt(BuyingController): asset.name, { "gross_purchase_amount": purchase_amount, - "purchase_receipt_amount": purchase_amount, + "purchase_amount": purchase_amount, }, ) @@ -1163,7 +1163,12 @@ def make_purchase_invoice(source_name, target_doc=None, args=None): qty = item_row.qty if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): qty = item_row.received_qty + pending_qty = qty - invoiced_qty_map.get(item_row.name, 0) + + if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"): + return pending_qty, 0 + returned_qty = flt(returned_qty_map.get(item_row.name, 0)) if returned_qty: if returned_qty >= pending_qty: @@ -1172,6 +1177,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None): else: pending_qty -= returned_qty returned_qty = 0 + return pending_qty, returned_qty doclist = get_mapped_doc( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 54a695126c7..ede2dc00714 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -895,6 +895,8 @@ class TestPurchaseReceipt(FrappeTestCase): create_purchase_order, ) + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) + po = create_purchase_order() pr = create_pr_against_po(po.name) @@ -914,6 +916,7 @@ class TestPurchaseReceipt(FrappeTestCase): po.cancel() def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self): + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) pr1 = make_purchase_receipt(qty=8, do_not_submit=True) pr1.append( "items", @@ -2783,6 +2786,84 @@ class TestPurchaseReceipt(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self): + item_code = make_item( + "_Test Purchase Receipt Bill For Rejected Quantity", + properties={"is_stock_item": 1}, + ).name + + pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100) + + return_pr = make_purchase_receipt( + item_code=item_code, + is_return=1, + return_against=pr.name, + qty=-2, + do_not_submit=1, + ) + return_pr.items[0].purchase_receipt_item = pr.items[0].name + return_pr.submit() + old_value = frappe.db.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) + pi = make_purchase_invoice(pr.name) + self.assertEqual(pi.items[0].qty, 3) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1) + pi = make_purchase_invoice(pr.name) + pi.submit() + self.assertEqual(pi.items[0].qty, 5) + + frappe.db.set_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value + ) + + def test_zero_valuation_rate_for_batched_item(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = make_item( + "_Test Zero Valuation Rate For the Batch Item", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pi = make_purchase_receipt( + qty=10, + rate=0, + item_code=item.name, + ) + + pi.reload() + batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle) + + se = make_stock_entry( + purpose="Material Issue", + item_code=item.name, + source=pi.items[0].warehouse, + qty=10, + batch_no=batch_no, + use_serial_batch_fields=0, + ) + + se.submit() + + se.reload() + + self.assertEqual(se.items[0].valuation_rate, 0) + self.assertEqual(se.items[0].basic_rate, 0) + + sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle) + for row in sabb_doc.entries: + self.assertEqual(row.incoming_rate, 0) + 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/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 67f946eb7c8..a1a8a85e745 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -249,8 +249,7 @@ class SerialandBatchBundle(Document): if self.has_serial_no: d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0)) else: - if sn_obj.batch_avg_rate.get(d.batch_no): - d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no)) + d.incoming_rate = abs(flt(sn_obj.batch_avg_rate.get(d.batch_no))) available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty")) if self.docstatus == 1: diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index d972d595e88..f03cc49ef73 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -418,7 +418,7 @@ { "default": "0", "depends_on": "eval: doc.enable_stock_reservation", - "description": "Stock will be reserved on submission of Purchase Receipt created against Material Receipt for Sales Order.", + "description": "Stock will be reserved on submission of Purchase Receipt created against Material Request for Sales Order.", "fieldname": "auto_reserve_stock_for_sales_order_on_purchase", "fieldtype": "Check", "label": "Auto Reserve Stock for Sales Order on Purchase" @@ -469,4 +469,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py index 7bd8d704fda..15d9a12bc65 100644 --- a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py +++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.py @@ -220,7 +220,7 @@ def get_serial_nos(doctype, txt, searchfield, start, page_len, filters): def get_batch_nos(doctype, txt, searchfield, start, page_len, filters): query_filters = {} - if txt: + if filters.get("voucher_no") and txt: query_filters["batch_no"] = ["like", f"%{txt}%"] if filters.get("voucher_no"): @@ -239,5 +239,8 @@ def get_batch_nos(doctype, txt, searchfield, start, page_len, filters): ) else: + if txt: + query_filters["name"] = ["like", f"%{txt}%"] + query_filters["item"] = filters.get("item_code") return frappe.get_all("Batch", filters=query_filters, as_list=True)