chore: Extract Expense Claim, FnF, Gratuity, Employee Advance related code from accounting

- Added hooks `advance_payment_doctypes`, `invoice_doctypes`, `period_closing_doctypes` for other apps to extend accounting functionality

- Removed `set_query` code from `journal_entry.js` and `payment_entry.js`

- removed `update_status_for_full_and_final_statement` trigger on JE submission/cancellation

- refactored `payment_entry.py`: split functions for validating reference doctypes for easy overriding, removed hrms references from `get_reference_details` and `get_payment_entry`

- removed dead code: functions `get_bill_no_and_update_amounts`, `get_total_amount_exchange_rate_base_on_currency`, `get_total_amount_exchange_rate_for_employee_advance`, `get_amounts_based_on_ref_doc`, `get_amounts_based_on_reference_doctype`
This commit is contained in:
Rucha Mahabal 2022-06-23 16:17:34 +05:30
parent f6fbcc99a3
commit 1ff0e4519f
6 changed files with 50 additions and 247 deletions

View File

@ -49,15 +49,8 @@ class AccountingPeriod(Document):
@frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
doctypes = [
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
doctypes = frappe.get_hooks("period_closing_doctypes")
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)

View File

@ -240,25 +240,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
var jvd = frappe.get_doc(cdt, cdn);
// expense claim
if(jvd.reference_type==="Expense Claim") {
return {
filters: {
'total_sanctioned_amount': ['>', 0],
'status': ['!=', 'Paid'],
'docstatus': 1
}
};
}
if(jvd.reference_type==="Employee Advance") {
return {
filters: {
'docstatus': 1
}
};
}
// journal entry
if(jvd.reference_type==="Journal Entry") {
frappe.model.validate_missing(jvd, "account");
@ -271,13 +252,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
};
}
// payroll entry
if(jvd.reference_type==="Payroll Entry") {
return {
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.get_payroll_entries_for_jv",
};
}
var out = {
filters: [
[jvd.reference_type, "docstatus", "=", 1]

View File

@ -83,7 +83,6 @@ class JournalEntry(AccountsController):
self.update_advance_paid()
self.update_inter_company_jv()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
@ -97,7 +96,6 @@ class JournalEntry(AccountsController):
self.unlink_inter_company_jv()
self.unlink_asset_adjustment_entry()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
@ -106,21 +104,13 @@ class JournalEntry(AccountsController):
advance_paid = frappe._dict()
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in ("Sales Order", "Purchase Order", "Employee Advance"):
if d.reference_type in frappe.get_hooks("advance_payment_doctypes"):
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def update_status_for_full_and_final_statement(self):
for entry in self.accounts:
if entry.reference_type == "Full and Final Statement":
if self.docstatus == 1:
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Paid")
elif self.docstatus == 2:
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid")
def validate_inter_company_accounts(self):
if (
self.voucher_type == "Inter Company Journal Entry"

View File

@ -110,8 +110,6 @@ frappe.ui.form.on('Payment Entry', {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
} else if (frm.doc.party_type == "Supplier") {
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
} else if (frm.doc.party_type == "Employee") {
var doctypes = ["Expense Claim", "Journal Entry"];
} else {
var doctypes = ["Journal Entry"];
}
@ -140,17 +138,12 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
'Purchase Order', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party;
}
if(child.reference_doctype == "Expense Claim") {
filters["docstatus"] = 1;
filters["is_paid"] = 0;
}
return {
filters: filters
};
@ -730,7 +723,7 @@ frappe.ui.form.on('Payment Entry', {
c.payment_term = d.payment_term;
c.allocated_amount = d.allocated_amount;
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
if(flt(d.outstanding_amount) > 0)
total_positive_outstanding += flt(d.outstanding_amount);
else
@ -745,7 +738,7 @@ frappe.ui.form.on('Payment Entry', {
} else {
c.exchange_rate = 1;
}
if (in_list(['Sales Invoice', 'Purchase Invoice', "Expense Claim", "Fees"], d.reference_doctype)){
if (in_list(frm.events.get_invoice_doctypes(frm), d.reference_doctype)){
c.due_date = d.due_date;
}
});
@ -776,6 +769,14 @@ frappe.ui.form.on('Payment Entry', {
});
},
get_order_doctypes: function(frm) {
return ["Sales Order", "Purchase Order"];
},
get_invoice_doctypes: function(frm) {
return ["Sales Invoice", "Purchase Invoice"];
},
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
var total_positive_outstanding_including_order = 0;
var total_negative_outstanding = 0;
@ -946,14 +947,6 @@ frappe.ui.form.on('Payment Entry', {
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
return false;
}
if(frm.doc.party_type=="Employee" &&
!in_list(["Expense Claim", "Journal Entry"], row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
return false;
}
}
if (row) {

View File

@ -293,14 +293,7 @@ class PaymentEntry(AccountsController):
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
def validate_reference_documents(self):
if self.party_type == "Customer":
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder":
valid_reference_doctypes = "Journal Entry"
valid_reference_doctypes = self.get_valid_reference_doctypes()
for d in self.get("references"):
if not d.allocated_amount:
@ -326,7 +319,7 @@ class PaymentEntry(AccountsController):
else:
self.validate_journal_entry()
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
@ -352,6 +345,16 @@ class PaymentEntry(AccountsController):
if ref_doc.docstatus != 1:
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
return ("Journal Entry",)
def validate_paid_invoices(self):
no_oustanding_refs = {}
@ -974,12 +977,7 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in (
"Sales Order",
"Purchase Order",
"Employee Advance",
"Gratuity",
):
if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
def on_recurring(self, reference_doc, auto_repeat_doc):
@ -1235,7 +1233,7 @@ def get_outstanding_reference_documents(args):
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
@ -1562,20 +1560,17 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
total_amount = outstanding_amount = exchange_rate = bill_no = None
total_amount = outstanding_amount = exchange_rate = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
ref_doc.company
)
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
@ -1585,16 +1580,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
elif ref_doc.doctype == "Gratuity":
total_amount = ref_doc.amount
if not total_amount:
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
@ -1607,26 +1594,12 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = (
flt(ref_doc.get("total_sanctioned_amount"))
+ flt(ref_doc.get("total_taxes_and_charges"))
- flt(ref_doc.get("total_amount_reimbursed"))
- flt(ref_doc.get("total_advance_amount"))
)
elif reference_doctype == "Employee Advance":
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
elif reference_doctype == "Gratuity":
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
# Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
@ -1637,114 +1610,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"total_amount": flt(total_amount),
"outstanding_amount": flt(outstanding_amount),
"exchange_rate": flt(exchange_rate),
"bill_no": bill_no,
"bill_no": ref_doc.get("bill_no"),
}
)
def get_amounts_based_on_reference_doctype(
reference_doctype, ref_doc, party_account_currency, company_currency, reference_name
):
total_amount = outstanding_amount = exchange_rate = None
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
return total_amount, outstanding_amount, exchange_rate
def get_amounts_based_on_ref_doc(
reference_doctype, ref_doc, party_account_currency, company_currency
):
total_amount = outstanding_amount = exchange_rate = None
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(
party_account_currency, ref_doc
)
if not total_amount:
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc
)
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
)
return total_amount, outstanding_amount, exchange_rate, bill_no
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
return total_amount, exchange_rate
def get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc
):
exchange_rate = None
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
return total_amount, exchange_rate
def get_bill_no_and_update_amounts(
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
):
outstanding_amount = bill_no = None
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = (
flt(ref_doc.get("total_sanctioned_amount"))
+ flt(ref_doc.get("total_taxes_and_charges"))
- flt(ref_doc.get("total_amount_reimbursed"))
- flt(ref_doc.get("total_advance_amount"))
)
elif reference_doctype == "Employee Advance":
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
return outstanding_amount, exchange_rate, bill_no
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
reference_doc = None
@ -1863,8 +1733,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.set_missing_values()
if party_account and bank:
if dt == "Employee Advance":
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
@ -1899,8 +1767,6 @@ def set_party_type(dt):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
party_type = "Employee"
return party_type
@ -1911,12 +1777,6 @@ def set_party_account(dt, dn, doc, party_type):
party_account = doc.credit_to
elif dt == "Fees":
party_account = doc.receivable_account
elif dt == "Employee Advance":
party_account = doc.advance_account
elif dt == "Expense Claim":
party_account = doc.payable_account
elif dt == "Gratuity":
party_account = doc.payable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@ -1951,24 +1811,12 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.grand_total - doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = flt(doc.advance_amount)
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
if party_account_currency != doc.currency:
grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
elif dt == "Gratuity":
grand_total = doc.amount
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
@ -1990,8 +1838,6 @@ def set_paid_amount_and_received_amount(
received_amount = bank_amount
else:
received_amount = paid_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
received_amount = paid_amount * doc.get("exchange_rate", 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
@ -1999,8 +1845,6 @@ def set_paid_amount_and_received_amount(
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
paid_amount = received_amount * doc.get("exchange_rate", 1)
return paid_amount, received_amount

View File

@ -472,6 +472,19 @@ payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account
communication_doctypes = ["Customer", "Supplier"]
advance_payment_doctypes = ["Sales Order", "Purchase Order"]
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
period_closing_doctypes = [
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
accounting_dimension_doctypes = [
"GL Entry",
"Payment Ledger Entry",
@ -479,12 +492,8 @@ accounting_dimension_doctypes = [
"Purchase Invoice",
"Payment Entry",
"Asset",
"Expense Claim",
"Expense Claim Detail",
"Expense Taxes and Charges",
"Stock Entry",
"Budget",
"Payroll Entry",
"Delivery Note",
"Sales Invoice Item",
"Purchase Invoice Item",