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

feat: Payment Discount Term #553

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file.
44 changes: 44 additions & 0 deletions erpnext/accounts/doctype/discount_terms/discount_terms.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"actions": [],
"creation": "2024-10-31 12:32:34.084579",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"discount",
"no_of_days",
"discount_date"
],
"fields": [
{
"fieldname": "discount",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Discount (in %)"
},
{
"fieldname": "no_of_days",
"fieldtype": "Int",
"in_list_view": 1,
"label": "No of Days"
},
{
"fieldname": "discount_date",
"fieldtype": "Date",
"label": "Discount Date",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-04 13:16:20.552711",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Discount Terms",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
24 changes: 24 additions & 0 deletions erpnext/accounts/doctype/discount_terms/discount_terms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

import frappe
from frappe.model.document import Document


class DiscountTerms(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from frappe.types import DF

discount: DF.Float
discount_date: DF.Date | None
no_of_days: DF.Int
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass
75 changes: 68 additions & 7 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,7 @@ def get_reference_details(
def get_payment_entry(
dt,
dn,
amount_to_be_paid = None,
party_amount=None,
bank_account=None,
bank_amount=None,
Expand All @@ -2244,6 +2245,9 @@ def get_payment_entry(
if not party_type:
party_type = set_party_type(dt)

if float(amount_to_be_paid) < 1:
frappe.throw("Invalid Amount")

party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)

Expand All @@ -2263,13 +2267,17 @@ def get_payment_entry(
if party_bank_account:
account = frappe.db.get_value("Bank Account", party_bank_account, "account")
bank = get_bank_cash_account(doc, account)

paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)

if amount_to_be_paid :
paid_amount = received_amount = float(amount_to_be_paid)
allocated_amount = amount_to_be_paid
else:
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)
allocated_amount = outstanding_amount

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

Expand Down Expand Up @@ -2359,7 +2367,7 @@ def get_payment_entry(
"due_date": doc.get("due_date"),
"total_amount": grand_total,
"outstanding_amount": outstanding_amount,
"allocated_amount": outstanding_amount,
"allocated_amount": allocated_amount,
},
)

Expand Down Expand Up @@ -2766,3 +2774,56 @@ def set_missing_values(source, target):
@erpnext.allow_regional
def add_regional_gl_entries(gl_entries, doc):
return

@frappe.whitelist()
def get_discount_term(doc = None):
doc = frappe.parse_json(doc)
payment_reference = frappe._dict(doc.references[0])
sales_invoice_doc = frappe.get_doc(payment_reference.reference_doctype, payment_reference.reference_name)
received_amount = doc.paid_amount

paid_amount, received_amount, discount_amount, valid_discounts = apply_discount_paid_amount(
doc.paid_amount, received_amount, sales_invoice_doc, doc.paid_to_account_currency, frappe.utils.getdate(doc.reference_date)
)
return discount_amount

def apply_discount_paid_amount(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_discount_term = hasattr(doc, "payment_discount_terms") and doc.payment_discount_terms
is_multi_currency = party_account_currency != doc.company_currency
is_discount_applied = False

if doc.doctype in eligible_for_payments and has_payment_discount_term:
for term in doc.payment_discount_terms:
if term.discount and reference_date <= term.discount_date and is_discount_applied is False:
# grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
discount_amount = flt(paid_amount) * (term.discount / 100)

# 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
received_amount -= discount_amount_in_foreign_currency
else:
received_amount -= discount_amount
paid_amount -= discount_amount_in_foreign_currency

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

is_discount_applied = True

else:
continue

if total_discount:
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, valid_discounts

30 changes: 29 additions & 1 deletion erpnext/accounts/doctype/payment_term/payment_term.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"discount_validity_based_on",
"discount_validity",
"section_break_6",
"discount_terms_column",
"is_for_sales",
"is_for_purchase",
"table_ouui",
"description"
],
"fields": [
Expand Down Expand Up @@ -113,10 +117,33 @@
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "discount_terms_column",
"fieldtype": "Column Break",
"label": "Discount terms",
"options": "Discount Terms"
},
{
"fieldname": "table_ouui",
"fieldtype": "Table",
"options": "Discount Terms"
},
{
"default": "0",
"fieldname": "is_for_sales",
"fieldtype": "Check",
"label": "Is For Sales"
},
{
"default": "0",
"fieldname": "is_for_purchase",
"fieldtype": "Check",
"label": "Is For Purchase"
}
],
"links": [],
"modified": "2021-02-15 20:30:56.256403",
"modified": "2024-10-31 13:02:05.882908",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Term",
Expand Down Expand Up @@ -162,5 +189,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
16 changes: 6 additions & 10 deletions erpnext/accounts/doctype/payment_term/payment_term.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class PaymentTerm(Document):
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from erpnext.accounts.doctype.discount_terms.discount_terms import DiscountTerms
from frappe.types import DF

credit_days: DF.Int
Expand All @@ -20,19 +21,14 @@ class PaymentTerm(Document):
discount: DF.Float
discount_type: DF.Literal["Percentage", "Amount"]
discount_validity: DF.Int
discount_validity_based_on: DF.Literal[
"Day(s) after invoice date",
"Day(s) after the end of the invoice month",
"Month(s) after the end of the invoice month",
]
due_date_based_on: DF.Literal[
"Day(s) after invoice date",
"Day(s) after the end of the invoice month",
"Month(s) after the end of the invoice month",
]
discount_validity_based_on: DF.Literal["Day(s) after invoice date", "Day(s) after the end of the invoice month", "Month(s) after the end of the invoice month"]
due_date_based_on: DF.Literal["Day(s) after invoice date", "Day(s) after the end of the invoice month", "Month(s) after the end of the invoice month"]
invoice_portion: DF.Float
is_for_purchase: DF.Check
is_for_sales: DF.Check
mode_of_payment: DF.Link | None
payment_term_name: DF.Data | None
table_ouui: DF.Table[DiscountTerms]
# end: auto-generated types

pass
44 changes: 44 additions & 0 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
this.frm.trigger("supplier");
}
cur_frm.set_query("payment_term",function (doc) {
return {

filters: {
is_for_purchase: 1,
},
};
});
}

refresh(doc) {
Expand Down Expand Up @@ -440,6 +448,42 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
frm: cur_frm,
});
}

payment_term(frm){
if(frm.payment_term){
frappe.call({
doc: frm,
method: "get_payment_discount_term",
callback: (response)=>{
cur_frm.set_value('payment_discount_terms', []);
let discount_terms = response.message
if(discount_terms){
for (let discount_term of discount_terms){
let discount_date = frappe.datetime.add_days(cur_frm.doc.posting_date, discount_term.no_of_days)
let childTable = cur_frm.add_child("payment_discount_terms");

childTable.no_of_days = discount_term.no_of_days
childTable.discount = discount_term.discount
childTable.discount_date = discount_date
cur_frm.refresh_fields("payment_discount_terms");
}
}
}
})
} else {
if (frm.payment_discount_terms.length > 0) cur_frm.set_value('payment_discount_terms', []);
}
}

validate(frm){
if(frm.payment_discount_terms && frm.payment_discount_terms.length > 0){
for(let discount_row of frm.payment_discount_terms){
if(discount_row.discount_date > frm.due_date){
frappe.throw(`Error from Row ${discount_row.idx} <br><b>Discount term period</b> cannot be greater then <b>Payment Due Date</b>.`)
}
}
}
}
};

cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
Expand Down
17 changes: 15 additions & 2 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
"billing_address_display",
"terms_tab",
"payment_schedule_section",
"payment_term",
"payment_discount_terms",
"payment_terms_template",
"ignore_default_payment_terms_template",
"payment_schedule",
Expand Down Expand Up @@ -1624,13 +1626,24 @@
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
"label": "Update Outstanding for Self"
},
{
"fieldname": "payment_term",
"fieldtype": "Link",
"label": "Payment Term",
"options": "Payment Term"
},
{
"fieldname": "payment_discount_terms",
"fieldtype": "Table",
"options": "Discount Terms"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2024-07-25 19:42:36.931278",
"modified": "2024-11-05 16:09:42.417227",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
Expand Down Expand Up @@ -1693,4 +1706,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}
Loading
Loading