Skip to content

Commit

Permalink
chore: release v13 (#34531)
Browse files Browse the repository at this point in the history
* chore: Update user manual link (#34478)

* chore: Update user manual link (#34478)

(cherry picked from commit be723bb)

# Conflicts:
#	erpnext/patches.txt

* chore: resolve conflicts

* chore: Update version

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* chore: Update version

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* fix: Overallocation of 'qty' from Cr Notes to Parent Invoice

Cr Notes 'qty' are overallocated to parent invoice, when there are
mulitple instances of same item in Invoice.

(cherry picked from commit e2f19c6)

* refactor: Ignore linked Cr Notes in Report output

(cherry picked from commit d0715a8)

* test: Gross Profit report output for Cr notes

2 New test cases added.
1. Standalone Cr notes will be reported as normal Invoices
2. Cr notes against an Invoice will not overallocate qty if there are
multiple instances of same item

(cherry picked from commit cc61dae)

* fix: incorrect depr schedules after asset repair [v13] (#34520)

* fix: incorrect schedule after repair for WDV and DD

* chore: only fix schedules for assets with calc_depr true

* fix: incorrect schedule after repair for straight line and manual

* refactor: calc depr in asset repair and if statement (#34526)

refactor: minor asset repair of calc depr and if statement

* fix(client): Amount calculation for 0 qty debit notes (#34455)

fix(client): Amount calculation for 0 qty debit notes (#34455)

fix(client): Amount calculaton for 0 qty debit notes

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
(cherry picked from commit ee6c107)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>

* fix: german translations (#34312)

fix: german translations (#34312)

fix: some german translations
(cherry picked from commit 59c2e7e)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* fix: exchange gain/loss GL's should be removed if advance is cancelled (#34529)

* fix: report GL for invoice when advance has different exchange rate

If deferred revenue/expense is enabled for any item, don't repost.

* test: cancelling advance should remove exchange gain/loss

If there are no deferred revenue/expense cancelling advance should
cancel the exchange gain/loss booked due to different exchange rates
of payment and its linked invoice

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
  • Loading branch information
6 people committed Mar 21, 2023
1 parent cfcbdfc commit 6e492ec
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 47 deletions.
25 changes: 24 additions & 1 deletion erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import frappe
from frappe import ValidationError, _, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
from frappe.utils import cint, comma_or, flt, get_link_to_form, getdate, nowdate
from six import iteritems, string_types

import erpnext
Expand Down Expand Up @@ -168,8 +168,31 @@ def delink_advance_entry_references(self):
for reference in self.references:
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(reference.reference_doctype, reference.reference_name)

repost_required = False
for adv_reference in doc.get("advances"):
if adv_reference.exchange_gain_loss != 0:
repost_required = True
break
if repost_required:
for item in doc.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
frappe.msgprint(
_(
"Linked Invoice {0} has Exchange Gain/Loss GL entries due to this Payment. Submit a Journal manually to reverse its effects."
).format(get_link_to_form(doc.doctype, doc.name))
)
repost_required = False

doc.delink_advance_entries(self.name)

if repost_required:
doc.reload()
doc.docstatus = 2
doc.make_gl_entries()
doc.docstatus = 1
doc.make_gl_entries()

def set_missing_values(self):
if self.payment_type == "Internal Transfer":
for field in (
Expand Down
72 changes: 72 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3470,6 +3470,78 @@ def test_gain_loss_with_advance_entry(self):
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
)

def test_gain_loss_on_advance_cancellation(self):
unlink_enabled = frappe.db.get_single_value(
"Accounts Settings", "unlink_payment_on_cancellation_of_invoice"
)

frappe.db.set_single_value("Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 1)

pe = frappe.get_doc(
{
"doctype": "Payment Entry",
"payment_type": "Receive",
"party_type": "Customer",
"party": "_Test Customer USD",
"company": "_Test Company",
"paid_from_account_currency": "USD",
"paid_to_account_currency": "INR",
"source_exchange_rate": 70,
"target_exchange_rate": 1,
"reference_no": "1",
"reference_date": nowdate(),
"received_amount": 70,
"paid_amount": 1,
"paid_from": "_Test Receivable USD - _TC",
"paid_to": "_Test Cash - _TC",
}
)
pe.insert()
pe.submit()

si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=75,
do_not_save=1,
rate=1,
)
si = si.save()

si.append(
"advances",
{
"reference_type": "Payment Entry",
"reference_name": pe.name,
"advance_amount": 1,
"allocated_amount": 1,
"ref_exchange_rate": 70,
},
)
si.save()
si.submit()
expected_gle = [
["_Test Receivable USD - _TC", 75.0, 5.0],
["Exchange Gain/Loss - _TC", 5.0, 0.0],
["Sales - _TC", 0.0, 75.0],
]
check_gl_entries(self, si.name, expected_gle, nowdate())

# cancel advance payment
pe.reload()
pe.cancel()

expected_gle_after = [
["_Test Receivable USD - _TC", 75.0, 0.0],
["Sales - _TC", 0.0, 75.0],
]
check_gl_entries(self, si.name, expected_gle_after, nowdate())

frappe.db.set_single_value(
"Accounts Settings", "unlink_payment_on_cancellation_of_invoice", unlink_enabled
)

def test_batch_expiry_for_sales_invoice_return(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.item.test_item import make_item
Expand Down
23 changes: 22 additions & 1 deletion erpnext/accounts/report/gross_profit/gross_profit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from frappe.utils import cint, flt

from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate


Expand Down Expand Up @@ -465,7 +466,14 @@ def get_average_rate_based_on_group_by(self):
):
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
for returned_item_row in returned_item_rows:
row.qty += flt(returned_item_row.qty)
# returned_items 'qty' should be stateful
if returned_item_row.qty != 0:
if row.qty >= abs(returned_item_row.qty):
row.qty += returned_item_row.qty
returned_item_row.qty = 0
else:
row.qty = 0
returned_item_row.qty += row.qty
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
if flt(row.qty) or row.base_amount:
Expand Down Expand Up @@ -664,6 +672,19 @@ def load_invoice_items(self):
if self.filters.to_date:
conditions += " and posting_date <= %(to_date)s"

conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"

if self.filters.item_group:
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))

if self.filters.sales_person:
conditions += """
and exists(select 1
from `tabSales Team` st
where st.parent = `tabSales Invoice`.name
and st.sales_person = %(sales_person)s)
"""

if self.filters.group_by == "Sales Person":
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
Expand Down
79 changes: 79 additions & 0 deletions erpnext/accounts/report/gross_profit/test_gross_profit.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,82 @@ def test_order_connected_dn_and_inv(self):
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])

def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
"""
Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return

# Invoice with an item added twice
sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
sinv = sinv.save().submit()

# Create Credit Note for Invoice
cr_note = make_sales_return(sinv.name)
cr_note = cr_note.save().submit()

filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)

columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 0.0,
"avg._selling_rate": 0.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
# Both items of Invoice should have '0' qty
self.assertEqual(len(gp_entry), 2)
self.assertDictContainsSubset(expected_entry, gp_entry[0])
self.assertDictContainsSubset(expected_entry, gp_entry[1])

def test_standalone_cr_notes(self):
"""
Standalone cr notes will be reported as usual
"""
# Make Cr Note
sinv = self.create_sales_invoice(
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
sinv.is_return = 1
sinv = sinv.save().submit()

filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)

columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": -1.0,
"avg._selling_rate": 100.0,
"valuation_rate": 0.0,
"selling_amount": -100.0,
"buying_amount": 0.0,
"gross_profit": -100.0,
"gross_profit_%": 100.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
35 changes: 23 additions & 12 deletions erpnext/assets/doctype/asset/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,12 +380,19 @@ def make_depreciation_schedule(self, date_of_disposal):
value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))

# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
if (
finance_book.expected_value_after_useful_life
and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
)
and (
not self.flags.increase_in_asset_value_due_to_repair
or not finance_book.depreciation_method in ("Written Down Value", "Double Declining Balance")
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
):
depreciation_amount += (
value_after_depreciation - finance_book.expected_value_after_useful_life
Expand Down Expand Up @@ -1171,17 +1178,21 @@ def get_total_days(date, frequency):
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
if asset.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif asset.flags.increase_in_asset_value_due_to_repair:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)

# if the Depreciation Schedule is being modified after Asset Repair
# if the Depreciation Schedule is being prepared for the first time
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))

Expand Down
42 changes: 34 additions & 8 deletions erpnext/assets/doctype/asset_repair/asset_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ def calculate_total_repair_cost(self):
def before_submit(self):
self.check_repair_status()

self.asset_doc.flags.increase_in_asset_value_due_to_repair = False

if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True

self.increase_asset_value()

if self.get("stock_consumption"):
Expand All @@ -49,20 +53,23 @@ def before_submit(self):
if self.get("capitalize_repair_cost"):
self.make_gl_entries()

if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.modify_depreciation_schedule()

self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
if self.asset_doc.calculate_depreciation:
self.update_asset_expected_value_after_useful_life()
self.asset_doc.save()

def before_cancel(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)

self.asset_doc.flags.increase_in_asset_value_due_to_repair = False

if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
self.asset_doc.flags.increase_in_asset_value_due_to_repair = True

self.decrease_asset_value()

if self.get("stock_consumption"):
Expand All @@ -72,14 +79,13 @@ def before_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)

if (
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
and self.increase_in_asset_life
):
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.revert_depreciation_schedule_on_cancellation()

self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
if self.asset_doc.calculate_depreciation:
self.update_asset_expected_value_after_useful_life()
self.asset_doc.save()

def after_delete(self):
Expand All @@ -100,6 +106,26 @@ def check_for_stock_items_and_warehouse(self):
title=_("Missing Warehouse"),
)

def update_asset_expected_value_after_useful_life(self):
for row in self.asset_doc.get("finance_books"):
if row.depreciation_method in ("Written Down Value", "Double Declining Balance"):
accumulated_depreciation_after_full_schedule = [
d.accumulated_depreciation_amount
for d in self.asset_doc.get("schedules")
if cint(d.finance_book_id) == row.idx
]

accumulated_depreciation_after_full_schedule = max(
accumulated_depreciation_after_full_schedule
)

asset_value_after_full_schedule = flt(
flt(row.value_after_depreciation) - flt(accumulated_depreciation_after_full_schedule),
row.precision("expected_value_after_useful_life"),
)

row.expected_value_after_useful_life = asset_value_after_full_schedule

def increase_asset_value(self):
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()

Expand Down
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,4 @@ erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0})
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v13_0.update_docs_link
Loading

0 comments on commit 6e492ec

Please sign in to comment.