Merge pull request #41459 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
Deepesh Garg
2024-05-15 10:41:19 +05:30
committed by GitHub
28 changed files with 432 additions and 149 deletions

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -340,10 +340,11 @@
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">30 Days</th>
<th style="width: 25%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 25%">120 Days</th>
<th style="width: 25%">0 - 30 Days</th>
<th style="width: 25%">30 - 60 Days</th>
<th style="width: 25%">60 - 90 Days</th>
<th style="width: 25%">90 - 120 Days</th>
<th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
@@ -352,6 +353,7 @@
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>

View File

@@ -1182,7 +1182,7 @@ class PurchaseInvoice(BuyingController):
asset.name,
{
"gross_purchase_amount": purchase_amount,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)

View File

@@ -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",

View File

@@ -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(

View File

@@ -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")

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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 <b>equal</b> 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

View File

@@ -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,
},
)

View File

@@ -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()

View File

@@ -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"

View File

@@ -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 = []

View File

@@ -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)

View File

@@ -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",

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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",

View File

@@ -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;
}
}

View File

@@ -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",

View File

@@ -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(

View File

@@ -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

View File

@@ -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:

View File

@@ -418,7 +418,7 @@
{
"default": "0",
"depends_on": "eval: doc.enable_stock_reservation",
"description": "Stock will be reserved on submission of <b>Purchase Receipt</b> created against Material Receipt for Sales Order.",
"description": "Stock will be reserved on submission of <b>Purchase Receipt</b> 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
}
}

View File

@@ -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)