Browse Source

Merge branch 'develop' of https://github.com/frappe/erpnext into subscription_active_fixes

develop
hrwx 3 years ago
parent
commit
2bce193b81
  1. 23
      .github/workflows/patch.yml
  2. 3
      erpnext/accounts/doctype/gl_entry/gl_entry.py
  3. 6
      erpnext/accounts/doctype/journal_entry/journal_entry.py
  4. 44
      erpnext/accounts/doctype/payment_entry/payment_entry.py
  5. 29
      erpnext/accounts/doctype/sales_invoice/sales_invoice.py
  6. 59
      erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
  7. 5
      erpnext/accounts/doctype/subscription/subscription.py
  8. 55
      erpnext/assets/doctype/asset/asset.py
  9. 1066
      erpnext/assets/doctype/asset/test_asset.py
  10. 5
      erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
  11. 2
      erpnext/regional/india/utils.py
  12. 6
      erpnext/stock/doctype/stock_entry/stock_entry.js

23
.github/workflows/patch.yml

@ -86,4 +86,27 @@ jobs:
cd ~/frappe-bench/ cd ~/frappe-bench/
wget https://erpnext.com/files/v10-erpnext.sql.gz wget https://erpnext.com/files/v10-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
for version in $(seq 12 13)
do
echo "Updating to v$version"
branch_name="version-$version"
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
git -C "apps/frappe" checkout -q -f $branch_name
git -C "apps/erpnext" checkout -q -f $branch_name
bench setup requirements --python
bench --site test_site migrate
done
echo "Updating to latest version"
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
bench --site test_site migrate bench --site test_site migrate

3
erpnext/accounts/doctype/gl_entry/gl_entry.py

@ -58,7 +58,8 @@ class GLEntry(Document):
# Update outstanding amt on against voucher # Update outstanding amt on against voucher
if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees']
and self.against_voucher and self.flags.update_outstanding == 'Yes'): and self.against_voucher and self.flags.update_outstanding == 'Yes'
and not frappe.flags.is_reverse_depr_entry):
update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type,
self.against_voucher) self.against_voucher)

6
erpnext/accounts/doctype/journal_entry/journal_entry.py

@ -58,7 +58,10 @@ class JournalEntry(AccountsController):
if not frappe.flags.in_import: if not frappe.flags.in_import:
self.validate_total_debit_and_credit() self.validate_total_debit_and_credit()
self.validate_against_jv() if not frappe.flags.is_reverse_depr_entry:
self.validate_against_jv()
self.validate_stock_accounts()
self.validate_reference_doc() self.validate_reference_doc()
if self.docstatus == 0: if self.docstatus == 0:
self.set_against_account() self.set_against_account()
@ -69,7 +72,6 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table() self.validate_empty_accounts_table()
self.set_account_and_party_balance() self.set_account_and_party_balance()
self.validate_inter_company_accounts() self.validate_inter_company_accounts()
self.validate_stock_accounts()
if self.docstatus == 0: if self.docstatus == 0:
self.apply_tax_withholding() self.apply_tax_withholding()

44
erpnext/accounts/doctype/payment_entry/payment_entry.py

@ -389,7 +389,7 @@ class PaymentEntry(AccountsController):
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
for key, allocated_amount in iteritems(invoice_payment_amount_map): for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1):
if not invoice_paid_amount_map.get(key): if not invoice_paid_amount_map.get(key):
frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1])) frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1]))
@ -407,7 +407,7 @@ class PaymentEntry(AccountsController):
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
else: else:
if allocated_amount > outstanding: if allocated_amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0]))
if allocated_amount and outstanding: if allocated_amount and outstanding:
frappe.db.sql(""" frappe.db.sql("""
@ -1053,12 +1053,6 @@ def get_outstanding_reference_documents(args):
party_account_currency = get_account_currency(args.get("party_account")) party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency")
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"),
args.get("party_account"), args.get("company"), party_account_currency, company_currency)
# Get positive outstanding sales /purchase invoices/ Fees # Get positive outstanding sales /purchase invoices/ Fees
condition = "" condition = ""
if args.get("voucher_type") and args.get("voucher_no"): if args.get("voucher_type") and args.get("voucher_no"):
@ -1105,6 +1099,12 @@ def get_outstanding_reference_documents(args):
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args) args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args)
# Get negative outstanding sales /purchase invoices
negative_outstanding_invoices = []
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"),
args.get("party_account"), party_account_currency, company_currency, condition=condition)
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data: if not data:
@ -1137,22 +1137,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
'invoice_amount': flt(d.invoice_amount), 'invoice_amount': flt(d.invoice_amount),
'outstanding_amount': flt(d.outstanding_amount), 'outstanding_amount': flt(d.outstanding_amount),
'payment_amount': payment_term.payment_amount, 'payment_amount': payment_term.payment_amount,
'payment_term': payment_term.payment_term, 'payment_term': payment_term.payment_term
'allocated_amount': payment_term.outstanding
})) }))
outstanding_invoices_after_split = []
if invoice_ref_based_on_payment_terms: if invoice_ref_based_on_payment_terms:
for idx, ref in invoice_ref_based_on_payment_terms.items(): for idx, ref in invoice_ref_based_on_payment_terms.items():
voucher_no = outstanding_invoices[idx]['voucher_no'] voucher_no = ref[0]['voucher_no']
voucher_type = outstanding_invoices[idx]['voucher_type'] voucher_type = ref[0]['voucher_type']
frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format(
voucher_type, voucher_no, len(ref)), alert=True) voucher_type, voucher_no, len(ref)), alert=True)
outstanding_invoices.pop(idx - 1) outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices))
index = outstanding_invoices.index(existing_row[0])
outstanding_invoices.pop(index)
return outstanding_invoices outstanding_invoices_after_split += outstanding_invoices
return outstanding_invoices_after_split
def get_orders_to_be_billed(posting_date, party_type, party, def get_orders_to_be_billed(posting_date, party_type, party,
company, party_account_currency, company_currency, cost_center=None, filters=None): company, party_account_currency, company_currency, cost_center=None, filters=None):
@ -1219,7 +1223,7 @@ def get_orders_to_be_billed(posting_date, party_type, party,
return order_list return order_list
def get_negative_outstanding_invoices(party_type, party, party_account, def get_negative_outstanding_invoices(party_type, party, party_account,
company, party_account_currency, company_currency, cost_center=None): party_account_currency, company_currency, cost_center=None, condition=None):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = "" supplier_condition = ""
if voucher_type == "Purchase Invoice": if voucher_type == "Purchase Invoice":
@ -1241,19 +1245,21 @@ def get_negative_outstanding_invoices(party_type, party, party_account,
`tab{voucher_type}` `tab{voucher_type}`
where where
{party_type} = %s and {party_account} = %s and docstatus = 1 and {party_type} = %s and {party_account} = %s and docstatus = 1 and
company = %s and outstanding_amount < 0 outstanding_amount < 0
{supplier_condition} {supplier_condition}
{condition}
order by order by
posting_date, name posting_date, name
""".format(**{ """.format(**{
"supplier_condition": supplier_condition, "supplier_condition": supplier_condition,
"condition": condition,
"rounded_total_field": rounded_total_field, "rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field, "grand_total_field": grand_total_field,
"voucher_type": voucher_type, "voucher_type": voucher_type,
"party_type": scrub(party_type), "party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to", "party_account": "debit_to" if party_type == "Customer" else "credit_to",
"cost_center": cost_center "cost_center": cost_center
}), (party, party_account, company), as_dict=True) }), (party, party_account), as_dict=True)
@frappe.whitelist() @frappe.whitelist()

29
erpnext/accounts/doctype/sales_invoice/sales_invoice.py

@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import (
get_disposal_account_and_cost_center, get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain, get_gl_entries_on_asset_regain,
post_depreciation_entries, make_depreciation_entry,
) )
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
@ -934,6 +934,7 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
if asset.calculate_depreciation: if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_sale(asset)
self.reset_depreciation_schedule(asset) self.reset_depreciation_schedule(asset)
else: else:
@ -997,22 +998,20 @@ class SalesInvoice(SellingController):
def depreciate_asset(self, asset): def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(self.posting_date) asset.prepare_depreciation_data(date_of_sale=self.posting_date)
asset.save() asset.save()
post_depreciation_entries(self.posting_date) make_depreciation_entry(asset.name, self.posting_date)
def reset_depreciation_schedule(self, asset): def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset # recreate original depreciation schedule of the asset
asset.prepare_depreciation_data() asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset) self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save() asset.save()
self.delete_depreciation_entry_made_after_sale(asset)
def modify_depreciation_schedule_for_asset_repairs(self, asset): def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all( asset_repairs = frappe.get_all(
'Asset Repair', 'Asset Repair',
@ -1026,7 +1025,7 @@ class SalesInvoice(SellingController):
asset_repair.modify_depreciation_schedule() asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data() asset.prepare_depreciation_data()
def delete_depreciation_entry_made_after_sale(self, asset): def reverse_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice() posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
@ -1041,11 +1040,19 @@ class SalesInvoice(SellingController):
row += 1 row += 1
if schedule.schedule_date == posting_date_of_original_invoice: if schedule.schedule_date == posting_date_of_original_invoice:
if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice): if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \
or self.sale_happens_in_the_future(posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate() reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit() reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
asset.save()
def get_posting_date_of_sales_invoice(self): def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date') return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date')
@ -1060,6 +1067,12 @@ class SalesInvoice(SellingController):
return True return True
return False return False
def sale_happens_in_the_future(self, posting_date_of_original_invoice):
if posting_date_of_original_invoice > getdate():
return True
return False
@property @property
def enable_discount_accounting(self): def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"): if not hasattr(self, "_enable_discount_accounting"):

59
erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py

@ -2237,9 +2237,9 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0) enable_discount_accounting(enable=0)
def test_asset_depreciation_on_sale(self): def test_asset_depreciation_on_sale_with_pro_rata(self):
""" """
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30. Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
""" """
create_asset_data() create_asset_data()
@ -2252,7 +2252,7 @@ class TestSalesInvoice(unittest.TestCase):
expected_values = [ expected_values = [
["2020-06-30", 1311.48, 1311.48], ["2020-06-30", 1311.48, 1311.48],
["2021-06-30", 20000.0, 21311.48], ["2021-06-30", 20000.0, 21311.48],
["2021-09-30", 3966.76, 25278.24] ["2021-09-30", 5041.1, 26352.58]
] ]
for i, schedule in enumerate(asset.schedules): for i, schedule in enumerate(asset.schedules):
@ -2261,6 +2261,59 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry) self.assertTrue(schedule.journal_entry)
def test_asset_depreciation_on_sale_without_pro_rata(self):
"""
Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale.
"""
create_asset_data()
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1,
available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3,
expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1)
post_depreciation_entries(getdate("2021-09-30"))
create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-12-31"))
asset.load_from_db()
expected_values = [
["2020-12-31", 30000, 30000],
["2021-12-31", 30000, 60000]
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
def test_depreciation_on_return_of_sold_asset(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
create_asset_data()
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1)
post_depreciation_entries(getdate("2021-09-30"))
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30"))
return_si = make_return_doc("Sales Invoice", si.name)
return_si.submit()
asset.load_from_db()
expected_values = [
["2020-06-30", 1311.48, 1311.48, True],
["2021-06-30", 20000.0, 21311.48, True],
["2022-06-30", 20000.0, 41311.48, False],
["2023-06-30", 20000.0, 61311.48, False],
["2024-06-30", 20000.0, 81311.48, False],
["2025-06-06", 18688.52, 100000.0, False]
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertEqual(schedule.journal_entry, schedule.journal_entry)
def test_sales_invoice_against_supplier(self): def test_sales_invoice_against_supplier(self):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer, make_customer,

5
erpnext/accounts/doctype/subscription/subscription.py

@ -563,6 +563,9 @@ class Subscription(Document):
else: else:
self.set_status_grace_period() self.set_status_grace_period()
if getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
# Generate invoices periodically even if current invoice are unpaid # Generate invoices periodically even if current invoice are unpaid
if self.generate_new_invoices_past_due_date and not \ if self.generate_new_invoices_past_due_date and not \
self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \
@ -571,8 +574,6 @@ class Subscription(Document):
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.generate_invoice(prorate) self.generate_invoice(prorate)
if getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
@staticmethod @staticmethod
def is_paid(invoice): def is_paid(invoice):

55
erpnext/assets/doctype/asset/asset.py

@ -75,12 +75,12 @@ class Asset(AccountsController):
if self.is_existing_asset and self.purchase_invoice: if self.is_existing_asset and self.purchase_invoice:
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
def prepare_depreciation_data(self, date_of_sale=None): def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
if self.calculate_depreciation: if self.calculate_depreciation:
self.value_after_depreciation = 0 self.value_after_depreciation = 0
self.set_depreciation_rate() self.set_depreciation_rate()
self.make_depreciation_schedule(date_of_sale) self.make_depreciation_schedule(date_of_sale)
self.set_accumulated_depreciation(date_of_sale) self.set_accumulated_depreciation(date_of_sale, date_of_return)
else: else:
self.finance_books = [] self.finance_books = []
self.value_after_depreciation = (flt(self.gross_purchase_amount) - self.value_after_depreciation = (flt(self.gross_purchase_amount) -
@ -182,7 +182,7 @@ class Asset(AccountsController):
d.precision("rate_of_depreciation")) d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self, date_of_sale): def make_depreciation_schedule(self, date_of_sale):
if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules: if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.get('schedules'):
self.schedules = [] self.schedules = []
if not self.available_for_use_date: if not self.available_for_use_date:
@ -232,13 +232,15 @@ class Asset(AccountsController):
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
from_date, date_of_sale) from_date, date_of_sale)
self.append("schedules", { if depreciation_amount > 0:
"schedule_date": date_of_sale, self.append("schedules", {
"depreciation_amount": depreciation_amount, "schedule_date": date_of_sale,
"depreciation_method": d.depreciation_method, "depreciation_amount": depreciation_amount,
"finance_book": d.finance_book, "depreciation_method": d.depreciation_method,
"finance_book_id": d.idx "finance_book": d.finance_book,
}) "finance_book_id": d.idx
})
break break
# For first row # For first row
@ -257,11 +259,15 @@ class Asset(AccountsController):
self.to_date = add_months(self.available_for_use_date, self.to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation)) n * cint(d.frequency_of_depreciation))
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, days, months = self.get_pro_rata_amt(d,
depreciation_amount, schedule_date, self.to_date) depreciation_amount, schedule_date, self.to_date)
monthly_schedule_date = add_months(schedule_date, 1) depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
depreciation_amount, d.finance_book)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days) schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date last_schedule_date = schedule_date
@ -397,7 +403,28 @@ class Asset(AccountsController):
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date")
.format(row.idx)) .format(row.idx))
def set_accumulated_depreciation(self, date_of_sale=None, ignore_booked_entry = False): # to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(self, finance_book):
if self.has_only_one_finance_book():
return self.schedules[0].depreciation_amount
else:
for schedule in self.schedules:
if schedule.finance_book == finance_book:
return schedule.depreciation_amount
def has_only_one_finance_book(self):
if len(self.finance_books) == 1:
return True
def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False):
straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line']
finance_books = [] finance_books = []
@ -414,7 +441,7 @@ class Asset(AccountsController):
value_after_depreciation -= flt(depreciation_amount) value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line # for the last row, if depreciation method = Straight Line
if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale: if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale and not date_of_return:
book = self.get('finance_books')[cint(d.finance_book_id) - 1] book = self.get('finance_books')[cint(d.finance_book_id) - 1]
depreciation_amount += flt(value_after_depreciation - depreciation_amount += flt(value_after_depreciation -
flt(book.expected_value_after_useful_life), d.precision("depreciation_amount")) flt(book.expected_value_after_useful_life), d.precision("depreciation_amount"))
@ -833,7 +860,7 @@ def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"): if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time # if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life: if not asset.flags.increase_in_asset_life:
depreciation_amount = (flt(row.value_after_depreciation) - depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being modified after Asset Repair # if the Depreciation Schedule is being modified after Asset Repair

1066
erpnext/assets/doctype/asset/test_asset.py

File diff suppressed because it is too large

5
erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py

@ -41,10 +41,13 @@ def get_conditions(filters):
if filters.get("from_date") and filters.get("to_date"): if filters.get("from_date") and filters.get("to_date"):
conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
for field in ['company', 'name', 'status']: for field in ['company', 'name']:
if filters.get(field): if filters.get(field):
conditions += f" and po.{field} = %({field})s" conditions += f" and po.{field} = %({field})s"
if filters.get('status'):
conditions += " and po.status in %(status)s"
if filters.get('project'): if filters.get('project'):
conditions += " and poi.project = %(project)s" conditions += " and poi.project = %(project)s"

2
erpnext/regional/india/utils.py

@ -855,7 +855,7 @@ def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"): if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time # if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life: if not asset.flags.increase_in_asset_life:
depreciation_amount = (flt(row.value_after_depreciation) - depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being modified after Asset Repair # if the Depreciation Schedule is being modified after Asset Repair

6
erpnext/stock/doctype/stock_entry/stock_entry.js

@ -88,7 +88,11 @@ frappe.ui.form.on('Stock Entry', {
} }
} }
filters["warehouse"] = item.s_warehouse || item.t_warehouse; // User could want to select a manually created empty batch (no warehouse)
// or a pre-existing batch
if (frm.doc.purpose != "Material Receipt") {
filters["warehouse"] = item.s_warehouse || item.t_warehouse;
}
return { return {
query : "erpnext.controllers.queries.get_batch_no", query : "erpnext.controllers.queries.get_batch_no",

Loading…
Cancel
Save