Skip to content

Commit

Permalink
fix: Create single PE for multiple unpaid invoices
Browse files Browse the repository at this point in the history
- make sure that doctype and party is the same for all invoices
- create single PE and link to BT
  • Loading branch information
marination committed Apr 10, 2024
1 parent 466cd26 commit 6e5f3be
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 78 deletions.
152 changes: 75 additions & 77 deletions banking/overrides/bank_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
)
from erpnext.accounts.doctype.bank_transaction.bank_transaction import BankTransaction

DOCTYPE, DOCNAME, AMOUNT, PARTY = 0, 1, 2, 3


class CustomBankTransaction(BankTransaction):
def add_payment_entries(self, vouchers):
Expand All @@ -23,37 +25,44 @@ def add_payment_entries(self, vouchers):
invoices_to_bill = []

for voucher in vouchers:
voucher_type, voucher_name = voucher["payment_doctype"], voucher["payment_name"]
if find(
self.payment_entries,
lambda x: x.payment_document == voucher["payment_doctype"]
and x.payment_entry == voucher["payment_name"],
lambda x: x.payment_document == voucher_type and x.payment_entry == voucher_name,
):
continue # Can't add same voucher twice

payment_doctype, payment_name = voucher["payment_doctype"], voucher["payment_name"]
outstanding_amount = self.get_outstanding_amount(payment_doctype, payment_name)
outstanding_amount = get_outstanding_amount(voucher_type, voucher_name)
allocated_by_voucher = min(unallocated_amount, outstanding_amount)

if outstanding_amount > 0:
# Make Payment Entry against the unpaid invoice, link PE to Bank Transaction
invoices_to_bill.append((payment_doctype, payment_name, allocated_by_voucher))
if (
voucher_type
in (
"Sales Invoice",
"Purchase Invoice",
"Expense Claim",
)
and outstanding_amount > 0.0
):
# Make PE against the unpaid invoice, link PE to Bank Transaction
invoices_to_bill.append(
(voucher_type, voucher_name, allocated_by_voucher, voucher.get("party"))
)
else:
self.add_to_payment_entry(payment_doctype, payment_name)

# Make single PE against multiple invoices
if invoices_to_bill:
payment_name = self.make_pe_against_invoices(invoices_to_bill)
payment_doctype = "Payment Entry" # Change doctype to PE
self.add_to_payment_entry(payment_doctype, payment_name)
self.add_to_payment_entry(voucher_type, voucher_name)

# Reduce unallocated amount
unallocated_amount = flt(
unallocated_amount - allocated_by_voucher, self.precision("unallocated_amount")
)

# runs on_update_after_submit
# Make single PE against multiple invoices
if invoices_to_bill:
payment_name = self.make_pe_against_invoices(invoices_to_bill)
self.add_to_payment_entry("Payment Entry", payment_name) # Change doctype to PE

if len(self.payment_entries) != pe_length_before:
self.save()
self.save() # runs on_update_after_submit

def add_to_payment_entry(self, payment_doctype, payment_name):
"""Add the payment entry to the bank transaction"""
Expand All @@ -71,100 +80,89 @@ def get_outstanding_amount(self, payment_doctype, payment_name):
# Check if the invoice is unpaid
return flt(frappe.db.get_value(payment_doctype, payment_name, "outstanding_amount"))

def make_pe_against_invoice(self, payment_doctype, payment_name, to_allocate):
def make_pe_against_invoices(self, invoices_to_bill):
"""Make Payment Entry against multiple invoices."""
self.validate_invoices_to_bill(invoices_to_bill)

bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
if payment_doctype == "Expense Claim":
first_invoice = invoices_to_bill[0]
if first_invoice[DOCTYPE] == "Expense Claim":
from hrms.overrides.employee_payment_entry import get_payment_entry_for_employee

payment_entry = get_payment_entry_for_employee(
payment_doctype,
payment_name,
party_amount=to_allocate,
first_invoice[DOCTYPE],
first_invoice[DOCNAME],
party_amount=first_invoice[AMOUNT],
bank_account=bank_account,
)
else:
payment_entry = get_payment_entry(
payment_doctype,
payment_name,
party_amount=to_allocate,
first_invoice[DOCTYPE],
first_invoice[DOCNAME],
party_amount=first_invoice[AMOUNT],
bank_account=bank_account,
)
payment_entry.reference_no = self.reference_number or first_invoice[DOCNAME]

payment_entry.reference_no = self.reference_number or payment_name
payment_entry.reference_date = self.date
payment_entry.submit()
invoices = split_invoices_based_on_payment_terms(
self.prepare_invoices_to_split(invoices_to_bill[1:]), self.company
)

return payment_entry.name
to_allocate = self.unallocated_amount
for row in invoices:
row_allocated_amount = min(row.outstanding_amount, to_allocate) # partial allocation
row.allocated_amount = row_allocated_amount
row.reference_doctype = row.voucher_type
row.reference_name = row.voucher_no
payment_entry.append("references", row)

def make_pe_against_invoices(self, invoices_to_bill):
"""Make Payment Entry against multiple invoices."""
bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
to_allocate -= row_allocated_amount
if to_allocate <= 0:
break

payment_entry_dict = frappe._dict(
{
"company": self.company,
"payment_type": "Receive" if self.deposit > 0.0 else "Pay",
"reference_no": self.reference_number or invoices_to_bill[0][1],
"reference_date": self.date,
"party_type": "Customer" if self.deposit > 0.0 else "Supplier",
"paid_amount": self.unallocated_amount,
"received_amount": self.unallocated_amount,
}
)
payment_entry = frappe.new_doc("Payment Entry")
payment_entry.update(payment_entry_dict)
payment_entry.party = (
self.party
or frappe.db.get_value(
invoices_to_bill[0][0],
invoices_to_bill[0][1],
payment_entry_dict.party_type.lower(),
),
payment_entry.paid_amount = sum(
row.allocated_amount for row in payment_entry.references
)
payment_entry.submit() # TODO: submit it after testing
return payment_entry.name

if payment_entry_dict.payment_type == "Receive":
payment_entry.paid_to = bank_account
else:
payment_entry.paid_from = bank_account

def prepare_invoices_to_split(self, invoices):
invoices_to_split = []
for invoice in invoices_to_bill:
for invoice in invoices:
invoice_data = frappe.db.get_value(
invoice[0],
invoice[1],
invoice[DOCTYPE],
invoice[DOCNAME],
[
"name as voucher_no",
"posting_date",
"base_grand_total as invoice_amount",
"outstanding_amount",
"due_date",
],
as_dict=True,
)
invoice_data["voucher_type"] = invoice[0]
invoice_data["outstanding_amount"] = invoice[AMOUNT]
invoice_data["voucher_type"] = invoice[DOCTYPE]
invoices_to_split.append(invoice_data)

invoices = split_invoices_based_on_payment_terms(invoices_to_split, self.company)
return invoices_to_split

to_allocate = self.unallocated_amount
for row in invoices:
row_allocated_amount = min(row.outstanding_amount, to_allocate)
row.allocated_amount = row_allocated_amount
row.reference_doctype = row.voucher_type
row.reference_name = row.voucher_no
payment_entry.append("references", row)

to_allocate -= row_allocated_amount
if to_allocate <= 0:
break
def validate_invoices_to_bill(self, invoices_to_bill):
unique_doctypes = {invoice[DOCTYPE] for invoice in invoices_to_bill}
if len(unique_doctypes) > 1:
frappe.throw(
frappe._("Cannot make Reconciliation Payment Entry against multiple doctypes")
)

payment_entry.save()
return payment_entry.name
unique_parties = {invoice[PARTY] for invoice in invoices_to_bill}
if len(unique_parties) > 1:
frappe.throw(
frappe._("Cannot make Reconciliation Payment Entry against multiple parties")
)


def get_outstanding_amount(payment_doctype, payment_name):
def get_outstanding_amount(payment_doctype, payment_name) -> float:
if payment_doctype not in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
return 0
return 0.0

if payment_doctype == "Expense Claim":
ec = frappe.get_doc(payment_doctype, payment_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ erpnext.accounts.bank_reconciliation.MatchTab = class MatchTab {
}

reconcile_selected_vouchers() {
var me = this;
const me = this;
let selected_vouchers = [];
let selected_map = this.actions_table.rowmanager.checkMap;
let voucher_rows = this.actions_table.getRows();
Expand Down

0 comments on commit 6e5f3be

Please sign in to comment.