feat: Record Advance Payments as Liability

Ability to let user record advance payments as liability instead of a negative asset.

Issue #34282
This commit is contained in:
Gursheen Anand
2023-06-02 17:13:51 +05:30
parent b7407a1d81
commit 74619269f0
21 changed files with 559 additions and 172 deletions

View File

@@ -8,6 +8,8 @@ import frappe
from frappe import _, bold, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
from frappe.query_builder.functions import Abs, Sum
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import (
add_days,
add_months,
@@ -871,24 +873,30 @@ class AccountsController(TransactionBase):
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
}
if d.get("paid_from"):
advance_row["account"] = d.paid_from
if d.get("paid_to"):
advance_row["account"] = d.paid_to
self.append("advances", advance_row)
def get_advance_entries(self, include_unallocated=True):
if self.doctype == "Sales Invoice":
party_account = self.debit_to
party_type = "Customer"
party = self.customer
amount_field = "credit_in_account_currency"
order_field = "sales_order"
order_doctype = "Sales Order"
party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_received_account"])
else:
party_account = self.credit_to
party_type = "Supplier"
party = self.supplier
amount_field = "debit_in_account_currency"
order_field = "purchase_order"
order_doctype = "Purchase Order"
party_account = frappe.db.get_values("Company", {"company_name": self.company}, ["default_receivable_account", "default_advances_paid_account"])
party_account = list(party_account[0])
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
@@ -2136,45 +2144,41 @@ def get_advance_journal_entries(
order_list,
include_unallocated=True,
):
dr_or_cr = (
"credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
)
conditions = []
if include_unallocated:
conditions.append("ifnull(t2.reference_name, '')=''")
journal_entry = frappe.qb.DocType('Journal Entry')
journal_acc = frappe.qb.DocType('Journal Entry Account')
q = (frappe.qb.from_(journal_entry)
.inner_join(journal_acc)
.on(journal_entry.name == journal_acc.parent)
.select(
ConstantColumn('Journal Entry').as_('reference_type'),
(journal_entry.name).as_('reference_name'),
(journal_entry.remark).as_('remarks'),
(journal_acc.debit_in_account_currency if party_type == 'Supplier' else journal_acc.credit_in_account_currency).as_('amount'),
(journal_acc.name).as_('reference_row'),
(journal_acc.reference_name).as_('against_order'),
(journal_acc.exchange_rate)
)
.where(journal_acc.account.isin(party_account)
& (journal_acc.party_type == party_type)
& (journal_acc.party == party)
& (journal_acc.is_advance == 'Yes')
& (journal_entry.docstatus == 1)
)
)
if party_type == "Customer":
q = q.where(journal_acc.credit_in_account_currency > 0)
if order_list:
order_condition = ", ".join(["%s"] * len(order_list))
conditions.append(
" (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))".format(
order_doctype, order_condition
)
)
q = q.where(journal_acc.reference_type == order_doctype)
if include_unallocated:
q = q.where(journal_acc.reference_name.isin(order_list)
|(journal_acc.reference_name == ''))
else:
q = q.where(journal_acc.reference_name.isin(order_list))
reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
# nosemgrep
journal_entries = frappe.db.sql(
"""
select
'Journal Entry' as reference_type, t1.name as reference_name,
t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
t2.reference_name as against_order, t2.exchange_rate
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t1.name = t2.parent and t2.account = %s
and t2.party_type = %s and t2.party = %s
and t2.is_advance = 'Yes' and t1.docstatus = 1
and {1} > 0 {2}
order by t1.posting_date""".format(
amount_field, dr_or_cr, reference_condition
),
[party_account, party_type, party] + order_list,
as_dict=1,
)
q = q.orderby(journal_entry.posting_date)
journal_entries = q.run(as_dict=True)
return list(journal_entries)
@@ -2189,65 +2193,76 @@ def get_advance_payment_entries(
limit=None,
condition=None,
):
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
currency_field = (
"paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
)
q = build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition)
payment_entries = q.run(as_dict=True)
return list(payment_entries)
def build_query(party_type, party, party_account, order_doctype, order_list, include_unallocated, against_all_orders, limit, condition):
payment_type = "Receive" if party_type == "Customer" else "Pay"
exchange_rate_field = (
"source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
)
payment_entry = frappe.qb.DocType('Payment Entry')
payment_ref = frappe.qb.DocType('Payment Entry Reference')
payment_entries_against_order, unallocated_payment_entries = [], []
limit_cond = "limit %s" % limit if limit else ""
if order_list or against_all_orders:
if order_list:
reference_condition = " and t2.reference_name in ({0})".format(
", ".join(["%s"] * len(order_list))
)
else:
reference_condition = ""
order_list = []
payment_entries_against_order = frappe.db.sql(
"""
select
'Payment Entry' as reference_type, t1.name as reference_name,
t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
t2.reference_name as against_order, t1.posting_date,
t1.{0} as currency, t1.{4} as exchange_rate
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
where
t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
and t2.reference_doctype = %s {2}
order by t1.posting_date {3}
""".format(
currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field
),
[party_account, payment_type, party_type, party, order_doctype] + order_list,
as_dict=1,
q = (frappe.qb.from_(payment_entry)
.select(
ConstantColumn('Payment Entry').as_('reference_type'),
(payment_entry.name).as_('reference_name'),
payment_entry.posting_date,
(payment_entry.remarks).as_('remarks')
)
.where(payment_entry.payment_type == payment_type)
.where(payment_entry.party_type == party_type)
.where(payment_entry.party == party)
.where(payment_entry.docstatus == 1)
)
if party_type == "Customer":
q = q.select(payment_entry.paid_from_account_currency)
q = q.select(payment_entry.paid_from)
q = q.where(payment_entry.paid_from.isin(party_account))
else:
q = q.select(payment_entry.paid_to_account_currency)
q = q.select(payment_entry.paid_to)
q = q.where(payment_entry.paid_to.isin(party_account))
if payment_type == "Receive":
q = q.select(payment_entry.source_exchange_rate)
else:
q.select(payment_entry.target_exchange_rate)
if include_unallocated:
unallocated_payment_entries = frappe.db.sql(
"""
select 'Payment Entry' as reference_type, name as reference_name, posting_date,
remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
from `tabPayment Entry`
where
{0} = %s and party_type = %s and party = %s and payment_type = %s
and docstatus = 1 and unallocated_amount > 0 {condition}
order by posting_date {1}
""".format(
party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""
),
(party_account, party_type, party, payment_type),
as_dict=1,
)
q = q.select((payment_entry.unallocated_amount).as_('amount'))
q = q.where(payment_entry.unallocated_amount>0)
return list(payment_entries_against_order) + list(unallocated_payment_entries)
if condition:
q = q.where(payment_entry.company == condition["company"])
q = q.where(payment_entry.posting_date >= condition["from_payment_date"]) if condition.get("from_payment_date") else q
q = q.where(payment_entry.posting_date <= condition["to_payment_date"]) if condition.get("to_payment_date") else q
if condition.get("get_payments") == True:
q = q.where(payment_entry.cost_center == condition["cost_center"]) if condition.get("cost_center") else q
q = q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q
q = q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q
else:
q = q.where(payment_entry.total_debit >= condition["minimum_payment_amount"]) if condition.get("minimum_payment_amount") else q
q = q.where(payment_entry.total_debit <= condition["maximum_payment_amount"]) if condition.get("maximum_payment_amount") else q
elif order_list or against_all_orders:
q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent)
q = q.select(
(payment_ref.allocated_amount).as_('amount'),
(payment_ref.name).as_('reference_row'),
(payment_ref.reference_name).as_('against_order'),
payment_ref.reference_doctype == order_doctype
)
if order_list:
q = q.where(payment_ref.reference_name.isin(order_list))
q = q.orderby(payment_entry.posting_date)
q = q.limit(limit) if limit else q
return q
def update_invoice_status():
@@ -2846,3 +2861,72 @@ def validate_regional(doc):
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass
def make_advance_liability_entry(gl_entries, pe, allocated_amount, invoice, party_type):
pe = frappe.get_doc("Payment Entry", pe)
if party_type=="Customer":
invoice = frappe.get_doc("Sales Invoice", invoice)
account = pe.paid_from
dr_or_cr = "debit"
rev = "credit"
against = invoice.debit_to
party = invoice.customer
voucher_type = "Sales Invoice"
else:
invoice = frappe.get_doc("Purchase Invoice", invoice)
account = pe.paid_to
dr_or_cr = "credit"
rev = "debit"
against = invoice.credit_to
party = invoice.supplier
voucher_type = "Purchase Invoice"
gl_entries.append(invoice.get_gl_dict(
{
"account": account,
"party_type": party_type,
"party": party,
"due_date": invoice.due_date,
"against": against,
dr_or_cr: allocated_amount,
dr_or_cr + "_in_account_currency": allocated_amount,
rev: 0,
rev + "_in_account_currency": 0,
"against_voucher": invoice.return_against
if cint(invoice.is_return) and invoice.return_against
else invoice.name,
"against_voucher_type": invoice.doctype,
"cost_center": invoice.cost_center,
"project": invoice.project,
"voucher_type": voucher_type,
"voucher_no": invoice.name
},
invoice.party_account_currency,
item=invoice,
))
(dr_or_cr, rev) = ("credit", "debit") if party_type=="Customer" else ("debit", "credit")
gl_entries.append(invoice.get_gl_dict(
{
"account": against,
"party_type": party_type,
"party": party,
"due_date": invoice.due_date,
"against": account,
dr_or_cr: allocated_amount,
dr_or_cr + "_in_account_currency": allocated_amount,
rev: 0,
rev + "_in_account_currency": 0,
"against_voucher": invoice.return_against
if cint(invoice.is_return) and invoice.return_against
else invoice.name,
"against_voucher_type": invoice.doctype,
"cost_center": invoice.cost_center,
"project": invoice.project,
"voucher_type": voucher_type,
"voucher_no": invoice.name
},
invoice.party_account_currency,
item=invoice,
))