Skip to content

Commit

Permalink
Merge branch 'develop' into fix-tax-breakup-for-diff-tax-rates
Browse files Browse the repository at this point in the history
  • Loading branch information
vorasmit authored Jul 24, 2023
2 parents 6f376cf + 1bc87a9 commit 1b8490d
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 54 deletions.
9 changes: 3 additions & 6 deletions erpnext/accounts/doctype/payment_entry/payment_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,10 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});

payment_term_list = payment_term_list.map(pt => pt.payment_term);

return {
query: "erpnext.controllers.queries.get_payment_terms_for_references",
filters: {
'name': ['in', payment_term_list]
'reference': child.reference_name
}
}
}
Expand Down Expand Up @@ -1463,4 +1460,4 @@ frappe.ui.form.on('Payment Entry', {
});
}
},
})
})
38 changes: 34 additions & 4 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,20 @@ def validate_allocated_amount(self):
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))

def term_based_allocation_enabled_for_reference(
self, reference_doctype: str, reference_name: str
) -> bool:
if (
reference_doctype
and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"]
and reference_name
):
if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"):
return frappe.db.get_value(
"Payment Terms Template", template, "allocate_payment_based_on_payment_terms"
)
return False

def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
Expand All @@ -228,10 +242,23 @@ def validate_allocated_amount_with_latest_data(self):
d = frappe._dict(d)
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d

for d in self.get("references"):
latest = (latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()).get(
d.payment_term
)
for idx, d in enumerate(self.get("references"), start=1):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()

# If term based allocation is enabled, throw
if (
d.payment_term is None or d.payment_term == ""
) and self.term_based_allocation_enabled_for_reference(
d.reference_doctype, d.reference_name
):
frappe.throw(
_(
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
).format(frappe.bold(d.reference_name), frappe.bold(idx))
)

# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
latest = latest.get(d.payment_term) or latest.get(None)

# The reference has already been fully paid
if not latest:
Expand Down Expand Up @@ -1633,6 +1660,9 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
"invoice_amount": flt(d.invoice_amount),
"outstanding_amount": flt(d.outstanding_amount),
"payment_term_outstanding": payment_term_outstanding,
"allocated_amount": payment_term_outstanding
if payment_term_outstanding
else d.outstanding_amount,
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
"account": d.account,
Expand Down
109 changes: 109 additions & 0 deletions erpnext/accounts/doctype/payment_entry/test_payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,101 @@ def test_details_update_on_reference_table(self):
}
self.assertDictEqual(ref_details, expected_response)

@change_settings(
"Accounts Settings",
{
"unlink_payment_on_cancellation_of_invoice": 1,
"delete_linked_ledger_entries": 1,
"allow_multi_currency_invoices_against_single_party_account": 1,
},
)
def test_overallocation_validation_on_payment_terms(self):
"""
Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown.
"""
customer = create_customer()
create_payment_terms_template()

# Validate allocation on base/company currency
si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200)
si1.payment_terms_template = "Test Receivable Template"
si1.save().submit()

si1.reload()
pe = get_payment_entry(si1.doctype, si1.name).save()
# Allocated amount should be according to the payment schedule
for idx, schedule in enumerate(si1.payment_schedule):
with self.subTest(idx=idx):
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
pe.save()

# Overallocation validation should trigger
pe.paid_amount = 400
pe.references[0].allocated_amount = 200
pe.references[1].allocated_amount = 200
self.assertRaises(frappe.ValidationError, pe.save)
pe.delete()
si1.cancel()
si1.delete()

# Validate allocation on foreign currency
si2 = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=80,
do_not_save=1,
)
si2.payment_terms_template = "Test Receivable Template"
si2.save().submit()

si2.reload()
pe = get_payment_entry(si2.doctype, si2.name).save()
# Allocated amount should be according to the payment schedule
for idx, schedule in enumerate(si2.payment_schedule):
with self.subTest(idx=idx):
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
pe.save()

# Overallocation validation should trigger
pe.paid_amount = 200
pe.references[0].allocated_amount = 100
pe.references[1].allocated_amount = 100
self.assertRaises(frappe.ValidationError, pe.save)
pe.delete()
si2.cancel()
si2.delete()

# Validate allocation in base/company currency on a foreign currency document
# when invoice is made is foreign currency, but posted to base/company currency debtors account
si3 = create_sales_invoice(
customer=customer,
currency="USD",
conversion_rate=80,
do_not_save=1,
)
si3.payment_terms_template = "Test Receivable Template"
si3.save().submit()

si3.reload()
pe = get_payment_entry(si3.doctype, si3.name).save()
# Allocated amount should be equal to payment term outstanding
self.assertEqual(len(pe.references), 2)
for idx, ref in enumerate(pe.references):
with self.subTest(idx=idx):
self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount)
pe.save()

# Overallocation validation should trigger
pe.paid_amount = 16000
pe.references[0].allocated_amount = 8000
pe.references[1].allocated_amount = 8000
self.assertRaises(frappe.ValidationError, pe.save)
pe.delete()
si3.cancel()
si3.delete()


def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
Expand Down Expand Up @@ -1150,3 +1245,17 @@ def create_payment_terms_template_with_discount(
def create_payment_term(name):
if not frappe.db.exists("Payment Term", name):
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()


def create_customer(name="_Test Customer 2 USD", currency="USD"):
customer = None
if frappe.db.exists("Customer", name):
customer = name
else:
customer = frappe.new_doc("Customer")
customer.customer_name = name
customer.default_currency = currency
customer.type = "Individual"
customer.save()
customer = customer.name
return customer
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
def get_ar_filters(doc, entry):
return {
"report_date": doc.posting_date if doc.posting_date else None,
"customer_name": entry.customer,
"customer": entry.customer,
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
"sales_partner": doc.sales_partner if doc.sales_partner else None,
"sales_person": doc.sales_person if doc.sales_person else None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@

<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
<h4 class="text-center">
{% if (filters.customer_name) %}
{{ filters.customer_name }}
{% else %}
{{ filters.customer ~ filters.supplier }}
{% endif %}
{{ filters.customer }}
</h4>
<h6 class="text-center">
{% if (filters.tax_id) %}
{{ _("Tax Id: ") }}{{ filters.tax_id }}
{% endif %}
{% if (filters.tax_id) %}
{{ _("Tax Id: ") }}{{ filters.tax_id }}
{% endif %}
</h6>
<h5 class="text-center">
{{ _(filters.ageing_based_on) }}
Expand Down
5 changes: 2 additions & 3 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.events.append_time_log(frm, timesheet, 1.0);
}
});
frm.refresh_field("timesheets");
frm.trigger("calculate_timesheet_totals");
},

async get_exchange_rate(frm, from_currency, to_currency) {
Expand Down Expand Up @@ -936,9 +938,6 @@ frappe.ui.form.on('Sales Invoice', {
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
row.timesheet_detail = time_log.name;
row.project_name = time_log.project_name;

frm.refresh_field("timesheets");
frm.trigger("calculate_timesheet_totals");
},

calculate_timesheet_totals: function(frm) {
Expand Down
6 changes: 3 additions & 3 deletions erpnext/accounts/party.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from erpnext import get_company_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
from erpnext.utilities.regional import temporary_flag

PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
SALES_TRANSACTION_TYPES = {
Expand Down Expand Up @@ -261,9 +262,8 @@ def set_address_details(
)

if doctype in TRANSACTION_TYPES:
# required to set correct region
frappe.flags.company = company
get_regional_address_details(party_details, doctype, company)
with temporary_flag("company", company):
get_regional_address_details(party_details, doctype, company)

return party_address, shipping_address

Expand Down
2 changes: 2 additions & 0 deletions erpnext/assets/doctype/asset/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,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
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name
Expand Down
23 changes: 12 additions & 11 deletions erpnext/assets/doctype/asset_movement/asset_movement.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,21 @@ def validate_location(self):
frappe.throw(_("Source and Target Location cannot be same"))

if self.purpose == "Receipt":
if not (d.source_location or d.from_employee) and not (d.target_location or d.to_employee):
if not (d.source_location) and not (d.target_location or d.to_employee):
frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
)
elif d.from_employee and not d.target_location:
frappe.throw(
_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
)
elif d.to_employee and d.target_location:
frappe.throw(
_(
"Asset {0} cannot be received at a location and given to an employee in a single movement"
).format(d.asset)
)
elif d.source_location:
if d.from_employee and not d.target_location:
frappe.throw(
_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
)
elif d.to_employee and d.target_location:
frappe.throw(
_(
"Asset {0} cannot be received at a location and given to an employee in a single movement"
).format(d.asset)
)

def validate_employee(self):
for d in self.assets:
Expand Down
5 changes: 4 additions & 1 deletion erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
get_item_tax_map,
get_item_warehouse,
)
from erpnext.utilities.regional import temporary_flag
from erpnext.utilities.transaction_base import TransactionBase


Expand Down Expand Up @@ -760,7 +761,9 @@ def get_gl_dict(self, args, account_currency=None, item=None):
}
)

update_gl_dict_with_regional_fields(self, gl_dict)
with temporary_flag("company", self.company):
update_gl_dict_with_regional_fields(self, gl_dict)

accounting_dimensions = get_accounting_dimensions()
dimension_dict = frappe._dict()

Expand Down
15 changes: 15 additions & 0 deletions erpnext/controllers/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,3 +874,18 @@ def get_fields(doctype, fields=None):
fields.insert(1, meta.title_field.strip())

return unique(fields)


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len, filters) -> list:
terms = []
if filters:
terms = frappe.db.get_all(
"Payment Schedule",
filters={"parent": filters.get("reference")},
fields=["payment_term"],
limit=page_len,
as_list=1,
)
return terms
Loading

0 comments on commit 1b8490d

Please sign in to comment.