diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index be692db5665..9982b4f9d88 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -106,7 +106,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"
@@ -469,7 +469,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2024-05-11 23:19:44.673975",
+ "modified": "2024-03-15 12:11:36.085158",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -498,4 +498,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 @@
- | 30 Days |
- 60 Days |
- 90 Days |
- 120 Days |
+ 0 - 30 Days |
+ 30 - 60 Days |
+ 60 - 90 Days |
+ 90 - 120 Days |
+ Above 120 Days |
@@ -352,6 +353,7 @@
{{ 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/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/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/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 49252bd5b54..63a8c842c9a 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -205,6 +205,7 @@ class StockController(AccountsController):
"company": self.company,
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
"use_serial_batch_fields": row.use_serial_batch_fields,
+ "via_landed_cost_voucher": via_landed_cost_voucher,
"do_not_submit": True if not via_landed_cost_voucher else False,
}
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 2aa31be0f0e..97c85502c98 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -21,7 +21,8 @@ def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all(
"BOM Item",
filters={"parent": bom},
- fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
+ fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom", "idx"],
+ order_by="idx ASC",
)
for item in exploded_items:
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js
index 23fa9ab41b0..4a34d126f88 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js
@@ -93,4 +93,11 @@ frappe.query_reports["Exponential Smoothing Forecasting"] = {
},
},
],
+ formatter: function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (column.fieldname === "item_code" && value.includes("Total Quantity")) {
+ value = "" + value + "";
+ }
+ return value;
+ },
};
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 85648d6b326..0f5fa959dc5 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -144,7 +144,7 @@ class ForecastingReport(ExponentialSmoothingForecast):
if not self.data:
return
- total_row = {"item_code": _(frappe.bold("Total Quantity"))}
+ total_row = {"item_code": _("Total Quantity")}
for value in self.data:
for period in self.period_list:
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/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index 3719c96c6e7..4ed428421ca 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -47,9 +47,14 @@ frappe.ui.form.on("Batch", {
},
make_dashboard: (frm) => {
if (!frm.is_new()) {
+ let for_stock_levels = 0;
+ if (!frm.doc.batch_qty && frm.doc.expiry_date) {
+ for_stock_levels = 1;
+ }
+
frappe.call({
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
- args: { batch_no: frm.doc.name, item_code: frm.doc.item },
+ args: { batch_no: frm.doc.name, item_code: frm.doc.item, for_stock_levels: for_stock_levels },
callback: (r) => {
if (!r.message) {
return;
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 8726642cb43..77b87aa995c 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -199,6 +199,7 @@ def get_batch_qty(
posting_date=None,
posting_time=None,
ignore_voucher_nos=None,
+ for_stock_levels=False,
):
"""Returns batch actual qty if warehouse is passed,
or returns dict of qty by warehouse if warehouse is None
@@ -222,6 +223,7 @@ def get_batch_qty(
"posting_time": posting_time,
"batch_no": batch_no,
"ignore_voucher_nos": ignore_voucher_nos,
+ "for_stock_levels": for_stock_levels,
}
)
diff --git a/erpnext/stock/doctype/batch/batch_list.js b/erpnext/stock/doctype/batch/batch_list.js
index 2060d6e8763..644ef131399 100644
--- a/erpnext/stock/doctype/batch/batch_list.js
+++ b/erpnext/stock/doctype/batch/batch_list.js
@@ -3,8 +3,6 @@ frappe.listview_settings["Batch"] = {
get_indicator: (doc) => {
if (doc.disabled) {
return [__("Disabled"), "gray", "disabled,=,1"];
- } else if (!doc.batch_qty) {
- return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"];
} else if (
doc.expiry_date &&
frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0
@@ -14,6 +12,8 @@ frappe.listview_settings["Batch"] = {
"red",
"expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0",
];
+ } else if (!doc.batch_qty) {
+ return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"];
} else {
return [__("Active"), "green", "batch_qty,>,0|disabled,=,0"];
}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 1c43233d7c2..1ceb949d691 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -5,7 +5,7 @@ import copy
import json
import frappe
-from frappe import _
+from frappe import _, bold
from frappe.model.document import Document
from frappe.query_builder import Interval
from frappe.query_builder.functions import Count, CurDate, UnixTimestamp
@@ -469,6 +469,13 @@ class Item(Document):
def validate_warehouse_for_reorder(self):
"""Validate Reorder level table for duplicate and conditional mandatory"""
warehouse_material_request_type: list[tuple[str, str]] = []
+
+ _warehouse_before_save = frappe._dict()
+ if not self.is_new() and self._doc_before_save:
+ _warehouse_before_save = {
+ d.name: d.warehouse for d in self._doc_before_save.get("reorder_levels") or []
+ }
+
for d in self.get("reorder_levels"):
if not d.warehouse_group:
d.warehouse_group = d.warehouse
@@ -485,6 +492,19 @@ class Item(Document):
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
+ if d.warehouse_group and d.warehouse:
+ if _warehouse_before_save.get(d.name) == d.warehouse:
+ continue
+
+ child_warehouses = get_child_warehouses(d.warehouse_group)
+ if d.warehouse not in child_warehouses:
+ frappe.throw(
+ _(
+ "Row #{0}: The warehouse {1} is not a child warehouse of a group warehouse {2}"
+ ).format(d.idx, bold(d.warehouse), bold(d.warehouse_group)),
+ title=_("Incorrect Check in (group) Warehouse for Reorder"),
+ )
+
def stock_ledger_created(self):
if not hasattr(self, "_stock_ledger_created"):
self._stock_ledger_created = len(
@@ -1360,3 +1380,10 @@ def get_asset_naming_series():
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
return get_asset_naming_series()
+
+
+@frappe.request_cache
+def get_child_warehouses(warehouse):
+ from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+ return get_child_warehouses(warehouse)
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 2b3d3b72a02..d5f13e62a5c 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -862,6 +862,27 @@ class TestItem(FrappeTestCase):
self.assertEqual(data[0].description, item.description)
self.assertTrue("description" in data[0])
+ def test_group_warehouse_for_reorder_item(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+ item_doc = make_item("_Test Group Warehouse For Reorder Item", {"is_stock_item": 1})
+ warehouse = create_warehouse("_Test Warehouse - _TC")
+ warehouse_doc = frappe.get_doc("Warehouse", warehouse)
+ warehouse_doc.db_set("parent_warehouse", "")
+
+ item_doc.append(
+ "reorder_levels",
+ {
+ "warehouse": warehouse,
+ "warehouse_reorder_level": 10,
+ "warehouse_reorder_qty": 100,
+ "material_request_type": "Purchase",
+ "warehouse_group": "_Test Warehouse Group - _TC",
+ },
+ )
+
+ self.assertRaises(frappe.ValidationError, item_doc.save)
+
def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings")
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 13b7f97b7c4..39f9ecb915d 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -946,6 +946,128 @@ class TestLandedCostVoucher(FrappeTestCase):
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
)
+ def test_do_not_validate_against_landed_cost_voucher_for_serial_for_legacy_pr(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos
+
+ frappe.flags.ignore_serial_batch_bundle_validation = True
+ frappe.flags.use_serial_and_batch_fields = True
+ sn_item = "Test Don't Validate Against LCV For Serial NO for Legacy PR"
+ sn_item_doc = make_item(
+ sn_item,
+ {
+ "has_serial_no": 1,
+ "serial_no_series": "SN-ALCVTDVLCVSNO-.####",
+ "is_stock_item": 1,
+ },
+ )
+
+ serial_nos = [
+ "SN-ALCVTDVLCVSNO-0001",
+ "SN-ALCVTDVLCVSNO-0002",
+ "SN-ALCVTDVLCVSNO-0003",
+ "SN-ALCVTDVLCVSNO-0004",
+ "SN-ALCVTDVLCVSNO-0005",
+ ]
+
+ for sn in serial_nos:
+ if not frappe.db.exists("Serial No", sn):
+ sn_doc = frappe.get_doc(
+ {
+ "doctype": "Serial No",
+ "item_code": sn_item,
+ "serial_no": sn,
+ }
+ )
+ sn_doc.insert()
+
+ warehouse = "_Test Warehouse - _TC"
+ company = frappe.db.get_value("Warehouse", warehouse, "company")
+
+ pr = make_purchase_receipt(
+ company=company,
+ warehouse=warehouse,
+ item_code=sn_item,
+ qty=5,
+ rate=100,
+ uom=sn_item_doc.stock_uom,
+ stock_uom=sn_item_doc.stock_uom,
+ )
+
+ pr.reload()
+
+ for sn in serial_nos:
+ sn_doc = frappe.get_doc("Serial No", sn)
+ sn_doc.db_set(
+ {
+ "warehouse": warehouse,
+ "status": "Active",
+ }
+ )
+
+ for row in pr.items:
+ if row.item_code == sn_item:
+ row.db_set("serial_no", ", ".join(serial_nos))
+
+ stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": pr.name})
+ for sle in stock_ledger_entries:
+ doc = frappe.get_doc("Stock Ledger Entry", sle.name)
+ if doc.item_code == sn_item:
+ doc.db_set("serial_no", ", ".join(serial_nos))
+
+ dn = create_delivery_note(
+ company=company,
+ warehouse=warehouse,
+ item_code=sn_item,
+ qty=5,
+ rate=100,
+ uom=sn_item_doc.stock_uom,
+ stock_uom=sn_item_doc.stock_uom,
+ )
+
+ stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": dn.name})
+ for sle in stock_ledger_entries:
+ doc = frappe.get_doc("Stock Ledger Entry", sle.name)
+ if doc.item_code == sn_item:
+ doc.db_set("serial_no", ", ".join(serial_nos))
+
+ frappe.flags.ignore_serial_batch_bundle_validation = False
+ frappe.flags.use_serial_and_batch_fields = False
+
+ lcv = make_landed_cost_voucher(
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
+ receipt_document=pr.name,
+ charges=20,
+ distribute_charges_based_on="Qty",
+ do_not_save=True,
+ )
+
+ lcv.get_items_from_purchase_receipts()
+ lcv.save()
+ lcv.submit()
+
+ pr.reload()
+
+ for row in pr.items:
+ self.assertEqual(row.valuation_rate, 104)
+ self.assertTrue(row.serial_and_batch_bundle)
+ self.assertEqual(
+ row.valuation_rate,
+ frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
+ )
+
+ lcv.cancel()
+ pr.reload()
+
+ for row in pr.items:
+ self.assertEqual(row.valuation_rate, 100)
+ self.assertTrue(row.serial_and_batch_bundle)
+ self.assertEqual(
+ row.valuation_rate,
+ frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"),
+ )
+
def make_landed_cost_voucher(**args):
args = frappe._dict(args)
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..5e16115db01 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:
@@ -429,6 +428,9 @@ class SerialandBatchBundle(Document):
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} should be submit first.")
def check_future_entries_exists(self):
+ if self.flags and self.flags.via_landed_cost_voucher:
+ return
+
if not self.has_serial_no:
return
@@ -1863,14 +1865,14 @@ def get_available_batches(kwargs):
batch_ledger.warehouse,
Sum(batch_ledger.qty).as_("qty"),
)
- .where(
- (batch_table.disabled == 0)
- & ((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
- )
+ .where(batch_table.disabled == 0)
.where(stock_ledger_entry.is_cancelled == 0)
.groupby(batch_ledger.batch_no, batch_ledger.warehouse)
)
+ if not kwargs.get("for_stock_levels"):
+ query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
+
if kwargs.get("posting_date"):
if kwargs.get("posting_time") is None:
kwargs.posting_time = nowtime()
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)
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 573d7280ca1..21a96c41cb4 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -840,6 +840,9 @@ class SerialBatchCreation:
self.set_auto_serial_batch_entries_for_inward()
self.add_serial_nos_for_batch_item()
+ if hasattr(self, "via_landed_cost_voucher") and self.via_landed_cost_voucher:
+ doc.flags.via_landed_cost_voucher = self.via_landed_cost_voucher
+
self.set_serial_batch_entries(doc)
if not doc.get("entries"):
return frappe._dict({})
diff --git a/erpnext/templates/print_formats/includes/total.html b/erpnext/templates/print_formats/includes/total.html
index 879203bbf25..f964047bd08 100644
--- a/erpnext/templates/print_formats/includes/total.html
+++ b/erpnext/templates/print_formats/includes/total.html
@@ -1,14 +1,14 @@
-
+
{% if doc.flags.show_inclusive_tax_in_print %}
-
+
{{ doc.get_formatted("net_total", doc) }}
{% else %}
-
+
{{ doc.get_formatted("total", doc) }}
{% endif %}