Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: release v13 #34812

Merged
merged 36 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
625b8e8
Asset maintenance task add dropdown "3 Yearly" (#34607)
BevanTony1 Mar 29, 2023
ab06cb4
fix: lost opportunity report issue (#34626)
mergify[bot] Mar 29, 2023
545807a
fix: incorrect arg name in asset value adjustment
anandbaburajan Mar 30, 2023
46638b1
fix: serial no with zero quantity issue in stock reco
rohitwaghchaure Mar 30, 2023
60046fe
Merge pull request #34651 from frappe/mergify/bp/version-13-hotfix/pr…
anandbaburajan Mar 30, 2023
a872a7a
Merge pull request #34653 from frappe/mergify/bp/version-13-hotfix/pr…
rohitwaghchaure Mar 30, 2023
e54ff34
chore: auto fill asset name and available for use date (backport #346…
mergify[bot] Mar 30, 2023
503c58e
chore: improve asset depr posting failure msg (#34661)
anandbaburajan Mar 30, 2023
dab1f1a
fix: incorrect balance qty in the stock ledger report
rohitwaghchaure Mar 30, 2023
f22e777
fix: posting time issue
rohitwaghchaure Mar 29, 2023
5f32696
Merge pull request #34666 from frappe/mergify/bp/version-13-hotfix/pr…
anandbaburajan Mar 30, 2023
74280e0
Merge pull request #34672 from frappe/mergify/bp/version-13-hotfix/pr…
rohitwaghchaure Mar 30, 2023
6b02628
Merge pull request #34670 from frappe/mergify/bp/version-13-hotfix/pr…
rohitwaghchaure Mar 30, 2023
85d8ed9
chore: make `Production Plan Item Reference` table hidden in Producti…
s-aga-r Mar 31, 2023
45eb440
chore: `conflicts`
s-aga-r Mar 31, 2023
2770840
Merge pull request #34682 from frappe/mergify/bp/version-13-hotfix/pr…
s-aga-r Mar 31, 2023
198830a
fix: enclose ternary operator in parentheses
ruthra-kumar Mar 31, 2023
008c985
Merge pull request #34685 from frappe/mergify/bp/version-13-hotfix/pr…
ruthra-kumar Mar 31, 2023
9725698
fix: BOM Update Cost, when no actual qty
s-aga-r Mar 30, 2023
e7fd47a
Merge pull request #34698 from frappe/mergify/bp/version-13-hotfix/pr…
s-aga-r Apr 2, 2023
9cf30d7
fix: bom update log not working for large batch size
rohitwaghchaure Apr 3, 2023
313aecf
Merge pull request #34720 from frappe/mergify/bp/version-13-hotfix/pr…
rohitwaghchaure Apr 3, 2023
50de045
fix: filter out old allocation's cf leaves while fetching leave detai…
ruchamahabal Apr 3, 2023
892c480
fix: format currency/float as per number format in work history (#34545)
RJPvT Apr 4, 2023
fed43ae
fix: asset monthly WDV and DD schedule [v13] (#34645)
anandbaburajan Apr 5, 2023
92a26dd
fix: Allocate tax loss to tax account head on early payment discount …
mergify[bot] Apr 5, 2023
3007ac3
fix: don't include cancelled JVs in assdeprledger report (#34737)
mergify[bot] Apr 5, 2023
e76df6f
fix!: require sender and message for contact us page (#34707)
mergify[bot] Apr 5, 2023
af828e4
fix: Shop by category fixes (backport #34688) (#34751)
mergify[bot] Apr 5, 2023
b48fca3
fix: `payment entry is already created` on posawesome. (backport #347…
mergify[bot] Apr 5, 2023
034e35e
Revert "fix: `payment entry is already created` on posawesome. (#34712)"
deepeshgarg007 Apr 5, 2023
16ae117
'Make Asset Movement' button translation fix
ihesamyou Apr 4, 2023
ce151dd
Merge pull request #34780 from frappe/mergify/bp/version-13-hotfix/pr…
anandbaburajan Apr 7, 2023
71bafab
fix: Item tax validity comparison fixes (#34784)
mergify[bot] Apr 10, 2023
8609bf4
fix: provide filter by depreciable assets in fixed asset register (#3…
mergify[bot] Apr 11, 2023
77f1322
revert: remove frappe.send_message (v13) (#34820)
mergify[bot] Apr 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"determine_address_tax_category_from",
"column_break_19",
"add_taxes_from_item_tax_template",
"book_tax_discount_loss",
"period_closing_settings_section",
"acc_frozen_upto",
"frozen_accounts_modifier",
Expand Down Expand Up @@ -284,14 +285,21 @@
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account"
},
{
"default": "0",
"description": "Split Early Payment Discount Loss into Income and Tax Loss",
"fieldname": "book_tax_discount_loss",
"fieldtype": "Check",
"label": "Book Tax Loss on Early Payment Discount"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-07-11 13:37:50.605141",
"modified": "2023-03-28 09:50:20.375233",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
Expand Down
2 changes: 0 additions & 2 deletions erpnext/accounts/doctype/payment_entry/payment_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,6 @@ frappe.ui.form.on('Payment Entry', {
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
party_account_currency, "references");

frm.set_currency_labels(["amount"], company_currency, "deductions");

cur_frm.set_df_property("source_exchange_rate", "description",
("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));

Expand Down
242 changes: 217 additions & 25 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,28 +440,45 @@ def update_payment_schedule(self, cancel=0):

for ref in self.get("references"):
if ref.payment_term and ref.reference_name:
key = (ref.payment_term, ref.reference_name)
key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += ref.allocated_amount

if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all(
"Payment Schedule",
filters={"parent": ref.reference_name},
fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
fields=[
"paid_amount",
"payment_amount",
"payment_term",
"discount",
"outstanding",
"discount_type",
],
)
for term in payment_schedule:
invoice_key = (term.payment_term, ref.reference_name)
invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
if not (term.discount_type and term.discount):
continue

if term.discount_type == "Percentage":
invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
term.discount / 100
)
else:
invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount

for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1):
if not invoice_paid_amount_map.get(key):
frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))

allocated_amount = self.get_allocated_amount_in_transaction_currency(
allocated_amount, key[2], key[1]
)

outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))

Expand Down Expand Up @@ -496,6 +513,33 @@ def update_payment_schedule(self, cancel=0):
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
)

def get_allocated_amount_in_transaction_currency(
self, allocated_amount, reference_doctype, reference_docname
):
"""
Payment Entry could be in base currency while reference's payment schedule
is always in transaction currency.
E.g.
* SI with base=INR and currency=USD
* SI with payment schedule in USD
* PE in INR (accounting done in base currency)
"""
ref_currency, ref_exchange_rate = frappe.db.get_value(
reference_doctype, reference_docname, ["currency", "conversion_rate"]
)
is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
# PE in different currency
reference_is_multi_currency = self.paid_from_account_currency != ref_currency

if not (is_single_currency and reference_is_multi_currency):
return allocated_amount

allocated_amount = flt(
allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
)

return allocated_amount

def set_status(self):
if self.docstatus == 2:
self.status = "Cancelled"
Expand Down Expand Up @@ -1801,7 +1845,14 @@ def get_bill_no_and_update_amounts(


@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
def get_payment_entry(
dt,
dn,
party_amount=None,
bank_account=None,
bank_amount=None,
reference_date=None,
):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
Expand All @@ -1822,15 +1873,17 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)

paid_amount, received_amount, discount_amount = apply_early_payment_discount(
paid_amount, received_amount, doc
reference_date = getdate(reference_date)
paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
)

pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = doc.company
pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate()
pe.reference_date = reference_date
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
Expand Down Expand Up @@ -1871,7 +1924,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
):

for reference in get_reference_as_per_payment_terms(
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
pe.append("references", reference)
else:
Expand Down Expand Up @@ -1922,16 +1975,17 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()

if discount_amount:
pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center
or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
}
base_total_discount_loss = 0
if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)

set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
)
pe.set_difference_amount()

pe.set_difference_amount()

return pe

Expand Down Expand Up @@ -2067,20 +2121,30 @@ def set_paid_amount_and_received_amount(
return paid_amount, received_amount


def apply_early_payment_discount(paid_amount, received_amount, doc):
def apply_early_payment_discount(
paid_amount, received_amount, doc, party_account_currency, reference_date
):
total_discount = 0
valid_discounts = []
eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule

if doc.doctype in eligible_for_payments and has_payment_schedule:
# Non eligible documents may not have `company_currency` field
is_multi_currency = party_account_currency != doc.company_currency

for term in doc.payment_schedule:
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
if not term.discounted_amount and term.discount and reference_date <= term.discount_date:

if term.discount_type == "Percentage":
discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(grand_total) * (term.discount / 100)
else:
discount_amount = term.discount

discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
# if accounting is done in the same currency, paid_amount = received_amount
conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1
discount_amount_in_foreign_currency = discount_amount * conversion_rate

if doc.doctype == "Sales Invoice":
paid_amount -= discount_amount
Expand All @@ -2089,23 +2153,151 @@ def apply_early_payment_discount(paid_amount, received_amount, doc):
received_amount -= discount_amount
paid_amount -= discount_amount_in_foreign_currency

valid_discounts.append({"type": term.discount_type, "discount": term.discount})
total_discount += discount_amount

if total_discount:
money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
currency = doc.get("currency") if is_multi_currency else doc.company_currency
money = frappe.utils.fmt_money(total_discount, currency=currency)
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)

return paid_amount, received_amount, total_discount
return paid_amount, received_amount, total_discount, valid_discounts


def set_pending_discount_loss(
pe, doc, discount_amount, base_total_discount_loss, party_account_currency
):
# If multi-currency, get base discount amount to adjust with base currency deductions/losses
if party_account_currency != doc.company_currency:
discount_amount = discount_amount * doc.get("conversion_rate", 1)

# Avoid considering miniscule losses
discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total"))

# Set base discount amount (discount loss/pending rounding loss) in deductions
if discount_amount > 0.0:
positive_negative = -1 if pe.payment_type == "Pay" else 1

# If tax loss booking is enabled, pending loss will be rounding loss.
# Otherwise it will be the total discount loss.
book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
account_type = "round_off_account" if book_tax_loss else "default_discount_account"

pe.set_gain_or_loss(
account_details={
"account": frappe.get_cached_value("Company", pe.company, account_type),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": discount_amount * positive_negative,
}
)


def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
"""Split early payment discount into Income Loss & Tax Loss."""
total_discount_percent = get_total_discount_percent(doc, valid_discounts)

if not total_discount_percent:
return 0.0

base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)

# Round off total loss rather than individual losses to reduce rounding error
return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total"))


def get_total_discount_percent(doc, valid_discounts) -> float:
"""Get total percentage and amount discount applied as a percentage."""
total_discount_percent = (
sum(
discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
)
or 0.0
)

# Operate in percentages only as it makes the income & tax split easier
total_discount_amount = (
sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
or 0.0
)

if total_discount_amount:
discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
total_discount_percent += discount_percentage
return total_discount_percent

return total_discount_percent


def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
"""Add loss on income discount in base currency."""
precision = doc.precision("total")
base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)

pe.append(
"deductions",
{
"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(base_loss_on_income, precision),
},
)

return base_loss_on_income # Return loss without rounding


def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
"""Add loss on tax discount in base currency."""
tax_discount_loss = {}
base_total_tax_loss = 0
precision = doc.precision("tax_amount_after_discount_amount", "taxes")

# The same account head could be used more than once
for tax in doc.get("taxes", []):
base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
total_discount_percentage / 100
)

account = tax.get("account_head")
if not tax_discount_loss.get(account):
tax_discount_loss[account] = base_tax_loss
else:
tax_discount_loss[account] += base_tax_loss

for account, loss in tax_discount_loss.items():
base_total_tax_loss += loss
if loss == 0.0:
continue

pe.append(
"deductions",
{
"account": account,
"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
"amount": flt(loss, precision),
},
)

return base_total_tax_loss # Return loss without rounding


def get_reference_as_per_payment_terms(
payment_schedule, dt, dn, doc, grand_total, outstanding_amount
payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
):
references = []
is_multi_currency_acc = (doc.currency != doc.company_currency) and (
party_account_currency != doc.company_currency
)

for payment_term in payment_schedule:
payment_term_outstanding = flt(
payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
)
if not is_multi_currency_acc:
# If accounting is done in company currency for multi-currency transaction
payment_term_outstanding = flt(
payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
)

if payment_term_outstanding:
references.append(
Expand Down
Loading