feat: multi-currency payroll (#23519)
* feat: multi-currency payroll * fix: refactor and added conditions * fix: uncommented code * style: removed comments * fix: missing argument * style: styling changes * fix: test cases * Update asset_value_adjustment.py * patch: update columns * style: formating * style: formatting * fix: 1st review * fix: refactor * Revert "fix: refactor" This reverts commit eca0e17d11a192d60f249b2af992971c625aec46. reverting to previous state * Revert "fix: 1st review" This reverts commit 7eac48b102157df4353598f73b2ea97308af436a. reverting before 1st review * fix: 2nd review changes * fix: test cases * fix: added call to fetch exchange rate * fix: remove unnecessary code * fix: refactor * fix: refactor patch * fix: refactor * fix: refactor * fix: clear test data * fix: slider * feat: multi-currency payroll * fix: refactor and added conditions * fix: uncommented code * style: removed comments * fix: missing argument * style: styling changes * fix: test cases * patch: update columns * Update asset_value_adjustment.py * style: formating * style: formatting * fix: 1st review * fix: refactor * Revert "fix: refactor" This reverts commit eca0e17d11a192d60f249b2af992971c625aec46. reverting to previous state * Revert "fix: 1st review" This reverts commit 7eac48b102157df4353598f73b2ea97308af436a. reverting before 1st review * fix: 2nd review changes * fix: test cases * fix: added call to fetch exchange rate * fix: remove unnecessary code * fix: refactor * fix: refactor patch * fix: refactor * fix: refactor * fix: clear test data * fix: slider * feat: Added company field in leave encashment and employee benefit * refactor: Refactored multi-currency payroll patch * fix: currency column in salary register * refactor: Refactored code for making bank and return entry against employee advance * fix: minor cleanup * fix: fixed translation * fix: removed salary component type * fix: fixed sider issues * fix: translation and slider * style: formatted msg * fix: fixed slider * fix: travis * fix: refactor * fix: slider * fix: slider * fix: slider * fix: travis * fix: patch * fix: patch * fix: travis * fix: travis * fix: travis * fix: travis * fix: travis * fix: travis * fix: re-run travis * fix: rerun travis * fix: rerun travis * fix: rerun travis * fix: travis rerun * fix: increased throttle_user_limit from 60 to 100 * fix: patch * fix: patch * fix: assign payroll payable account as default payroll payable account in SSA * fix: removed debugger * fix: slider Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
8b6c58d560
commit
ccf5dc66e2
@ -9,5 +9,6 @@
|
||||
"root_login": "root",
|
||||
"root_password": "travis",
|
||||
"host_name": "http://test_site:8000",
|
||||
"install_apps": ["erpnext"]
|
||||
"install_apps": ["erpnext"],
|
||||
"throttle_user_limit": 100
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
return{
|
||||
filters: [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'company', '=', d.company]
|
||||
]
|
||||
}
|
||||
});
|
||||
frappe.ui.form.on('Mode of Payment', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: [
|
||||
['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
|
||||
['Account', 'is_group', '=', 0],
|
||||
['Account', 'company', '=', d.company]
|
||||
]
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
@ -202,17 +202,32 @@ class PaymentEntry(AccountsController):
|
||||
# if account_type not in account_types:
|
||||
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
|
||||
|
||||
def set_exchange_rate(self):
|
||||
def set_exchange_rate(self, ref_doc=None):
|
||||
self.set_source_exchange_rate(ref_doc)
|
||||
self.set_target_exchange_rate(ref_doc)
|
||||
|
||||
def set_source_exchange_rate(self, ref_doc=None):
|
||||
if self.paid_from and not self.source_exchange_rate:
|
||||
if self.paid_from_account_currency == self.company_currency:
|
||||
self.source_exchange_rate = 1
|
||||
else:
|
||||
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
if ref_doc:
|
||||
if self.paid_from_account_currency == ref_doc.currency:
|
||||
self.source_exchange_rate = ref_doc.get("exchange_rate")
|
||||
|
||||
if not self.source_exchange_rate:
|
||||
self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
|
||||
def set_target_exchange_rate(self, ref_doc=None):
|
||||
if self.paid_to and not self.target_exchange_rate:
|
||||
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
if ref_doc:
|
||||
if self.paid_to_account_currency == ref_doc.currency:
|
||||
self.target_exchange_rate = ref_doc.get("exchange_rate")
|
||||
|
||||
if not self.target_exchange_rate:
|
||||
self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
|
||||
self.company_currency, self.posting_date)
|
||||
|
||||
def validate_mandatory(self):
|
||||
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
|
||||
@ -282,9 +297,10 @@ class PaymentEntry(AccountsController):
|
||||
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
||||
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
|
||||
If this is undesirable please cancel the corresponding Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
|
||||
frappe.msgprint(
|
||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
|
||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||
title=_("Warning"), indicator="orange")
|
||||
|
||||
|
||||
@ -909,22 +925,24 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
exchange_rate = 1
|
||||
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if party_account_currency == company_currency:
|
||||
if ref_doc.doctype == "Expense Claim":
|
||||
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
|
||||
else:
|
||||
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)
|
||||
if not total_amount:
|
||||
if party_account_currency == company_currency:
|
||||
total_amount = ref_doc.base_grand_total
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
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
|
||||
# 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)
|
||||
|
||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
bill_no = ref_doc.get("bill_no")
|
||||
@ -932,11 +950,15 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
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 = ref_doc.advance_amount - flt(ref_doc.paid_amount)
|
||||
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)
|
||||
else:
|
||||
# Get the exchange rate based on the posting date of the ref doc
|
||||
# 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)
|
||||
|
||||
@ -948,102 +970,104 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
"bill_no": 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
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
|
||||
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
|
||||
party_type = "Customer"
|
||||
elif dt in ("Purchase Invoice", "Purchase Order"):
|
||||
party_type = "Supplier"
|
||||
elif dt in ("Expense Claim", "Employee Advance"):
|
||||
party_type = "Employee"
|
||||
elif dt in ("Fees"):
|
||||
party_type = "Student"
|
||||
|
||||
# party account
|
||||
if dt == "Sales Invoice":
|
||||
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
||||
elif dt == "Purchase Invoice":
|
||||
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
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
|
||||
if dt not in ("Sales Invoice", "Purchase Invoice"):
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
else:
|
||||
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
||||
|
||||
# payment type
|
||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
payment_type = "Pay"
|
||||
|
||||
# amounts
|
||||
grand_total = outstanding_amount = 0
|
||||
if party_amount:
|
||||
grand_total = outstanding_amount = party_amount
|
||||
elif dt in ("Sales Invoice", "Purchase Invoice"):
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = doc.base_rounded_total or doc.base_grand_total
|
||||
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 = doc.advance_amount
|
||||
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
|
||||
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
|
||||
else:
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||
else:
|
||||
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
||||
outstanding_amount = grand_total - flt(doc.advance_paid)
|
||||
party_type = set_party_type(dt)
|
||||
party_account = set_party_account(dt, dn, doc, party_type)
|
||||
party_account_currency = set_party_account_currency(dt, party_account, doc)
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
|
||||
|
||||
# bank or cash
|
||||
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account)
|
||||
bank = get_bank_cash_account(doc, bank_account)
|
||||
|
||||
if not bank:
|
||||
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account)
|
||||
|
||||
paid_amount = received_amount = 0
|
||||
if party_account_currency == bank.account_currency:
|
||||
paid_amount = received_amount = abs(outstanding_amount)
|
||||
elif payment_type == "Receive":
|
||||
paid_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
received_amount = bank_amount
|
||||
else:
|
||||
received_amount = paid_amount * doc.get('conversion_rate', 1)
|
||||
else:
|
||||
received_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
paid_amount = bank_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)
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = payment_type
|
||||
@ -1115,10 +1139,120 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
pe.setup_party_account_field()
|
||||
pe.set_missing_values()
|
||||
if party_account and bank:
|
||||
pe.set_exchange_rate()
|
||||
if dt == "Employee Advance":
|
||||
reference_doc = doc
|
||||
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||
pe.set_amounts()
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account)
|
||||
|
||||
if not bank:
|
||||
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
|
||||
account=bank_account)
|
||||
|
||||
return bank
|
||||
|
||||
def set_party_type(dt):
|
||||
if dt in ("Sales Invoice", "Sales Order", "Dunning"):
|
||||
party_type = "Customer"
|
||||
elif dt in ("Purchase Invoice", "Purchase Order"):
|
||||
party_type = "Supplier"
|
||||
elif dt in ("Expense Claim", "Employee Advance"):
|
||||
party_type = "Employee"
|
||||
elif dt in ("Fees"):
|
||||
party_type = "Student"
|
||||
return party_type
|
||||
|
||||
def set_party_account(dt, dn, doc, party_type):
|
||||
if dt == "Sales Invoice":
|
||||
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
||||
elif dt == "Purchase Invoice":
|
||||
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
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
return party_account
|
||||
|
||||
def set_party_account_currency(dt, party_account, doc):
|
||||
if dt not in ("Sales Invoice", "Purchase Invoice"):
|
||||
party_account_currency = get_account_currency(party_account)
|
||||
else:
|
||||
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
|
||||
return party_account_currency
|
||||
|
||||
def set_payment_type(dt, doc):
|
||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
payment_type = "Pay"
|
||||
return payment_type
|
||||
|
||||
def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
|
||||
grand_total = outstanding_amount = 0
|
||||
if party_amount:
|
||||
grand_total = outstanding_amount = party_amount
|
||||
elif dt in ("Sales Invoice", "Purchase Invoice"):
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = doc.base_rounded_total or doc.base_grand_total
|
||||
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
|
||||
else:
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||
else:
|
||||
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
||||
outstanding_amount = grand_total - flt(doc.advance_paid)
|
||||
return grand_total, outstanding_amount
|
||||
|
||||
def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
|
||||
paid_amount = received_amount = 0
|
||||
if party_account_currency == bank.account_currency:
|
||||
paid_amount = received_amount = abs(outstanding_amount)
|
||||
elif payment_type == "Receive":
|
||||
paid_amount = abs(outstanding_amount)
|
||||
if bank_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:
|
||||
paid_amount = bank_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
|
||||
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
|
@ -1,92 +1,38 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-07-27 17:24:24.956896",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2016-07-27 17:24:24.956896",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
||||
"fieldname": "default_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Default Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-09-02 07:49:06.567389",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Salary Component Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-18 17:57:57.110257",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Salary Component Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -134,7 +134,7 @@ def setup_employee():
|
||||
salary_component = frappe.get_doc('Salary Component', d.name)
|
||||
salary_component.append('accounts', dict(
|
||||
company=erpnext.get_default_company(),
|
||||
default_account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
|
||||
account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
|
||||
))
|
||||
salary_component.save()
|
||||
|
||||
|
@ -15,11 +15,16 @@ frappe.ui.form.on('Employee Advance', {
|
||||
});
|
||||
|
||||
frm.set_query("advance_account", function() {
|
||||
if (!frm.doc.employee) {
|
||||
frappe.msgprint(__("Please select employee first"));
|
||||
}
|
||||
var company_currency = erpnext.get_currency(frm.doc.company);
|
||||
return {
|
||||
filters: {
|
||||
"root_type": "Asset",
|
||||
"is_group": 0,
|
||||
"company": frm.doc.company
|
||||
"company": frm.doc.company,
|
||||
"account_currency": ["in", [frm.doc.currency, company_currency]],
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -63,7 +68,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
}, __('Create'));
|
||||
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
|
||||
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||
frm.events.make_deduction_via_additional_salary(frm)
|
||||
frm.events.make_deduction_via_additional_salary(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
@ -127,7 +132,9 @@ frappe.ui.form.on('Employee Advance', {
|
||||
'employee_advance_name': frm.doc.name,
|
||||
'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount),
|
||||
'advance_account': frm.doc.advance_account,
|
||||
'mode_of_payment': frm.doc.mode_of_payment
|
||||
'mode_of_payment': frm.doc.mode_of_payment,
|
||||
'currency': frm.doc.currency,
|
||||
'exchange_rate': frm.doc.exchange_rate
|
||||
},
|
||||
callback: function(r) {
|
||||
const doclist = frappe.model.sync(r.message);
|
||||
@ -138,16 +145,72 @@ frappe.ui.form.on('Employee Advance', {
|
||||
|
||||
employee: function (frm) {
|
||||
if (frm.doc.employee) {
|
||||
return frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
|
||||
args: {
|
||||
"employee": frm.doc.employee,
|
||||
"posting_date": frm.doc.posting_date
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("pending_amount",r.message);
|
||||
}
|
||||
});
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('get_pending_amount')
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
get_pending_amount: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
|
||||
args: {
|
||||
"employee": frm.doc.employee,
|
||||
"posting_date": frm.doc.posting_date
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("pending_amount", r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
currency: function(frm) {
|
||||
var from_currency = frm.doc.currency;
|
||||
var company_currency;
|
||||
if (!frm.doc.company) {
|
||||
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
|
||||
} else {
|
||||
company_currency = erpnext.get_currency(frm.doc.company);
|
||||
}
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.set_exchange_rate(frm, from_currency, company_currency);
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, from_currency, company_currency) {
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -13,6 +13,8 @@
|
||||
"department",
|
||||
"column_break_4",
|
||||
"posting_date",
|
||||
"currency",
|
||||
"exchange_rate",
|
||||
"repay_unclaimed_amount_from_salary",
|
||||
"section_break_8",
|
||||
"purpose",
|
||||
@ -91,7 +93,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Advance Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -99,7 +101,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -107,7 +109,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Claimed Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -161,7 +163,7 @@
|
||||
"fieldname": "return_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Returned Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -175,13 +177,31 @@
|
||||
"fieldname": "pending_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Pending Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "currency",
|
||||
"fieldname": "exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate",
|
||||
"precision": "9",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-12 12:42:39.833818",
|
||||
"modified": "2020-11-25 12:01:55.980721",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Advance",
|
||||
|
@ -19,7 +19,6 @@ class EmployeeAdvance(Document):
|
||||
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.validate_employee_advance_account()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ('GL Entry')
|
||||
@ -38,16 +37,9 @@ class EmployeeAdvance(Document):
|
||||
elif self.docstatus == 2:
|
||||
self.status = "Cancelled"
|
||||
|
||||
def validate_employee_advance_account(self):
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
if (self.advance_account and
|
||||
company_currency != frappe.db.get_value('Account', self.advance_account, 'account_currency')):
|
||||
frappe.throw(_("Advance account currency should be same as company currency {0}")
|
||||
.format(company_currency))
|
||||
|
||||
def set_total_advance_paid(self):
|
||||
paid_amount = frappe.db.sql("""
|
||||
select ifnull(sum(debit_in_account_currency), 0) as paid_amount
|
||||
select ifnull(sum(debit), 0) as paid_amount
|
||||
from `tabGL Entry`
|
||||
where against_voucher_type = 'Employee Advance'
|
||||
and against_voucher = %s
|
||||
@ -56,7 +48,7 @@ class EmployeeAdvance(Document):
|
||||
""", (self.name, self.employee), as_dict=1)[0].paid_amount
|
||||
|
||||
return_amount = frappe.db.sql("""
|
||||
select name, ifnull(sum(credit_in_account_currency), 0) as return_amount
|
||||
select ifnull(sum(credit), 0) as return_amount
|
||||
from `tabGL Entry`
|
||||
where against_voucher_type = 'Employee Advance'
|
||||
and voucher_type != 'Expense Claim'
|
||||
@ -65,6 +57,11 @@ class EmployeeAdvance(Document):
|
||||
and party = %s
|
||||
""", (self.name, self.employee), as_dict=1)[0].return_amount
|
||||
|
||||
if paid_amount != 0:
|
||||
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
|
||||
if return_amount != 0:
|
||||
return_amount = flt(return_amount) / flt(self.exchange_rate)
|
||||
|
||||
if flt(paid_amount) > self.advance_amount:
|
||||
frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
|
||||
EmployeeAdvanceOverPayment)
|
||||
@ -107,16 +104,27 @@ def make_bank_entry(dt, dn):
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
|
||||
mode_of_payment=doc.mode_of_payment)
|
||||
if not payment_account:
|
||||
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
||||
|
||||
advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
|
||||
|
||||
advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
|
||||
|
||||
paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.posting_date = nowdate()
|
||||
je.voucher_type = 'Bank Entry'
|
||||
je.company = doc.company
|
||||
je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
|
||||
je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
|
||||
|
||||
je.append("accounts", {
|
||||
"account": doc.advance_account,
|
||||
"debit_in_account_currency": flt(doc.advance_amount),
|
||||
"account_currency": advance_account_currency,
|
||||
"exchange_rate": flt(advance_exchange_rate),
|
||||
"debit_in_account_currency": flt(advance_amount),
|
||||
"reference_type": "Employee Advance",
|
||||
"reference_name": doc.name,
|
||||
"party_type": "Employee",
|
||||
@ -128,19 +136,41 @@ def make_bank_entry(dt, dn):
|
||||
je.append("accounts", {
|
||||
"account": payment_account.account,
|
||||
"cost_center": erpnext.get_default_cost_center(doc.company),
|
||||
"credit_in_account_currency": flt(doc.advance_amount),
|
||||
"credit_in_account_currency": flt(paying_amount),
|
||||
"account_currency": payment_account.account_currency,
|
||||
"account_type": payment_account.account_type
|
||||
"account_type": payment_account.account_type,
|
||||
"exchange_rate": flt(paying_exchange_rate)
|
||||
})
|
||||
|
||||
return je.as_dict()
|
||||
|
||||
def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
|
||||
if advance_account_currency != doc.currency:
|
||||
advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
|
||||
advance_exchange_rate = 1
|
||||
else:
|
||||
advance_amount = doc.advance_amount
|
||||
advance_exchange_rate = doc.exchange_rate
|
||||
|
||||
return advance_amount, advance_exchange_rate
|
||||
|
||||
def get_paying_amount_paying_exchange_rate(payment_account, doc):
|
||||
if payment_account.account_currency != doc.currency:
|
||||
paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
|
||||
paying_exchange_rate = 1
|
||||
else:
|
||||
paying_amount = doc.advance_amount
|
||||
paying_exchange_rate = doc.exchange_rate
|
||||
|
||||
return paying_amount, paying_exchange_rate
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_return_through_additional_salary(doc):
|
||||
import json
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
additional_salary = frappe.new_doc('Additional Salary')
|
||||
additional_salary.employee = doc.employee
|
||||
additional_salary.currency = doc.currency
|
||||
additional_salary.amount = doc.paid_amount - doc.claimed_amount
|
||||
additional_salary.company = doc.company
|
||||
additional_salary.ref_doctype = doc.doctype
|
||||
@ -149,26 +179,28 @@ def create_return_through_additional_salary(doc):
|
||||
return additional_salary
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, mode_of_payment=None):
|
||||
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||
|
||||
mode_of_payment_type = ''
|
||||
if mode_of_payment:
|
||||
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
|
||||
if mode_of_payment_type not in ["Cash", "Bank"]:
|
||||
# if mode of payment is General then it unset the type
|
||||
mode_of_payment_type = None
|
||||
|
||||
def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, currency, exchange_rate, mode_of_payment=None):
|
||||
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||
if not bank_cash_account:
|
||||
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
|
||||
|
||||
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
|
||||
|
||||
je = frappe.new_doc('Journal Entry')
|
||||
je.posting_date = nowdate()
|
||||
# if mode of payment is Bank then voucher type is Bank Entry
|
||||
je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
|
||||
je.voucher_type = get_voucher_type(mode_of_payment)
|
||||
je.company = company
|
||||
je.remark = 'Return against Employee Advance: ' + employee_advance_name
|
||||
je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
|
||||
|
||||
advance_account_amount = flt(return_amount) if advance_account_currency==currency \
|
||||
else flt(return_amount) * flt(exchange_rate)
|
||||
|
||||
je.append('accounts', {
|
||||
'account': advance_account,
|
||||
'credit_in_account_currency': return_amount,
|
||||
'credit_in_account_currency': advance_account_amount,
|
||||
'account_currency': advance_account_currency,
|
||||
'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
|
||||
'reference_type': 'Employee Advance',
|
||||
'reference_name': employee_advance_name,
|
||||
'party_type': 'Employee',
|
||||
@ -176,13 +208,25 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
|
||||
'is_advance': 'Yes'
|
||||
})
|
||||
|
||||
bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
|
||||
else flt(return_amount) * flt(exchange_rate)
|
||||
|
||||
je.append("accounts", {
|
||||
"account": return_account.account,
|
||||
"debit_in_account_currency": return_amount,
|
||||
"account_currency": return_account.account_currency,
|
||||
"account_type": return_account.account_type
|
||||
"account": bank_cash_account.account,
|
||||
"debit_in_account_currency": bank_amount,
|
||||
"account_currency": bank_cash_account.account_currency,
|
||||
"account_type": bank_cash_account.account_type,
|
||||
"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
|
||||
})
|
||||
|
||||
return je.as_dict()
|
||||
|
||||
def get_voucher_type(mode_of_payment=None):
|
||||
voucher_type = "Cash Entry"
|
||||
|
||||
if mode_of_payment:
|
||||
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
|
||||
if mode_of_payment_type == "Bank":
|
||||
voucher_type = "Bank Entry"
|
||||
|
||||
return voucher_type
|
@ -3,15 +3,17 @@
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import frappe, erpnext
|
||||
import unittest
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.hr.doctype.employee_advance.employee_advance import make_bank_entry
|
||||
from erpnext.hr.doctype.employee_advance.employee_advance import EmployeeAdvanceOverPayment
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
class TestEmployeeAdvance(unittest.TestCase):
|
||||
def test_paid_amount_and_status(self):
|
||||
advance = make_employee_advance()
|
||||
employee_name = make_employee("_T@employe.advance")
|
||||
advance = make_employee_advance(employee_name)
|
||||
|
||||
journal_entry = make_payment_entry(advance)
|
||||
journal_entry.submit()
|
||||
@ -33,11 +35,13 @@ def make_payment_entry(advance):
|
||||
|
||||
return journal_entry
|
||||
|
||||
def make_employee_advance():
|
||||
def make_employee_advance(employee_name):
|
||||
doc = frappe.new_doc("Employee Advance")
|
||||
doc.employee = "_T-Employee-00001"
|
||||
doc.employee = employee_name
|
||||
doc.company = "_Test company"
|
||||
doc.purpose = "For site visit"
|
||||
doc.currency = erpnext.get_company_currency("_Test company")
|
||||
doc.exchange_rate = 1
|
||||
doc.advance_amount = 1000
|
||||
doc.posting_date = nowdate()
|
||||
doc.advance_account = "_Test Employee Advance - _TC"
|
||||
|
@ -7,6 +7,7 @@ import unittest
|
||||
from frappe.utils import random_string, nowdate
|
||||
from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
test_records = frappe.get_test_records('Expense Claim')
|
||||
test_dependencies = ['Employee']
|
||||
@ -126,6 +127,9 @@ def generate_taxes():
|
||||
|
||||
def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
|
||||
employee = frappe.db.get_value("Employee", {"status": "Active"})
|
||||
if not employee:
|
||||
employee = make_employee("test_employee@expense_claim.com", company=company)
|
||||
|
||||
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
|
||||
expense_claim = {
|
||||
"doctype": "Expense Claim",
|
||||
|
@ -71,9 +71,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"oldfieldname": "tax_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
@ -81,9 +79,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Total",
|
||||
"oldfieldname": "total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -106,7 +102,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-11 19:01:26.611758",
|
||||
"modified": "2020-09-23 20:27:36.027728",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Taxes and Charges",
|
||||
|
@ -22,7 +22,12 @@ frappe.ui.form.on('Leave Encashment', {
|
||||
}
|
||||
},
|
||||
employee: function(frm) {
|
||||
frm.trigger("get_leave_details_for_encashment");
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('get_leave_details_for_encashment')
|
||||
]);
|
||||
}
|
||||
},
|
||||
leave_type: function(frm) {
|
||||
frm.trigger("get_leave_details_for_encashment");
|
||||
@ -40,5 +45,20 @@ frappe.ui.form.on('Leave Encashment', {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -12,6 +12,7 @@
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"company",
|
||||
"column_break_4",
|
||||
"leave_type",
|
||||
"leave_allocation",
|
||||
@ -19,9 +20,11 @@
|
||||
"encashable_days",
|
||||
"amended_from",
|
||||
"payroll",
|
||||
"encashment_amount",
|
||||
"encashment_date",
|
||||
"additional_salary"
|
||||
"additional_salary",
|
||||
"column_break_14",
|
||||
"currency",
|
||||
"encashment_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -109,6 +112,7 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Encashment Amount",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -124,11 +128,34 @@
|
||||
"no_copy": 1,
|
||||
"options": "Additional Salary",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-16 11:51:57.732223",
|
||||
"modified": "2020-11-25 11:56:06.777241",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Encashment",
|
||||
|
@ -16,10 +16,16 @@ class LeaveEncashment(Document):
|
||||
def validate(self):
|
||||
set_employee_name(self)
|
||||
self.get_leave_details_for_encashment()
|
||||
self.validate_salary_structure()
|
||||
|
||||
if not self.encashment_date:
|
||||
self.encashment_date = getdate(nowdate())
|
||||
|
||||
def validate_salary_structure(self):
|
||||
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
||||
frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
|
||||
|
||||
|
||||
def before_submit(self):
|
||||
if self.encashment_amount <= 0:
|
||||
frappe.throw(_("You can only submit Leave Encashment for a valid encashment amount"))
|
||||
@ -30,6 +36,7 @@ class LeaveEncashment(Document):
|
||||
additional_salary = frappe.new_doc("Additional Salary")
|
||||
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
|
||||
additional_salary.employee = self.employee
|
||||
additional_salary.currency = self.currency
|
||||
earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
|
||||
if not earning_component:
|
||||
frappe.throw(_("Please set Earning Component for Leave type: {0}.").format(self.leave_type))
|
||||
|
@ -48,6 +48,10 @@ class TestLeaveEncashment(unittest.TestCase):
|
||||
frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
|
||||
frappe.db.sql("delete from `tab%s`" % dt)
|
||||
|
||||
def test_leave_balance_value_and_amount(self):
|
||||
frappe.db.sql('''delete from `tabLeave Encashment`''')
|
||||
leave_encashment = frappe.get_doc(dict(
|
||||
@ -55,7 +59,8 @@ class TestLeaveEncashment(unittest.TestCase):
|
||||
employee=self.employee,
|
||||
leave_type="_Test Leave Type Encashment",
|
||||
leave_period=self.leave_period.name,
|
||||
payroll_date=today()
|
||||
payroll_date=today(),
|
||||
currency="INR"
|
||||
)).insert()
|
||||
|
||||
self.assertEqual(leave_encashment.leave_balance, 10)
|
||||
@ -75,7 +80,8 @@ class TestLeaveEncashment(unittest.TestCase):
|
||||
employee=self.employee,
|
||||
leave_type="_Test Leave Type Encashment",
|
||||
leave_period=self.leave_period.name,
|
||||
payroll_date=today()
|
||||
payroll_date=today(),
|
||||
currency="INR"
|
||||
)).insert()
|
||||
|
||||
leave_encashment.submit()
|
||||
|
@ -26,11 +26,11 @@
|
||||
"disbursed_amount",
|
||||
"column_break_11",
|
||||
"maximum_loan_amount",
|
||||
"is_term_loan",
|
||||
"repayment_method",
|
||||
"repayment_periods",
|
||||
"monthly_repayment_amount",
|
||||
"repayment_start_date",
|
||||
"is_term_loan",
|
||||
"account_info",
|
||||
"mode_of_payment",
|
||||
"payment_account",
|
||||
|
@ -13,6 +13,8 @@ from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calcul
|
||||
|
||||
class Loan(AccountsController):
|
||||
def validate(self):
|
||||
if self.applicant_type == 'Employee' and self.repay_from_salary:
|
||||
validate_employee_currency_with_company_currency(self.applicant, self.company)
|
||||
self.set_loan_amount()
|
||||
self.validate_loan_amount()
|
||||
self.set_missing_fields()
|
||||
@ -329,5 +331,14 @@ def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, a
|
||||
|
||||
return unpledge_request
|
||||
|
||||
|
||||
|
||||
def validate_employee_currency_with_company_currency(applicant, company):
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
|
||||
if not applicant:
|
||||
frappe.throw(_("Please select Applicant"))
|
||||
if not company:
|
||||
frappe.throw(_("Please select Company"))
|
||||
employee_currency = get_employee_currency(applicant)
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
if employee_currency != company_currency:
|
||||
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
|
||||
.format(applicant, employee_currency))
|
||||
|
@ -19,6 +19,7 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
|
||||
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
|
||||
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
|
||||
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
class TestLoan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -44,6 +45,7 @@ class TestLoan(unittest.TestCase):
|
||||
create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
|
||||
|
||||
self.applicant1 = make_employee("robert_loan@loan.com")
|
||||
make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR')
|
||||
if not frappe.db.exists("Customer", "_Test Loan Customer"):
|
||||
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
|
||||
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee, make_salary_structure
|
||||
from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan_accounts
|
||||
|
||||
class TestLoanApplication(unittest.TestCase):
|
||||
@ -14,6 +14,7 @@ class TestLoanApplication(unittest.TestCase):
|
||||
create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
|
||||
'Interest Income Account - _TC', 'Penalty Income Account - _TC', 'Repay Over Number of Periods', 18)
|
||||
self.applicant = make_employee("kate_loan@loan.com", "_Test Company")
|
||||
make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant, currency='INR')
|
||||
self.create_loan_application()
|
||||
|
||||
def create_loan_application(self):
|
||||
@ -29,7 +30,6 @@ class TestLoanApplication(unittest.TestCase):
|
||||
})
|
||||
loan_application.insert()
|
||||
|
||||
|
||||
def test_loan_totals(self):
|
||||
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
|
||||
|
||||
|
@ -732,7 +732,9 @@ erpnext.patches.v13_0.set_youtube_video_id
|
||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
||||
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
||||
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||
|
@ -8,8 +8,8 @@ from frappe.utils import getdate
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import DuplicateAssignment
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('Payroll', 'doctype', 'salary_structure')
|
||||
frappe.reload_doc("Payroll", "doctype", "salary_structure_assignment")
|
||||
frappe.reload_doc('Payroll', 'doctype', 'Salary Structure')
|
||||
frappe.reload_doc("Payroll", "doctype", "Salary Structure Assignment")
|
||||
frappe.db.sql("""
|
||||
delete from `tabSalary Structure Assignment`
|
||||
where salary_structure in (select name from `tabSalary Structure` where is_active='No' or docstatus!=1)
|
||||
@ -33,6 +33,13 @@ def execute():
|
||||
AND employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')
|
||||
""".format(cols), as_dict=1)
|
||||
|
||||
all_companies = frappe.db.get_all("Company", fields=["name", "default_currency"])
|
||||
for d in all_companies:
|
||||
company = d.name
|
||||
company_currency = d.default_currency
|
||||
|
||||
frappe.db.sql("""update `tabSalary Structure` set currency = %s where company=%s""", (company_currency, company))
|
||||
|
||||
for d in ss_details:
|
||||
try:
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", d.employee,
|
||||
@ -42,6 +49,7 @@ def execute():
|
||||
from_date = joining_date
|
||||
elif relieving_date and getdate(from_date) > relieving_date:
|
||||
continue
|
||||
company_currency = frappe.db.get_value('Company', d.company, 'default_currency')
|
||||
|
||||
s = frappe.new_doc("Salary Structure Assignment")
|
||||
s.employee = d.employee
|
||||
@ -52,6 +60,7 @@ def execute():
|
||||
s.base = d.get("base")
|
||||
s.variable = d.get("variable")
|
||||
s.company = d.company
|
||||
s.currency = company_currency
|
||||
|
||||
# to migrate the data of the old employees
|
||||
s.flags.old_employee = True
|
||||
|
136
erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
Normal file
136
erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
Normal file
@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
def execute():
|
||||
|
||||
frappe.reload_doc('Accounts', 'doctype', 'Salary Component Account')
|
||||
if frappe.db.has_column('Salary Component Account', 'default_account'):
|
||||
rename_field("Salary Component Account", "default_account", "account")
|
||||
|
||||
doctype_list = [
|
||||
{
|
||||
'module':'HR',
|
||||
'doctype':'Employee Advance'
|
||||
},
|
||||
{
|
||||
'module':'HR',
|
||||
'doctype':'Leave Encashment'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Additional Salary'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Employee Benefit Application'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Employee Benefit Claim'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Employee Incentive'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Employee Tax Exemption Declaration'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Employee Tax Exemption Proof Submission'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Income Tax Slab'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Payroll Entry'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Retention Bonus'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Salary Structure'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Salary Structure Assignment'
|
||||
},
|
||||
{
|
||||
'module':'Payroll',
|
||||
'doctype':'Salary Slip'
|
||||
},
|
||||
]
|
||||
|
||||
for item in doctype_list:
|
||||
frappe.reload_doc(item['module'], 'doctype', item['doctype'])
|
||||
|
||||
# update company in employee advance based on employee company
|
||||
for dt in ['Employee Incentive', 'Leave Encashment', 'Employee Benefit Application', 'Employee Benefit Claim']:
|
||||
frappe.db.sql("""
|
||||
update `tab{doctype}`
|
||||
set company = (select company from tabEmployee where name=`tab{doctype}`.employee)
|
||||
""".format(doctype=dt))
|
||||
|
||||
# update exchange rate for employee advance
|
||||
frappe.db.sql("update `tabEmployee Advance` set exchange_rate=1")
|
||||
|
||||
# get all companies and it's currency
|
||||
all_companies = frappe.db.get_all("Company", fields=["name", "default_currency", "default_payroll_payable_account"])
|
||||
for d in all_companies:
|
||||
company = d.name
|
||||
company_currency = d.default_currency
|
||||
default_payroll_payable_account = d.default_payroll_payable_account
|
||||
|
||||
if not default_payroll_payable_account:
|
||||
default_payroll_payable_account = frappe.db.get_value("Account",
|
||||
{"account_name": _("Payroll Payable"), "company": company, "account_currency": company_currency, "is_group": 0})
|
||||
|
||||
# update currency in following doctypes based on company currency
|
||||
doctypes_for_currency = ['Employee Advance', 'Leave Encashment', 'Employee Benefit Application',
|
||||
'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary',
|
||||
'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
|
||||
'Income Tax Slab', 'Retention Bonus', 'Salary Structure']
|
||||
|
||||
for dt in doctypes_for_currency:
|
||||
frappe.db.sql("""update `tab{doctype}` set currency = %s where company=%s"""
|
||||
.format(doctype=dt), (company_currency, company))
|
||||
|
||||
# update fields in payroll entry
|
||||
frappe.db.sql("""
|
||||
update `tabPayroll Entry`
|
||||
set currency = %s,
|
||||
exchange_rate = 1,
|
||||
payroll_payable_account=%s
|
||||
where company=%s
|
||||
""", (company_currency, default_payroll_payable_account, company))
|
||||
|
||||
# update fields in Salary Structure Assignment
|
||||
frappe.db.sql("""
|
||||
update `tabSalary Structure Assignment`
|
||||
set currency = %s,
|
||||
payroll_payable_account=%s
|
||||
where company=%s
|
||||
""", (company_currency, default_payroll_payable_account, company))
|
||||
|
||||
# update fields in Salary Slip
|
||||
frappe.db.sql("""
|
||||
update `tabSalary Slip`
|
||||
set currency = %s,
|
||||
exchange_rate = 1,
|
||||
base_hour_rate = hour_rate,
|
||||
base_gross_pay = gross_pay,
|
||||
base_total_deduction = total_deduction,
|
||||
base_net_pay = net_pay,
|
||||
base_rounded_total = rounded_total,
|
||||
base_total_in_words = total_in_words
|
||||
where company=%s
|
||||
""", (company_currency, company))
|
@ -12,5 +12,57 @@ frappe.ui.form.on('Additional Salary', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (!frm.doc.currency) return;
|
||||
frm.set_query("salary_component", function() {
|
||||
return {
|
||||
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
|
||||
filters: {currency: frm.doc.currency, company: frm.doc.company}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('set_company')
|
||||
]);
|
||||
} else {
|
||||
frm.set_value("company", null);
|
||||
}
|
||||
},
|
||||
|
||||
set_company: function(frm) {
|
||||
frappe.call({
|
||||
method: "frappe.client.get_value",
|
||||
args: {
|
||||
doctype: "Employee",
|
||||
fieldname: "company",
|
||||
filters: {
|
||||
name: frm.doc.employee
|
||||
}
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
frm.set_value("company", data.message.company);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -11,20 +11,21 @@
|
||||
"employee",
|
||||
"employee_name",
|
||||
"salary_component",
|
||||
"overwrite_salary_structure_amount",
|
||||
"deduct_full_tax_on_selected_payroll_date",
|
||||
"type",
|
||||
"amount",
|
||||
"ref_doctype",
|
||||
"ref_docname",
|
||||
"amended_from",
|
||||
"column_break_5",
|
||||
"company",
|
||||
"is_recurring",
|
||||
"department",
|
||||
"currency",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"payroll_date",
|
||||
"type",
|
||||
"department",
|
||||
"amount",
|
||||
"amended_from"
|
||||
"is_recurring",
|
||||
"overwrite_salary_structure_amount",
|
||||
"deduct_full_tax_on_selected_payroll_date"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -59,6 +60,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -159,11 +161,22 @@
|
||||
"label": "Reference Document",
|
||||
"options": "ref_doctype",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 21:10:50.374063",
|
||||
"modified": "2020-10-20 17:51:13.419716",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Additional Salary",
|
||||
|
@ -22,10 +22,15 @@ class AdditionalSalary(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_salary_structure()
|
||||
self.validate_recurring_additional_salary_overlap()
|
||||
if self.amount < 0:
|
||||
frappe.throw(_("Amount should not be less than zero."))
|
||||
|
||||
def validate_salary_structure(self):
|
||||
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
||||
frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
|
||||
|
||||
def validate_recurring_additional_salary_overlap(self):
|
||||
if self.is_recurring:
|
||||
additional_salaries = frappe.db.sql("""
|
||||
|
@ -8,6 +8,7 @@ from frappe.utils import nowdate, add_days
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
|
||||
class TestAdditionalSalary(unittest.TestCase):
|
||||
@ -15,12 +16,19 @@ class TestAdditionalSalary(unittest.TestCase):
|
||||
def setUp(self):
|
||||
setup_test()
|
||||
|
||||
def tearDown(self):
|
||||
for dt in ["Salary Slip", "Additional Salary", "Salary Structure Assignment", "Salary Structure"]:
|
||||
frappe.db.sql("delete from `tab%s`" % dt)
|
||||
|
||||
def test_recurring_additional_salary(self):
|
||||
amount = 0
|
||||
salary_component = None
|
||||
emp_id = make_employee("test_additional@salary.com")
|
||||
frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800))
|
||||
salary_structure = make_salary_structure("Test Salary Structure Additional Salary", "Monthly", employee=emp_id)
|
||||
add_sal = get_additional_salary(emp_id)
|
||||
|
||||
ss = make_employee_salary_slip("test_additional@salary.com", "Monthly")
|
||||
|
||||
ss = make_employee_salary_slip("test_additional@salary.com", "Monthly", salary_structure=salary_structure.name)
|
||||
for earning in ss.earnings:
|
||||
if earning.salary_component == "Recurring Salary Component":
|
||||
amount = earning.amount
|
||||
@ -29,8 +37,6 @@ class TestAdditionalSalary(unittest.TestCase):
|
||||
self.assertEqual(amount, add_sal.amount)
|
||||
self.assertEqual(salary_component, add_sal.salary_component)
|
||||
|
||||
|
||||
|
||||
def get_additional_salary(emp_id):
|
||||
create_salary_component("Recurring Salary Component")
|
||||
add_sal = frappe.new_doc("Additional Salary")
|
||||
@ -40,6 +46,7 @@ def get_additional_salary(emp_id):
|
||||
add_sal.from_date = add_days(nowdate(), -50)
|
||||
add_sal.to_date = add_days(nowdate(), 180)
|
||||
add_sal.amount = 5000
|
||||
add_sal.currency = erpnext.get_default_currency()
|
||||
add_sal.save()
|
||||
add_sal.submit()
|
||||
|
||||
|
@ -3,7 +3,12 @@
|
||||
|
||||
frappe.ui.form.on('Employee Benefit Application', {
|
||||
employee: function(frm) {
|
||||
frm.trigger('set_earning_component');
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('set_earning_component')
|
||||
]);
|
||||
}
|
||||
var method, args;
|
||||
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
|
||||
method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
|
||||
@ -38,9 +43,26 @@ frappe.ui.form.on('Employee Benefit Application', {
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
payroll_period: function(frm) {
|
||||
var method, args;
|
||||
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
|
||||
if (frm.doc.employee && frm.doc.date && frm.doc.payroll_period) {
|
||||
method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
|
||||
args = {
|
||||
employee: frm.doc.employee,
|
||||
@ -60,11 +82,14 @@ var get_max_benefits=function(frm, method, args) {
|
||||
method: method,
|
||||
args: args,
|
||||
callback: function (data) {
|
||||
if(!data.exc){
|
||||
if(data.message){
|
||||
if (!data.exc) {
|
||||
if (data.message) {
|
||||
frm.set_value("max_benefits", data.message);
|
||||
} else {
|
||||
frm.set_value("max_benefits", 0);
|
||||
}
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -82,14 +107,19 @@ var calculate_all = function(doc) {
|
||||
var tbl = doc.employee_benefits || [];
|
||||
var pro_rata_dispensed_amount = 0;
|
||||
var total_amount = 0;
|
||||
for(var i = 0; i < tbl.length; i++){
|
||||
if(cint(tbl[i].amount) > 0) {
|
||||
total_amount += flt(tbl[i].amount);
|
||||
}
|
||||
if(tbl[i].pay_against_benefit_claim != 1){
|
||||
pro_rata_dispensed_amount += flt(tbl[i].amount);
|
||||
if (doc.max_benefits === 0) {
|
||||
doc.employee_benefits = [];
|
||||
} else {
|
||||
for (var i = 0; i < tbl.length; i++) {
|
||||
if (cint(tbl[i].amount) > 0) {
|
||||
total_amount += flt(tbl[i].amount);
|
||||
}
|
||||
if (tbl[i].pay_against_benefit_claim != 1) {
|
||||
pro_rata_dispensed_amount += flt(tbl[i].amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doc.total_amount = total_amount;
|
||||
doc.remaining_benefit = doc.max_benefits - total_amount;
|
||||
doc.pro_rata_dispensed_amount = pro_rata_dispensed_amount;
|
||||
|
@ -10,12 +10,14 @@
|
||||
"field_order": [
|
||||
"employee",
|
||||
"employee_name",
|
||||
"currency",
|
||||
"max_benefits",
|
||||
"remaining_benefit",
|
||||
"column_break_2",
|
||||
"date",
|
||||
"payroll_period",
|
||||
"department",
|
||||
"company",
|
||||
"amended_from",
|
||||
"section_break_4",
|
||||
"employee_benefits",
|
||||
@ -43,12 +45,14 @@
|
||||
"fieldname": "max_benefits",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Max Benefits (Yearly)",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "remaining_benefit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Remaining Benefits (Yearly)",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -108,18 +112,38 @@
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "pro_rata_dispensed_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Dispensed Amount (Pro-rated)",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 22:58:31.271922",
|
||||
"modified": "2020-11-25 11:49:05.095101",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Benefit Application",
|
||||
|
@ -33,8 +33,8 @@ class EmployeeBenefitApplication(Document):
|
||||
benefit_given = get_sal_slip_total_benefit_given(self.employee, payroll_period, component = benefit.earning_component)
|
||||
benefit_claim_remining = benefit_claimed - benefit_given
|
||||
if benefit_claimed > 0 and benefit_claim_remining > benefit.amount:
|
||||
frappe.throw(_("An amount of {0} already claimed for the component {1},\
|
||||
set the amount equal or greater than {2}").format(benefit_claimed, benefit.earning_component, benefit_claim_remining))
|
||||
frappe.throw(_("An amount of {0} already claimed for the component {1}, set the amount equal or greater than {2}").format(
|
||||
benefit_claimed, benefit.earning_component, benefit_claim_remining))
|
||||
|
||||
def validate_remaining_benefit_amount(self):
|
||||
# check salary structure earnings have flexi component (sum of max_benefit_amount)
|
||||
@ -62,11 +62,11 @@ class EmployeeBenefitApplication(Document):
|
||||
if pro_rata_amount == 0 and non_pro_rata_amount == 0:
|
||||
frappe.throw(_("Please add the remaining benefits {0} to any of the existing component").format(self.remaining_benefit))
|
||||
elif non_pro_rata_amount > 0 and non_pro_rata_amount < rounded(self.remaining_benefit):
|
||||
frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application \
|
||||
as pro-rata component").format(non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
|
||||
frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application as pro-rata component").format(
|
||||
non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
|
||||
elif non_pro_rata_amount == 0:
|
||||
frappe.throw(_("Please add the remaining benefits {0} to the application as \
|
||||
pro-rata component").format(self.remaining_benefit))
|
||||
frappe.throw(_("Please add the remaining benefits {0} to the application as pro-rata component").format(
|
||||
self.remaining_benefit))
|
||||
|
||||
def validate_max_benefit_for_component(self):
|
||||
if self.employee_benefits:
|
||||
@ -115,7 +115,7 @@ def get_max_benefits_remaining(employee, on_date, payroll_period):
|
||||
if max_benefits and max_benefits > 0:
|
||||
have_depends_on_payment_days = False
|
||||
per_day_amount_total = 0
|
||||
payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[0]
|
||||
payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[1]
|
||||
payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
|
||||
|
||||
# Get all salary slip flexi amount in the payroll period
|
||||
@ -239,4 +239,17 @@ def get_earning_components(doctype, txt, searchfield, start, page_len, filters):
|
||||
""", salary_structure)
|
||||
else:
|
||||
frappe.throw(_("Salary Structure not found for employee {0} and date {1}")
|
||||
.format(filters['employee'], filters['date']))
|
||||
.format(filters['employee'], filters['date']))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_earning_components_max_benefits(employee, date, earning_component):
|
||||
salary_structure = get_assigned_salary_structure(employee, date)
|
||||
amount = frappe.db.sql("""
|
||||
select amount
|
||||
from `tabSalary Detail`
|
||||
where parent = %s and is_flexible_benefit = 1
|
||||
and salary_component = %s
|
||||
order by name
|
||||
""", salary_structure, earning_component)
|
||||
|
||||
return amount if amount else 0
|
@ -33,6 +33,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Max Benefit Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -40,12 +41,13 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:45:00.519134",
|
||||
"modified": "2020-09-29 16:22:15.783854",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Benefit Application Detail",
|
||||
|
@ -12,5 +12,24 @@ frappe.ui.form.on('Employee Benefit Claim', {
|
||||
},
|
||||
employee: function(frm) {
|
||||
frm.set_value("earning_component", null);
|
||||
if (frm.doc.employee) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.set_df_property('currency', 'hidden', 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!frm.doc.earning_component) {
|
||||
frm.doc.max_amount_eligible = null;
|
||||
frm.doc.claimed_amount = null;
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
});
|
||||
|
@ -12,6 +12,8 @@
|
||||
"department",
|
||||
"column_break_3",
|
||||
"claim_date",
|
||||
"currency",
|
||||
"company",
|
||||
"benefit_type_and_amount",
|
||||
"earning_component",
|
||||
"max_amount_eligible",
|
||||
@ -76,6 +78,7 @@
|
||||
"fieldname": "max_amount_eligible",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Max Amount Eligible",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -92,6 +95,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Claimed Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -119,11 +123,29 @@
|
||||
"fieldname": "attachments",
|
||||
"fieldtype": "Attach",
|
||||
"label": "Attachments"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:01:50.791676",
|
||||
"modified": "2020-11-25 11:49:56.097352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Benefit Claim",
|
||||
|
@ -11,12 +11,57 @@ frappe.ui.form.on('Employee Incentive', {
|
||||
};
|
||||
});
|
||||
|
||||
if (!frm.doc.currency) return;
|
||||
frm.set_query("salary_component", function() {
|
||||
return {
|
||||
filters: {
|
||||
"type": "Earning"
|
||||
}
|
||||
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
|
||||
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('set_company')
|
||||
]);
|
||||
} else {
|
||||
frm.set_value("company", null);
|
||||
}
|
||||
},
|
||||
|
||||
set_company: function(frm) {
|
||||
frappe.call({
|
||||
method: "frappe.client.get_value",
|
||||
args: {
|
||||
doctype: "Employee",
|
||||
fieldname: "company",
|
||||
filters: {
|
||||
name: frm.doc.employee
|
||||
}
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
frm.set_value("company", data.message.company);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_currency: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -7,10 +7,12 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"employee",
|
||||
"incentive_amount",
|
||||
"employee_name",
|
||||
"salary_component",
|
||||
"company",
|
||||
"currency",
|
||||
"incentive_amount",
|
||||
"column_break_5",
|
||||
"salary_component",
|
||||
"payroll_date",
|
||||
"department",
|
||||
"amended_from"
|
||||
@ -28,6 +30,7 @@
|
||||
"fieldname": "incentive_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incentive Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -70,11 +73,29 @@
|
||||
"label": "Salary Component",
|
||||
"options": "Salary Component",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 22:42:51.209630",
|
||||
"modified": "2020-10-20 17:22:16.468042",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Incentive",
|
||||
|
@ -4,14 +4,23 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
class EmployeeIncentive(Document):
|
||||
def validate(self):
|
||||
self.validate_salary_structure()
|
||||
|
||||
def validate_salary_structure(self):
|
||||
if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
|
||||
frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
|
||||
|
||||
def on_submit(self):
|
||||
company = frappe.db.get_value('Employee', self.employee, 'company')
|
||||
|
||||
additional_salary = frappe.new_doc('Additional Salary')
|
||||
additional_salary.employee = self.employee
|
||||
additional_salary.currency = self.currency
|
||||
additional_salary.salary_component = self.salary_component
|
||||
additional_salary.overwrite_salary_structure_amount = 0
|
||||
additional_salary.amount = self.incentive_amount
|
||||
|
@ -14,6 +14,7 @@
|
||||
"column_break_2",
|
||||
"payroll_period",
|
||||
"company",
|
||||
"currency",
|
||||
"amended_from",
|
||||
"section_break_8",
|
||||
"declarations",
|
||||
@ -92,6 +93,7 @@
|
||||
"fieldname": "total_declared_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Declared Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -102,12 +104,22 @@
|
||||
"fieldname": "total_exemption_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Exemption Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 22:49:43.829892",
|
||||
"modified": "2020-10-20 16:42:24.493761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Declaration",
|
||||
|
@ -22,6 +22,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
|
||||
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
|
||||
"company": erpnext.get_default_company(),
|
||||
"payroll_period": "_Test Payroll Period",
|
||||
"currency": erpnext.get_default_currency(),
|
||||
"declarations": [
|
||||
dict(exemption_sub_category = "_Test Sub Category",
|
||||
exemption_category = "_Test Category",
|
||||
@ -39,6 +40,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
|
||||
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
|
||||
"company": erpnext.get_default_company(),
|
||||
"payroll_period": "_Test Payroll Period",
|
||||
"currency": erpnext.get_default_currency(),
|
||||
"declarations": [
|
||||
dict(exemption_sub_category = "_Test Sub Category",
|
||||
exemption_category = "_Test Category",
|
||||
@ -54,6 +56,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
|
||||
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
|
||||
"company": erpnext.get_default_company(),
|
||||
"payroll_period": "_Test Payroll Period",
|
||||
"currency": erpnext.get_default_currency(),
|
||||
"declarations": [
|
||||
dict(exemption_sub_category = "_Test Sub Category",
|
||||
exemption_category = "_Test Category",
|
||||
@ -70,6 +73,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
|
||||
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
|
||||
"company": erpnext.get_default_company(),
|
||||
"payroll_period": "_Test Payroll Period",
|
||||
"currency": erpnext.get_default_currency(),
|
||||
"declarations": [
|
||||
dict(exemption_sub_category = "_Test Sub Category",
|
||||
exemption_category = "_Test Category",
|
||||
|
@ -35,6 +35,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Maximum Exempted Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
@ -43,12 +44,13 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Declared Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:41:03.638739",
|
||||
"modified": "2020-10-20 16:43:09.606265",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Declaration Category",
|
||||
|
@ -54,5 +54,9 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
currency: function(frm) {
|
||||
frm.refresh_fields();
|
||||
}
|
||||
});
|
||||
|
@ -11,6 +11,7 @@
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"currency",
|
||||
"column_break_2",
|
||||
"submission_date",
|
||||
"payroll_period",
|
||||
@ -97,6 +98,7 @@
|
||||
"fieldname": "total_actual_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Actual Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -107,6 +109,7 @@
|
||||
"fieldname": "exemption_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Exemption Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -126,11 +129,20 @@
|
||||
"options": "Employee Tax Exemption Proof Submission",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 22:53:10.412321",
|
||||
"modified": "2020-10-20 16:47:03.410020",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Proof Submission",
|
||||
|
@ -34,6 +34,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Maximum Exemption Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
@ -48,12 +49,13 @@
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Actual Amount"
|
||||
"label": "Actual Amount",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:37:08.265600",
|
||||
"modified": "2020-10-20 16:47:31.480870",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Employee Tax Exemption Proof Submission Detail",
|
||||
|
@ -2,5 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Income Tax Slab', {
|
||||
|
||||
currency: function(frm) {
|
||||
frm.refresh_fields();
|
||||
}
|
||||
});
|
||||
|
@ -9,8 +9,9 @@
|
||||
"effective_from",
|
||||
"company",
|
||||
"column_break_3",
|
||||
"allow_tax_exemption",
|
||||
"currency",
|
||||
"standard_tax_exemption_amount",
|
||||
"allow_tax_exemption",
|
||||
"disabled",
|
||||
"amended_from",
|
||||
"taxable_salary_slabs_section",
|
||||
@ -70,7 +71,7 @@
|
||||
"fieldname": "standard_tax_exemption_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Standard Tax Exemption Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@ -90,11 +91,20 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Other Taxes and Charges",
|
||||
"options": "Income Tax Slab Other Charges"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 20:27:13.425084",
|
||||
"modified": "2020-10-19 13:54:24.728075",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Income Tax Slab",
|
||||
|
@ -45,7 +45,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Min Taxable Income",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
@ -57,12 +57,12 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Max Taxable Income",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:33:17.931912",
|
||||
"modified": "2020-10-19 13:45:12.850090",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Income Tax Slab Other Charges",
|
||||
|
@ -52,7 +52,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 23:25:13.779032",
|
||||
"modified": "2020-09-30 12:40:07.999878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Payroll Employee Detail",
|
||||
|
@ -17,6 +17,16 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("payroll_payable_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"root_type": "Liability",
|
||||
"is_group": 0,
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
@ -139,6 +149,36 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
frm.events.clear_employee_table(frm);
|
||||
},
|
||||
|
||||
currency: function (frm) {
|
||||
var company_currency;
|
||||
if (!frm.doc.company) {
|
||||
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
|
||||
} else {
|
||||
company_currency = erpnext.get_currency(frm.doc.company);
|
||||
}
|
||||
if (frm.doc.currency) {
|
||||
if (company_currency != frm.doc.currency) {
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: frm.doc.currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
department: function (frm) {
|
||||
frm.events.clear_employee_table(frm);
|
||||
},
|
||||
|
@ -11,8 +11,11 @@
|
||||
"column_break0",
|
||||
"posting_date",
|
||||
"payroll_frequency",
|
||||
"column_break1",
|
||||
"company",
|
||||
"column_break1",
|
||||
"currency",
|
||||
"exchange_rate",
|
||||
"payroll_payable_account",
|
||||
"section_break_8",
|
||||
"branch",
|
||||
"department",
|
||||
@ -257,12 +260,37 @@
|
||||
{
|
||||
"fieldname": "column_break_33",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "company",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "company",
|
||||
"fieldname": "exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate",
|
||||
"precision": "9",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "company",
|
||||
"fieldname": "payroll_payable_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payroll Payable Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 20:06:06.953904",
|
||||
"modified": "2020-10-23 13:00:33.753228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Payroll Entry",
|
||||
|
@ -3,7 +3,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe, erpnext
|
||||
from frappe.model.document import Document
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff
|
||||
@ -51,13 +51,15 @@ class PayrollEntry(Document):
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s and
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
|
||||
cond += "and %(from_date)s >= t2.from_date"
|
||||
emp_list = frappe.db.sql("""
|
||||
select
|
||||
@ -68,14 +70,26 @@ class PayrollEntry(Document):
|
||||
t1.name = t2.employee
|
||||
and t2.docstatus = 1
|
||||
%s order by t2.from_date desc
|
||||
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date}, as_dict=True)
|
||||
""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
|
||||
return emp_list
|
||||
|
||||
def fill_employee_details(self):
|
||||
self.set('employees', [])
|
||||
employees = self.get_emp_list()
|
||||
if not employees:
|
||||
frappe.throw(_("No employees for the mentioned criteria"))
|
||||
error_msg = _("No employees found for the mentioned criteria:<br>Company: {0}<br> Currency: {1}<br>Payroll Payable Account: {2}").format(
|
||||
frappe.bold(self.company), frappe.bold(self.currency), frappe.bold(self.payroll_payable_account))
|
||||
if self.branch:
|
||||
error_msg += "<br>" + _("Branch: {0}").format(frappe.bold(self.branch))
|
||||
if self.department:
|
||||
error_msg += "<br>" + _("Department: {0}").format(frappe.bold(self.department))
|
||||
if self.designation:
|
||||
error_msg += "<br>" + _("Designation: {0}").format(frappe.bold(self.designation))
|
||||
if self.start_date:
|
||||
error_msg += "<br>" + _("Start date: {0}").format(frappe.bold(self.start_date))
|
||||
if self.end_date:
|
||||
error_msg += "<br>" + _("End date: {0}").format(frappe.bold(self.end_date))
|
||||
frappe.throw(error_msg, title=_("No employees found"))
|
||||
|
||||
for d in employees:
|
||||
self.append('employees', d)
|
||||
@ -123,7 +137,9 @@ class PayrollEntry(Document):
|
||||
"posting_date": self.posting_date,
|
||||
"deduct_tax_for_unclaimed_employee_benefits": self.deduct_tax_for_unclaimed_employee_benefits,
|
||||
"deduct_tax_for_unsubmitted_tax_exemption_proof": self.deduct_tax_for_unsubmitted_tax_exemption_proof,
|
||||
"payroll_entry": self.name
|
||||
"payroll_entry": self.name,
|
||||
"exchange_rate": self.exchange_rate,
|
||||
"currency": self.currency
|
||||
})
|
||||
if len(emp_list) > 30:
|
||||
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
|
||||
@ -160,10 +176,10 @@ class PayrollEntry(Document):
|
||||
|
||||
def get_salary_component_account(self, salary_component):
|
||||
account = frappe.db.get_value("Salary Component Account",
|
||||
{"parent": salary_component, "company": self.company}, "default_account")
|
||||
{"parent": salary_component, "company": self.company}, "account")
|
||||
|
||||
if not account:
|
||||
frappe.throw(_("Please set default account in Salary Component {0}")
|
||||
frappe.throw(_("Please set account in Salary Component {0}")
|
||||
.format(salary_component))
|
||||
|
||||
return account
|
||||
@ -203,21 +219,11 @@ class PayrollEntry(Document):
|
||||
account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
|
||||
return account_dict
|
||||
|
||||
def get_default_payroll_payable_account(self):
|
||||
payroll_payable_account = frappe.get_cached_value('Company',
|
||||
{"company_name": self.company}, "default_payroll_payable_account")
|
||||
|
||||
if not payroll_payable_account:
|
||||
frappe.throw(_("Please set Default Payroll Payable Account in Company {0}")
|
||||
.format(self.company))
|
||||
|
||||
return payroll_payable_account
|
||||
|
||||
def make_accrual_jv_entry(self):
|
||||
self.check_permission('write')
|
||||
earnings = self.get_salary_component_total(component_type = "earnings") or {}
|
||||
deductions = self.get_salary_component_total(component_type = "deductions") or {}
|
||||
default_payroll_payable_account = self.get_default_payroll_payable_account()
|
||||
payroll_payable_account = self.payroll_payable_account
|
||||
jv_name = ""
|
||||
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
||||
|
||||
@ -230,14 +236,19 @@ class PayrollEntry(Document):
|
||||
journal_entry.posting_date = self.posting_date
|
||||
|
||||
accounts = []
|
||||
currencies = []
|
||||
payable_amount = 0
|
||||
multi_currency = 0
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
|
||||
# Earnings
|
||||
for acc_cc, amount in earnings.items():
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount += flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
@ -245,25 +256,32 @@ class PayrollEntry(Document):
|
||||
|
||||
# Deductions
|
||||
for acc_cc, amount in deductions.items():
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount -= flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Payable amount
|
||||
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": default_payroll_payable_account,
|
||||
"credit_in_account_currency": flt(payable_amount, precision),
|
||||
"account": payroll_payable_account,
|
||||
"credit_in_account_currency": flt(payable_amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
|
||||
journal_entry.set("accounts", accounts)
|
||||
journal_entry.title = default_payroll_payable_account
|
||||
if len(currencies) > 1:
|
||||
multi_currency = 1
|
||||
journal_entry.multi_currency = multi_currency
|
||||
journal_entry.title = payroll_payable_account
|
||||
journal_entry.save()
|
||||
|
||||
try:
|
||||
@ -275,6 +293,18 @@ class PayrollEntry(Document):
|
||||
|
||||
return jv_name
|
||||
|
||||
def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
|
||||
conversion_rate = 1
|
||||
exchange_rate = self.exchange_rate
|
||||
account_currency = frappe.db.get_value('Account', account, 'account_currency')
|
||||
if account_currency not in currencies:
|
||||
currencies.append(account_currency)
|
||||
if account_currency == company_currency:
|
||||
conversion_rate = self.exchange_rate
|
||||
exchange_rate = 1
|
||||
amount = flt(amount) * flt(conversion_rate)
|
||||
return exchange_rate, amount
|
||||
|
||||
def make_payment_entry(self):
|
||||
self.check_permission('write')
|
||||
|
||||
@ -303,31 +333,43 @@ class PayrollEntry(Document):
|
||||
self.create_journal_entry(salary_slip_total, "salary")
|
||||
|
||||
def create_journal_entry(self, je_payment_amount, user_remark):
|
||||
default_payroll_payable_account = self.get_default_payroll_payable_account()
|
||||
payroll_payable_account = self.payroll_payable_account
|
||||
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
||||
|
||||
accounts = []
|
||||
currencies = []
|
||||
multi_currency = 0
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
|
||||
if len(currencies) > 1:
|
||||
multi_currency = 1
|
||||
|
||||
journal_entry = frappe.new_doc('Journal Entry')
|
||||
journal_entry.voucher_type = 'Bank Entry'
|
||||
journal_entry.user_remark = _('Payment of {0} from {1} to {2}')\
|
||||
.format(user_remark, self.start_date, self.end_date)
|
||||
journal_entry.company = self.company
|
||||
journal_entry.posting_date = self.posting_date
|
||||
journal_entry.multi_currency = multi_currency
|
||||
|
||||
payment_amount = flt(je_payment_amount, precision)
|
||||
|
||||
journal_entry.set("accounts", [
|
||||
{
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": payment_amount
|
||||
},
|
||||
{
|
||||
"account": default_payroll_payable_account,
|
||||
"debit_in_account_currency": payment_amount,
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
}
|
||||
])
|
||||
journal_entry.set("accounts", accounts)
|
||||
journal_entry.save(ignore_permissions = True)
|
||||
|
||||
def update_salary_slip_status(self, jv_name = None):
|
||||
@ -496,6 +538,21 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
if publish_progress:
|
||||
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
|
||||
title = _("Creating Salary Slips..."))
|
||||
else:
|
||||
salary_slip_name = frappe.db.sql(
|
||||
'''SELECT
|
||||
name
|
||||
FROM `tabSalary Slip`
|
||||
WHERE company=%s
|
||||
AND start_date >= %s
|
||||
AND end_date <= %s
|
||||
AND employee = %s
|
||||
''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
|
||||
|
||||
salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
|
||||
salary_slip_doc.exchange_rate = args.exchange_rate
|
||||
salary_slip_doc.set_totals()
|
||||
salary_slip_doc.db_update()
|
||||
|
||||
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
|
||||
payroll_entry.db_set("salary_slips_created", 1)
|
||||
|
@ -10,8 +10,8 @@ from frappe.utils import add_months
|
||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
|
||||
make_earning_salary_component, make_deduction_salary_component, create_account
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
|
||||
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
|
||||
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
|
||||
|
||||
@ -34,10 +34,47 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
get_salary_component_account(data.name)
|
||||
|
||||
employee = frappe.db.get_value("Employee", {'company': company})
|
||||
make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
|
||||
company_doc = frappe.get_doc('Company', company)
|
||||
make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company, currency=company_doc.default_currency)
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
|
||||
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
|
||||
make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account,
|
||||
currency=company_doc.default_currency)
|
||||
|
||||
def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use
|
||||
company = erpnext.get_default_company()
|
||||
employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
|
||||
for data in frappe.get_all('Salary Component', fields = ["name"]):
|
||||
if not frappe.db.get_value('Salary Component Account',
|
||||
{'parent': data.name, 'company': company}, 'name'):
|
||||
get_salary_component_account(data.name)
|
||||
|
||||
company_doc = frappe.get_doc('Company', company)
|
||||
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company)
|
||||
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
|
||||
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
|
||||
payroll_entry.make_payment_entry()
|
||||
|
||||
salary_slip.load_from_db()
|
||||
|
||||
payroll_je = salary_slip.journal_entry
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
|
||||
payment_entry = frappe.db.sql('''
|
||||
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
|
||||
Where je.name = jea.parent
|
||||
And jea.reference_name = %s
|
||||
''', (payroll_entry.name), as_dict=1)
|
||||
|
||||
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
|
||||
self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
|
||||
|
||||
def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
|
||||
for data in frappe.get_all('Salary Component', fields = ["name"]):
|
||||
@ -52,24 +89,32 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
"company": "_Test Company"
|
||||
}).insert()
|
||||
|
||||
frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee1@example.com' """)
|
||||
frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee2@example.com' """)
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 1' """)
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 2' """)
|
||||
|
||||
employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
|
||||
department="cc - _TC", company="_Test Company")
|
||||
employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
|
||||
department="cc - _TC", company="_Test Company")
|
||||
|
||||
make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
|
||||
make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
|
||||
|
||||
if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
|
||||
create_account(account_name="_Test Payroll Payable",
|
||||
company="_Test Company", parent_account="Current Liabilities - _TC")
|
||||
frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
|
||||
"_Test Payroll Payable - _TC")
|
||||
create_account(account_name="_Test Payroll Payable",
|
||||
company="_Test Company", parent_account="Current Liabilities - _TC")
|
||||
|
||||
if not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") or \
|
||||
frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
|
||||
frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
|
||||
"_Test Payroll Payable - _TC")
|
||||
|
||||
make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
|
||||
make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
|
||||
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
|
||||
pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
|
||||
pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account="_Test Payroll Payable - _TC",
|
||||
currency=frappe.db.get_value("Company", "_Test Company", "default_currency"), department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
|
||||
je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
|
||||
je_entries = frappe.db.sql("""
|
||||
select account, cost_center, debit, credit
|
||||
@ -121,7 +166,7 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
employee_doc.save()
|
||||
|
||||
salary_structure = "Test Salary Structure for Loan"
|
||||
make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company")
|
||||
make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
|
||||
|
||||
loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
|
||||
loan.repay_from_salary = 1
|
||||
@ -133,8 +178,8 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
|
||||
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
make_payroll_entry(company="_Test Company", start_date=dates.start_date,
|
||||
end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
|
||||
make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
|
||||
currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
|
||||
|
||||
name = frappe.db.get_value('Salary Slip',
|
||||
{'posting_date': nowdate(), 'employee': applicant}, 'name')
|
||||
@ -165,6 +210,9 @@ def make_payroll_entry(**args):
|
||||
payroll_entry.payroll_frequency = "Monthly"
|
||||
payroll_entry.branch = args.branch or None
|
||||
payroll_entry.department = args.department or None
|
||||
payroll_entry.payroll_payable_account = args.payable_account
|
||||
payroll_entry.currency = args.currency
|
||||
payroll_entry.exchange_rate = args.exchange_rate or 1
|
||||
|
||||
if args.cost_center:
|
||||
payroll_entry.cost_center = args.cost_center
|
||||
@ -212,3 +260,11 @@ def make_holiday(holiday_list_name):
|
||||
}).insert()
|
||||
|
||||
return holiday_list_name
|
||||
|
||||
def get_salary_slip(user, period, salary_structure):
|
||||
salary_slip = make_employee_salary_slip(user, period, salary_structure)
|
||||
salary_slip.exchange_rate = 70
|
||||
salary_slip.calculate_net_pay()
|
||||
salary_slip.db_update()
|
||||
|
||||
return salary_slip
|
@ -9,45 +9,45 @@ QUnit.test("test: Set Salary Components", function (assert) {
|
||||
() => {
|
||||
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
|
||||
row.company = 'For Testing';
|
||||
row.default_account = 'Salary - FT';
|
||||
row.account = 'Salary - FT';
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
|
||||
|
||||
() => frappe.set_route('Form', 'Salary Component', 'Basic'),
|
||||
() => {
|
||||
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
|
||||
row.company = 'For Testing';
|
||||
row.default_account = 'Salary - FT';
|
||||
row.account = 'Salary - FT';
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
|
||||
|
||||
() => frappe.set_route('Form', 'Salary Component', 'Income Tax'),
|
||||
() => {
|
||||
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
|
||||
row.company = 'For Testing';
|
||||
row.default_account = 'Salary - FT';
|
||||
row.account = 'Salary - FT';
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
|
||||
|
||||
() => frappe.set_route('Form', 'Salary Component', 'Arrear'),
|
||||
() => {
|
||||
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
|
||||
row.company = 'For Testing';
|
||||
row.default_account = 'Salary - FT';
|
||||
row.account = 'Salary - FT';
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
|
||||
() => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
|
||||
|
||||
() => frappe.set_route('Form', 'Company', 'For Testing'),
|
||||
() => cur_frm.set_value('default_payroll_payable_account', 'Payroll Payable - FT'),
|
||||
|
@ -18,5 +18,22 @@ frappe.ui.form.on('Retention Bonus', {
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.call({
|
||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value('currency', r.message);
|
||||
frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -17,7 +17,8 @@
|
||||
"column_break_6",
|
||||
"employee_name",
|
||||
"department",
|
||||
"date_of_joining"
|
||||
"date_of_joining",
|
||||
"currency"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -46,6 +47,7 @@
|
||||
"fieldname": "bonus_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Bonus Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -89,11 +91,22 @@
|
||||
"label": "Salary Component",
|
||||
"options": "Salary Component",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 22:42:05.251951",
|
||||
"modified": "2020-10-20 17:27:47.003134",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Retention Bonus",
|
||||
@ -151,7 +164,6 @@
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('Salary Component', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
|
||||
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
|
@ -147,7 +147,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -160,7 +160,7 @@
|
||||
"fieldname": "default_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Default Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@ -169,6 +169,7 @@
|
||||
"hidden": 1,
|
||||
"label": "Additional Amount",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -177,6 +178,7 @@
|
||||
"fieldname": "tax_on_flexible_benefit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Tax on flexible benefit",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -184,6 +186,7 @@
|
||||
"fieldname": "tax_on_additional_salary",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Tax on additional salary",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -227,7 +230,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-07 20:39:41.619283",
|
||||
"modified": "2020-11-25 13:12:41.081106",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
@ -74,14 +74,85 @@ frappe.ui.form.on("Salary Slip", {
|
||||
if (!frm.doc.letter_head && company.default_letter_head) {
|
||||
frm.set_value('letter_head', company.default_letter_head);
|
||||
}
|
||||
frm.trigger("set_dynamic_labels");
|
||||
},
|
||||
|
||||
set_dynamic_labels: function(frm) {
|
||||
var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
|
||||
frappe.run_serially([
|
||||
() => frm.events.set_exchange_rate(frm, company_currency),
|
||||
() => frm.events.change_form_labels(frm, company_currency),
|
||||
() => frm.events.change_grid_labels(frm),
|
||||
() => frm.refresh_fields()
|
||||
]);
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, company_currency) {
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
exchange_rate: function(frm) {
|
||||
calculate_totals(frm);
|
||||
},
|
||||
|
||||
hide_loan_section: function(frm) {
|
||||
frm.set_df_property('section_break_43', 'hidden', 1);
|
||||
},
|
||||
|
||||
change_form_labels: function(frm, company_currency) {
|
||||
frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
|
||||
"base_net_pay", "base_rounded_total", "base_total_in_words"],
|
||||
company_currency);
|
||||
|
||||
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"],
|
||||
frm.doc.currency);
|
||||
|
||||
// toggle fields
|
||||
frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
|
||||
"base_net_pay", "base_rounded_total", "base_total_in_words"],
|
||||
frm.doc.currency != company_currency);
|
||||
},
|
||||
|
||||
change_grid_labels: function(frm) {
|
||||
frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
|
||||
"tax_on_additional_salary"], frm.doc.currency, "earnings");
|
||||
|
||||
frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
|
||||
"tax_on_additional_salary"], frm.doc.currency, "deductions");
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields");
|
||||
|
||||
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
|
||||
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
|
||||
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
||||
frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
|
||||
frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
|
||||
calculate_totals(frm);
|
||||
frm.trigger("set_dynamic_labels");
|
||||
},
|
||||
|
||||
salary_slip_based_on_timesheet: function(frm) {
|
||||
@ -118,51 +189,94 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
get_emp_and_working_day_details: function(frm) {
|
||||
return frappe.call({
|
||||
method: 'get_emp_and_working_day_details',
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
frm.refresh();
|
||||
if (r.message[1] !== "Leave" && r.message[0]) {
|
||||
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as ")+ r.message[0] +__(". You can can change this in ") + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
|
||||
if (frm.doc.employee) {
|
||||
return frappe.call({
|
||||
method: 'get_emp_and_working_day_details',
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (r.message[1] !== "Leave" && r.message[0]) {
|
||||
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as {0}. You can can change this in {1}", [r.message, frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)]));
|
||||
}
|
||||
frm.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Salary Slip Timesheet', {
|
||||
time_sheet: function(frm, dt, dn) {
|
||||
total_work_hours(frm, dt, dn);
|
||||
time_sheet: function(frm) {
|
||||
calculate_totals(frm);
|
||||
},
|
||||
timesheets_remove: function(frm, dt, dn) {
|
||||
total_work_hours(frm, dt, dn);
|
||||
timesheets_remove: function(frm) {
|
||||
calculate_totals(frm);
|
||||
}
|
||||
});
|
||||
|
||||
// calculate total working hours, earnings based on hourly wages and totals
|
||||
var total_work_hours = function(frm) {
|
||||
var total_working_hours = 0.0;
|
||||
$.each(frm.doc["timesheets"] || [], function(i, timesheet) {
|
||||
total_working_hours += timesheet.working_hours;
|
||||
});
|
||||
frm.set_value('total_working_hours', total_working_hours);
|
||||
|
||||
var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate;
|
||||
|
||||
frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => {
|
||||
var gross_pay = 0.0;
|
||||
$.each(frm.doc["earnings"], function(i, earning) {
|
||||
if (earning.salary_component == r.salary_component) {
|
||||
earning.amount = wages_amount;
|
||||
frm.refresh_fields('earnings');
|
||||
var calculate_totals = function(frm) {
|
||||
if (frm.doc.earnings || frm.doc.deductions) {
|
||||
frappe.call({
|
||||
method: "set_totals",
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
frm.refresh_fields();
|
||||
}
|
||||
gross_pay += earning.amount;
|
||||
});
|
||||
frm.set_value('gross_pay', gross_pay);
|
||||
|
||||
frm.doc.net_pay = flt(frm.doc.gross_pay) - flt(frm.doc.total_deduction);
|
||||
frm.doc.rounded_total = Math.round(frm.doc.net_pay);
|
||||
refresh_many(['net_pay', 'rounded_total']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Salary Detail', {
|
||||
amount: function(frm) {
|
||||
calculate_totals(frm);
|
||||
},
|
||||
|
||||
earnings_remove: function(frm) {
|
||||
calculate_totals(frm);
|
||||
},
|
||||
|
||||
deductions_remove: function(frm) {
|
||||
calculate_totals(frm);
|
||||
},
|
||||
|
||||
salary_component: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
if (child.salary_component) {
|
||||
frappe.call({
|
||||
method: "frappe.client.get",
|
||||
args: {
|
||||
doctype: "Salary Component",
|
||||
name: child.salary_component
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
var result = data.message;
|
||||
frappe.model.set_value(cdt, cdn, 'condition', result.condition);
|
||||
frappe.model.set_value(cdt, cdn, 'amount_based_on_formula', result.amount_based_on_formula);
|
||||
if (result.amount_based_on_formula === 1) {
|
||||
frappe.model.set_value(cdt, cdn, 'formula', result.formula);
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, 'amount', result.amount);
|
||||
}
|
||||
frappe.model.set_value(cdt, cdn, 'statistical_component', result.statistical_component);
|
||||
frappe.model.set_value(cdt, cdn, 'depends_on_payment_days', result.depends_on_payment_days);
|
||||
frappe.model.set_value(cdt, cdn, 'do_not_include_in_total', result.do_not_include_in_total);
|
||||
frappe.model.set_value(cdt, cdn, 'variable_based_on_taxable_salary', result.variable_based_on_taxable_salary);
|
||||
frappe.model.set_value(cdt, cdn, 'is_tax_applicable', result.is_tax_applicable);
|
||||
frappe.model.set_value(cdt, cdn, 'is_flexible_benefit', result.is_flexible_benefit);
|
||||
refresh_field("earnings");
|
||||
refresh_field("deductions");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
amount_based_on_formula: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
if (child.amount_based_on_formula === 1) {
|
||||
frappe.model.set_value(cdt, cdn, 'amount', null);
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, 'formula', null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -18,6 +18,8 @@
|
||||
"journal_entry",
|
||||
"payroll_entry",
|
||||
"company",
|
||||
"currency",
|
||||
"exchange_rate",
|
||||
"letter_head",
|
||||
"section_break_10",
|
||||
"start_date",
|
||||
@ -38,6 +40,7 @@
|
||||
"column_break_20",
|
||||
"total_working_hours",
|
||||
"hour_rate",
|
||||
"base_hour_rate",
|
||||
"section_break_26",
|
||||
"bank_name",
|
||||
"bank_account_no",
|
||||
@ -52,8 +55,10 @@
|
||||
"deductions",
|
||||
"totals",
|
||||
"gross_pay",
|
||||
"base_gross_pay",
|
||||
"column_break_25",
|
||||
"total_deduction",
|
||||
"base_total_deduction",
|
||||
"loan_repayment",
|
||||
"loans",
|
||||
"section_break_43",
|
||||
@ -63,10 +68,15 @@
|
||||
"total_loan_repayment",
|
||||
"net_pay_info",
|
||||
"net_pay",
|
||||
"base_net_pay",
|
||||
"column_break_53",
|
||||
"rounded_total",
|
||||
"base_rounded_total",
|
||||
"section_break_55",
|
||||
"total_in_words",
|
||||
"column_break_69",
|
||||
"base_total_in_words",
|
||||
"section_break_75",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@ -205,9 +215,13 @@
|
||||
{
|
||||
"fieldname": "salary_structure",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Salary Structure",
|
||||
"options": "Salary Structure",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
|
||||
@ -265,7 +279,7 @@
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Hour Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"print_hide_if_no_value": 1
|
||||
},
|
||||
{
|
||||
@ -347,24 +361,13 @@
|
||||
"fieldname": "gross_pay",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Pay",
|
||||
"oldfieldname": "gross_pay",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_25",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_deduction",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Deduction",
|
||||
"oldfieldname": "total_deduction",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "total_loan_repayment",
|
||||
"fieldname": "loan_repayment",
|
||||
@ -379,6 +382,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus != 0",
|
||||
"fieldname": "section_break_43",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
@ -416,13 +420,10 @@
|
||||
"label": "net pay info"
|
||||
},
|
||||
{
|
||||
"description": "Gross Pay - Total Deduction - Loan Repayment",
|
||||
"fieldname": "net_pay",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Net Pay",
|
||||
"oldfieldname": "net_pay",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -434,22 +435,13 @@
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounded Total",
|
||||
"options": "Company:company:default_currency",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_55",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Net Pay (in words) will be visible once you save the Salary Slip.",
|
||||
"fieldname": "total_in_words",
|
||||
"fieldtype": "Data",
|
||||
"label": "Total in words",
|
||||
"oldfieldname": "net_pay_in_words",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
@ -500,13 +492,99 @@
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
|
||||
"fetch_from": "salary_structure.currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_deduction",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Deduction",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_in_words",
|
||||
"fieldtype": "Data",
|
||||
"label": "Total in words",
|
||||
"length": 240,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_75",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Hour Rate (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide_if_no_value": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_gross_pay",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Pay (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1.0",
|
||||
"fieldname": "exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Exchange Rate",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_total_deduction",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Deduction (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_net_pay",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Net Pay (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "base_rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounded Total (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_total_in_words",
|
||||
"fieldtype": "Data",
|
||||
"label": "Total in words (Company Currency)",
|
||||
"length": 240,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_69",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 9,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-11 17:37:54.274384",
|
||||
"modified": "2020-10-21 23:02:59.400249",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Slip",
|
||||
|
@ -50,16 +50,20 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
self.calculate_net_pay()
|
||||
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
|
||||
self.total_in_words = money_in_words(total, company_currency)
|
||||
|
||||
if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
|
||||
max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
|
||||
if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
|
||||
frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
|
||||
format(max_working_hours), alert=True)
|
||||
|
||||
def set_net_total_in_words(self):
|
||||
doc_currency = self.currency
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
|
||||
base_total = self.base_net_pay if self.is_rounding_total_disabled() else self.base_rounded_total
|
||||
self.total_in_words = money_in_words(total, doc_currency)
|
||||
self.base_total_in_words = money_in_words(base_total, company_currency)
|
||||
|
||||
def on_submit(self):
|
||||
if self.net_pay < 0:
|
||||
frappe.throw(_("Net Pay cannot be less than 0"))
|
||||
@ -182,6 +186,7 @@ class SalarySlip(TransactionBase):
|
||||
if self.salary_slip_based_on_timesheet:
|
||||
self.salary_structure = self._salary_structure_doc.name
|
||||
self.hour_rate = self._salary_structure_doc.hour_rate
|
||||
self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
|
||||
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
|
||||
wages_amount = self.hour_rate * self.total_working_hours
|
||||
|
||||
@ -417,15 +422,22 @@ class SalarySlip(TransactionBase):
|
||||
if self.salary_structure:
|
||||
self.calculate_component_amounts("earnings")
|
||||
self.gross_pay = self.get_component_totals("earnings")
|
||||
self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
|
||||
|
||||
if self.salary_structure:
|
||||
self.calculate_component_amounts("deductions")
|
||||
self.total_deduction = self.get_component_totals("deductions")
|
||||
self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
|
||||
|
||||
self.set_loan_repayment()
|
||||
|
||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||
self.rounded_total = rounded(self.net_pay)
|
||||
self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
|
||||
self.base_rounded_total = flt(rounded(self.base_net_pay), self.precision('base_net_pay'))
|
||||
if self.hour_rate:
|
||||
self.base_hour_rate = flt(flt(self.hour_rate) * flt(self.exchange_rate), self.precision('base_hour_rate'))
|
||||
self.set_net_total_in_words()
|
||||
|
||||
def calculate_component_amounts(self, component_type):
|
||||
if not getattr(self, '_salary_structure_doc', None):
|
||||
@ -976,8 +988,9 @@ class SalarySlip(TransactionBase):
|
||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
||||
if payment.total_payment > total_amount:
|
||||
frappe.throw(_("Row {0}: Paid amount {1} is greater than pending accrued amount {2}against loan {3}").format(
|
||||
payment.idx, frappe.bold(payment.total_payment),frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2} against loan {3}""")
|
||||
.format(payment.idx, frappe.bold(payment.total_payment),
|
||||
frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||
|
||||
self.total_interest_amount += payment.interest_amount
|
||||
self.total_principal_amount += payment.principal_amount
|
||||
@ -1072,6 +1085,46 @@ class SalarySlip(TransactionBase):
|
||||
self.get_working_days_details(lwp=self.leave_without_pay)
|
||||
self.calculate_net_pay()
|
||||
|
||||
def set_totals(self):
|
||||
self.gross_pay = 0
|
||||
if self.salary_slip_based_on_timesheet == 1:
|
||||
self.calculate_total_for_salary_slip_based_on_timesheet()
|
||||
else:
|
||||
self.total_deduction = 0
|
||||
if self.earnings:
|
||||
for earning in self.earnings:
|
||||
self.gross_pay += flt(earning.amount)
|
||||
if self.deductions:
|
||||
for deduction in self.deductions:
|
||||
self.total_deduction += flt(deduction.amount)
|
||||
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
|
||||
self.set_base_totals()
|
||||
|
||||
def set_base_totals(self):
|
||||
self.base_gross_pay = flt(self.gross_pay) * flt(self.exchange_rate)
|
||||
self.base_total_deduction = flt(self.total_deduction) * flt(self.exchange_rate)
|
||||
self.rounded_total = rounded(self.net_pay)
|
||||
self.base_net_pay = flt(self.net_pay) * flt(self.exchange_rate)
|
||||
self.base_rounded_total = rounded(self.base_net_pay)
|
||||
self.set_net_total_in_words()
|
||||
|
||||
#calculate total working hours, earnings based on hourly wages and totals
|
||||
def calculate_total_for_salary_slip_based_on_timesheet(self):
|
||||
if self.timesheets:
|
||||
for timesheet in self.timesheets:
|
||||
if timesheet.working_hours:
|
||||
self.total_working_hours += timesheet.working_hours
|
||||
|
||||
wages_amount = self.total_working_hours * self.hour_rate
|
||||
self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
|
||||
salary_component = frappe.db.get_value('Salary Structure', {'name': self.salary_structure}, 'salary_component')
|
||||
if self.earnings:
|
||||
for i, earning in enumerate(self.earnings):
|
||||
if earning.salary_component == salary_component:
|
||||
self.earnings[i].amount = wages_amount
|
||||
self.gross_pay += self.earnings[i].amount
|
||||
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
|
||||
|
||||
def unlink_ref_doc_from_salary_slip(ref_no):
|
||||
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
|
||||
where journal_entry=%s and docstatus < 2""", (ref_no))
|
||||
|
@ -33,7 +33,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
|
||||
frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75)
|
||||
|
||||
emp_id = make_employee("test_for_attendance@salary.com")
|
||||
emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
|
||||
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
|
||||
|
||||
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
|
||||
@ -55,7 +55,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
mark_attendance(emp_id, add_days(first_sunday, 4), 'On Leave', leave_type='Casual Leave', ignore_validate=True) # invalid lwp
|
||||
mark_attendance(emp_id, add_days(first_sunday, 7), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # invalid lwp
|
||||
|
||||
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
|
||||
ss = make_employee_salary_slip("test_payment_days_based_on_attendance@salary.com", "Monthly", "Test Payment Based On Attendence")
|
||||
|
||||
self.assertEqual(ss.leave_without_pay, 1.25)
|
||||
self.assertEqual(ss.absent_days, 1)
|
||||
@ -78,7 +78,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
# Payroll based on attendance
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
emp_id = make_employee("test_for_attendance@salary.com")
|
||||
emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
|
||||
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
|
||||
|
||||
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
|
||||
@ -108,7 +108,8 @@ class TestSalarySlip(unittest.TestCase):
|
||||
#two day leave ppl with fraction_of_daily_salary_per_leave = 0.5 equivalent to single day lwp
|
||||
make_leave_application(emp_id, add_days(first_sunday, 4), add_days(first_sunday, 5), "Test Partially Paid Leave")
|
||||
|
||||
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
|
||||
ss = make_employee_salary_slip("test_payment_days_based_on_leave_application@salary.com", "Monthly", "Test Payment Based On Leave Application")
|
||||
|
||||
|
||||
self.assertEqual(ss.leave_without_pay, 4)
|
||||
|
||||
@ -127,12 +128,12 @@ class TestSalarySlip(unittest.TestCase):
|
||||
def test_salary_slip_with_holidays_included(self):
|
||||
no_of_days = self.get_no_of_days()
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
||||
make_employee("test_employee@salary.com")
|
||||
make_employee("test_salary_slip_with_holidays_included@salary.com")
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
|
||||
{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
|
||||
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||
{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "status", "Active")
|
||||
ss = make_employee_salary_slip("test_salary_slip_with_holidays_included@salary.com", "Monthly", "Test Salary Slip With Holidays Included")
|
||||
|
||||
self.assertEqual(ss.total_working_days, no_of_days[0])
|
||||
self.assertEqual(ss.payment_days, no_of_days[0])
|
||||
@ -143,12 +144,12 @@ class TestSalarySlip(unittest.TestCase):
|
||||
def test_salary_slip_with_holidays_excluded(self):
|
||||
no_of_days = self.get_no_of_days()
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
||||
make_employee("test_employee@salary.com")
|
||||
make_employee("test_salary_slip_with_holidays_excluded@salary.com")
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
|
||||
{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
|
||||
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||
{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "status", "Active")
|
||||
ss = make_employee_salary_slip("test_salary_slip_with_holidays_excluded@salary.com", "Monthly", "Test Salary Slip With Holidays Excluded")
|
||||
|
||||
self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
|
||||
self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1])
|
||||
@ -163,7 +164,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
||||
|
||||
# set joinng date in the same month
|
||||
make_employee("test_employee@salary.com")
|
||||
make_employee("test_payment_days@salary.com")
|
||||
if getdate(nowdate()).day >= 15:
|
||||
relieving_date = getdate(add_days(nowdate(),-10))
|
||||
date_of_joining = getdate(add_days(nowdate(),-10))
|
||||
@ -178,39 +179,39 @@ class TestSalarySlip(unittest.TestCase):
|
||||
relieving_date = getdate(nowdate())
|
||||
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining)
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", date_of_joining)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
|
||||
|
||||
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||
ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", "Test Payment Days")
|
||||
|
||||
self.assertEqual(ss.total_working_days, no_of_days[0])
|
||||
self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1))
|
||||
|
||||
# set relieving date in the same month
|
||||
frappe.db.set_value("Employee",frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", relieving_date)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Left")
|
||||
ss.save()
|
||||
|
||||
self.assertEqual(ss.total_working_days, no_of_days[0])
|
||||
self.assertEqual(ss.payment_days, getdate(relieving_date).day)
|
||||
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
|
||||
frappe.db.set_value("Employee", frappe.get_value("Employee",
|
||||
{"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
|
||||
{"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
|
||||
|
||||
def test_employee_salary_slip_read_permission(self):
|
||||
make_employee("test_employee@salary.com")
|
||||
make_employee("test_employee_salary_slip_read_permission@salary.com")
|
||||
|
||||
salary_slip_test_employee = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||
frappe.set_user("test_employee@salary.com")
|
||||
salary_slip_test_employee = make_employee_salary_slip("test_employee_salary_slip_read_permission@salary.com", "Monthly", "Test Employee Salary Slip Read Permission")
|
||||
frappe.set_user("test_employee_salary_slip_read_permission@salary.com")
|
||||
self.assertTrue(salary_slip_test_employee.has_permission("read"))
|
||||
|
||||
def test_email_salary_slip(self):
|
||||
@ -218,8 +219,8 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 1)
|
||||
|
||||
make_employee("test_employee@salary.com")
|
||||
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
|
||||
make_employee("test_email_salary_slip@salary.com")
|
||||
ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
|
||||
ss.company = "_Test Company"
|
||||
ss.save()
|
||||
ss.submit()
|
||||
@ -230,8 +231,9 @@ class TestSalarySlip(unittest.TestCase):
|
||||
def test_loan_repayment_salary_slip(self):
|
||||
from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan, make_loan_disbursement_entry, create_loan_accounts
|
||||
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
applicant = make_employee("test_loanemployee@salary.com", company="_Test Company")
|
||||
applicant = make_employee("test_loan_repayment_salary_slip@salary.com", company="_Test Company")
|
||||
|
||||
create_loan_accounts()
|
||||
|
||||
@ -243,6 +245,8 @@ class TestSalarySlip(unittest.TestCase):
|
||||
interest_income_account='Interest Income Account - _TC',
|
||||
penalty_income_account='Penalty Income Account - _TC')
|
||||
|
||||
make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR')
|
||||
frappe.db.sql("""delete from `tabLoan""")
|
||||
loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
|
||||
loan.repay_from_salary = 1
|
||||
loan.submit()
|
||||
@ -251,7 +255,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
|
||||
|
||||
ss = make_employee_salary_slip("test_loanemployee@salary.com", "Monthly")
|
||||
ss = make_employee_salary_slip("test_loan_repayment_salary_slip@salary.com", "Monthly", "Test Loan Repayment Salary Structure")
|
||||
ss.submit()
|
||||
|
||||
self.assertEqual(ss.total_loan_repayment, 592)
|
||||
@ -264,7 +268,7 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
for payroll_frequency in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
|
||||
make_employee(payroll_frequency + "_test_employee@salary.com")
|
||||
ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency)
|
||||
ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency, payroll_frequency + "_Test Payroll Frequency")
|
||||
if payroll_frequency == "Monthly":
|
||||
self.assertEqual(ss.end_date, m['month_end_date'])
|
||||
elif payroll_frequency == "Bimonthly":
|
||||
@ -279,6 +283,18 @@ class TestSalarySlip(unittest.TestCase):
|
||||
elif payroll_frequency == "Daily":
|
||||
self.assertEqual(ss.end_date, nowdate())
|
||||
|
||||
def test_multi_currency_salary_slip(self):
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
applicant = make_employee("test_multi_currency_salary_slip@salary.com", company="_Test Company")
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name='Test Multi Currency Salary Slip'""")
|
||||
salary_structure = make_salary_structure("Test Multi Currency Salary Slip", "Monthly", employee=applicant, company="_Test Company", currency='USD')
|
||||
salary_slip = make_salary_slip(salary_structure.name, employee = applicant)
|
||||
salary_slip.exchange_rate = 70
|
||||
salary_slip.calculate_net_pay()
|
||||
|
||||
self.assertEqual(salary_slip.gross_pay, 78000)
|
||||
self.assertEqual(salary_slip.base_gross_pay, 78000*70)
|
||||
|
||||
def test_tax_for_payroll_period(self):
|
||||
data = {}
|
||||
# test the impact of tax exemption declaration, tax exemption proof submission
|
||||
@ -399,16 +415,21 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
|
||||
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
|
||||
|
||||
employee = frappe.db.get_value("Employee", {"user_id": user})
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
|
||||
salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
|
||||
if not frappe.db.exists('Salary Structure', salary_structure):
|
||||
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
|
||||
else:
|
||||
salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
|
||||
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
|
||||
|
||||
if not salary_slip:
|
||||
if not salary_slip_name:
|
||||
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
|
||||
salary_slip.employee_name = frappe.get_value("Employee",
|
||||
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
|
||||
salary_slip.payroll_frequency = payroll_frequency
|
||||
salary_slip.posting_date = nowdate()
|
||||
salary_slip.insert()
|
||||
else:
|
||||
salary_slip = frappe.get_doc('Salary Slip', salary_slip_name)
|
||||
|
||||
return salary_slip
|
||||
|
||||
@ -449,7 +470,7 @@ def get_salary_component_account(sal_comp, company_list=None):
|
||||
|
||||
sal_comp.append("accounts", {
|
||||
"company": d,
|
||||
"default_account": create_account(account_name, d, parent_account)
|
||||
"account": create_account(account_name, d, parent_account)
|
||||
})
|
||||
sal_comp.save()
|
||||
|
||||
@ -576,7 +597,8 @@ def create_exemption_declaration(employee, payroll_period):
|
||||
"doctype": "Employee Tax Exemption Declaration",
|
||||
"employee": employee,
|
||||
"payroll_period": payroll_period,
|
||||
"company": erpnext.get_default_company()
|
||||
"company": erpnext.get_default_company(),
|
||||
"currency": erpnext.get_default_currency()
|
||||
})
|
||||
declaration.append("declarations", {
|
||||
"exemption_sub_category": "_Test Sub Category",
|
||||
@ -591,7 +613,8 @@ def create_proof_submission(employee, payroll_period, amount):
|
||||
"doctype": "Employee Tax Exemption Proof Submission",
|
||||
"employee": employee,
|
||||
"payroll_period": payroll_period.name,
|
||||
"submission_date": submission_date
|
||||
"submission_date": submission_date,
|
||||
"currency": erpnext.get_default_currency()
|
||||
})
|
||||
proof_submission.append("tax_exemption_proofs", {
|
||||
"exemption_sub_category": "_Test Sub Category",
|
||||
@ -608,13 +631,13 @@ def create_benefit_claim(employee, payroll_period, amount, component):
|
||||
"employee": employee,
|
||||
"claimed_amount": amount,
|
||||
"claim_date": claim_date,
|
||||
"earning_component": component
|
||||
"earning_component": component,
|
||||
"currency": erpnext.get_default_currency()
|
||||
}).submit()
|
||||
return claim_date
|
||||
|
||||
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False):
|
||||
if frappe.db.exists("Income Tax Slab", "Tax Slab: " + payroll_period.name):
|
||||
return
|
||||
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()):
|
||||
frappe.db.sql("""delete from `tabIncome Tax Slab`""")
|
||||
|
||||
slabs = [
|
||||
{
|
||||
@ -637,6 +660,7 @@ def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption =
|
||||
income_tax_slab = frappe.new_doc("Income Tax Slab")
|
||||
income_tax_slab.name = "Tax Slab: " + payroll_period.name
|
||||
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
|
||||
income_tax_slab.currency = currency
|
||||
|
||||
if allow_tax_exemption:
|
||||
income_tax_slab.allow_tax_exemption = 1
|
||||
@ -687,7 +711,8 @@ def create_additional_salary(employee, payroll_period, amount):
|
||||
"salary_component": "Performance Bonus",
|
||||
"payroll_date": salary_date,
|
||||
"amount": amount,
|
||||
"type": "Earning"
|
||||
"type": "Earning",
|
||||
"currency": erpnext.get_default_currency()
|
||||
}).submit()
|
||||
return salary_date
|
||||
|
||||
|
@ -41,20 +41,6 @@ frappe.ui.form.on('Salary Structure', {
|
||||
|
||||
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet)
|
||||
|
||||
frm.set_query("salary_component", "earnings", function() {
|
||||
return {
|
||||
filters: {
|
||||
type: "earning"
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_query("salary_component", "deductions", function() {
|
||||
return {
|
||||
filters: {
|
||||
type: "deduction"
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_query("payment_account", function () {
|
||||
var account_types = ["Bank", "Cash"];
|
||||
return {
|
||||
@ -65,9 +51,48 @@ frappe.ui.form.on('Salary Structure', {
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.trigger('set_earning_deduction_component');
|
||||
},
|
||||
|
||||
set_earning_deduction_component: function(frm) {
|
||||
if(!frm.doc.currency && !frm.doc.company) return;
|
||||
frm.set_query("salary_component", "earnings", function() {
|
||||
return {
|
||||
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
|
||||
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
|
||||
};
|
||||
});
|
||||
frm.set_query("salary_component", "deductions", function() {
|
||||
return {
|
||||
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
|
||||
filters: {type: "deduction", currency: frm.doc.currency, company: frm.doc.company}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
currency: function(frm) {
|
||||
calculate_totals(frm.doc);
|
||||
frm.trigger("set_dynamic_labels")
|
||||
frm.trigger('set_earning_deduction_component');
|
||||
frm.refresh()
|
||||
},
|
||||
|
||||
set_dynamic_labels: function(frm) {
|
||||
frm.set_currency_labels(["net_pay","hour_rate", "leave_encashment_amount_per_day", "max_benefits", "total_earning",
|
||||
"total_deduction"], frm.doc.currency);
|
||||
|
||||
frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
|
||||
frm.doc.currency, "earnings");
|
||||
|
||||
frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
|
||||
frm.doc.currency, "deductions");
|
||||
|
||||
frm.refresh_fields();
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.trigger("set_dynamic_labels")
|
||||
frm.trigger("toggle_fields");
|
||||
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
|
||||
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
|
||||
@ -101,10 +126,12 @@ frappe.ui.form.on('Salary Structure', {
|
||||
fields: [
|
||||
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
|
||||
{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
|
||||
{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
|
||||
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
|
||||
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
|
||||
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
|
||||
{fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
|
||||
{fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
|
||||
{fieldname:"payroll_payable_account", fieldtype: "Link", options: "Account", filters: {"company": frm.doc.company, "root_type": "Liability", "is_group": 0, "account_currency": frm.doc.currency}, label: __("Payroll Payable Account")},
|
||||
{fieldname:'base_variable', fieldtype:'Section Break'},
|
||||
{fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1},
|
||||
{fieldname:'income_tax_slab', fieldtype:'Link', label: __('Income Tax Slab'), options: 'Income Tax Slab'},
|
||||
|
@ -13,6 +13,7 @@
|
||||
"column_break1",
|
||||
"is_active",
|
||||
"payroll_frequency",
|
||||
"currency",
|
||||
"is_default",
|
||||
"time_sheet_earning_detail",
|
||||
"salary_slip_based_on_timesheet",
|
||||
@ -26,9 +27,9 @@
|
||||
"deductions",
|
||||
"conditions_and_formula_variable_and_example",
|
||||
"net_pay_detail",
|
||||
"column_break2",
|
||||
"total_earning",
|
||||
"total_deduction",
|
||||
"column_break2",
|
||||
"net_pay",
|
||||
"account",
|
||||
"mode_of_payment",
|
||||
@ -43,23 +44,17 @@
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "letter_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Letter Head",
|
||||
"options": "Letter Head",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Letter Head"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
@ -72,9 +67,7 @@
|
||||
"oldfieldname": "is_active",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nYes\nNo",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Monthly",
|
||||
@ -82,9 +75,7 @@
|
||||
"fieldname": "payroll_frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Payroll Frequency",
|
||||
"options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily"
|
||||
},
|
||||
{
|
||||
"default": "No",
|
||||
@ -95,62 +86,46 @@
|
||||
"no_copy": 1,
|
||||
"options": "Yes\nNo",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time_sheet_earning_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "salary_slip_based_on_timesheet",
|
||||
"fieldtype": "Check",
|
||||
"label": "Salary Slip Based on Timesheet",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Salary Slip Based on Timesheet"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Salary Component for timesheet based payroll.",
|
||||
"fieldname": "salary_component",
|
||||
"fieldtype": "Link",
|
||||
"label": "Salary Component",
|
||||
"options": "Salary Component",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Salary Component"
|
||||
},
|
||||
{
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Hour Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "leave_encashment_amount_per_day",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Leave Encashment Amount Per Day",
|
||||
"options": "Company:company:default_currency",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "max_benefits",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Max Benefits (Amount)",
|
||||
"options": "Company:company:default_currency",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"description": "Salary breakup based on Earning and Deduction.",
|
||||
@ -158,9 +133,7 @@
|
||||
"fieldtype": "Section Break",
|
||||
"oldfieldname": "earning_deduction",
|
||||
"oldfieldtype": "Section Break",
|
||||
"precision": "2",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"fieldname": "earnings",
|
||||
@ -168,9 +141,7 @@
|
||||
"label": "Earnings",
|
||||
"oldfieldname": "earning_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Salary Detail",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Salary Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "deductions",
|
||||
@ -178,22 +149,16 @@
|
||||
"label": "Deductions",
|
||||
"oldfieldname": "deduction_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Salary Detail",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Salary Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "net_pay_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "Simple",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Simple"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
@ -201,63 +166,45 @@
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Total Earning",
|
||||
"oldfieldname": "total_earning",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_deduction",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Total Deduction",
|
||||
"oldfieldname": "total_deduction",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "net_pay",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Net Pay",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Account",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_28",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Account",
|
||||
"options": "Account",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -266,23 +213,26 @@
|
||||
"no_copy": 1,
|
||||
"options": "Salary Structure",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "conditions_and_formula_variable_and_example",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Conditions and Formula variable and example",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Conditions and Formula variable and example"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 17:07:26.129355",
|
||||
"modified": "2020-09-30 11:30:32.190798",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Structure",
|
||||
|
@ -2,7 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe, erpnext
|
||||
|
||||
from frappe.utils import flt, cint, cstr
|
||||
from frappe import _
|
||||
@ -88,24 +88,26 @@ class SalaryStructure(Document):
|
||||
return employees
|
||||
|
||||
@frappe.whitelist()
|
||||
def assign_salary_structure(self, company=None, grade=None, department=None, designation=None,employee=None,
|
||||
from_date=None, base=None, variable=None, income_tax_slab=None):
|
||||
employees = self.get_employees(company= company, grade= grade,department= department,designation= designation,name=employee)
|
||||
def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
|
||||
payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
|
||||
employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
|
||||
|
||||
if employees:
|
||||
if len(employees) > 20:
|
||||
frappe.enqueue(assign_salary_structure_for_employees, timeout=600,
|
||||
employees=employees, salary_structure=self,from_date=from_date,
|
||||
base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
employees=employees, salary_structure=self,
|
||||
payroll_payable_account=payroll_payable_account,
|
||||
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
else:
|
||||
assign_salary_structure_for_employees(employees, self, from_date=from_date,
|
||||
base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
assign_salary_structure_for_employees(employees, self,
|
||||
payroll_payable_account=payroll_payable_account,
|
||||
from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
|
||||
else:
|
||||
frappe.msgprint(_("No Employee Found"))
|
||||
|
||||
|
||||
|
||||
def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None, variable=None, income_tax_slab=None):
|
||||
def assign_salary_structure_for_employees(employees, salary_structure, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
|
||||
salary_structures_assignments = []
|
||||
existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date)
|
||||
count=0
|
||||
@ -115,7 +117,7 @@ def assign_salary_structure_for_employees(employees, salary_structure, from_date
|
||||
count +=1
|
||||
|
||||
salary_structures_assignment = create_salary_structures_assignment(employee,
|
||||
salary_structure, from_date, base, variable, income_tax_slab)
|
||||
salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab)
|
||||
salary_structures_assignments.append(salary_structures_assignment)
|
||||
frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures..."))
|
||||
|
||||
@ -123,11 +125,22 @@ def assign_salary_structure_for_employees(employees, salary_structure, from_date
|
||||
frappe.msgprint(_("Structures have been assigned successfully"))
|
||||
|
||||
|
||||
def create_salary_structures_assignment(employee, salary_structure, from_date, base, variable, income_tax_slab=None):
|
||||
def create_salary_structures_assignment(employee, salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab=None):
|
||||
if not payroll_payable_account:
|
||||
payroll_payable_account = frappe.db.get_value('Company', salary_structure.company, 'default_payroll_payable_account')
|
||||
if not payroll_payable_account:
|
||||
frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults'))
|
||||
payroll_payable_account_currency = frappe.db.get_value('Account', payroll_payable_account, 'account_currency')
|
||||
company_curency = erpnext.get_company_currency(salary_structure.company)
|
||||
if payroll_payable_account_currency != salary_structure.currency and payroll_payable_account_currency != company_curency:
|
||||
frappe.throw(_("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format(salary_structure.currency, company_curency))
|
||||
|
||||
assignment = frappe.new_doc("Salary Structure Assignment")
|
||||
assignment.employee = employee
|
||||
assignment.salary_structure = salary_structure.name
|
||||
assignment.company = salary_structure.company
|
||||
assignment.currency = salary_structure.currency
|
||||
assignment.payroll_payable_account = payroll_payable_account
|
||||
assignment.from_date = from_date
|
||||
assignment.base = base
|
||||
assignment.variable = variable
|
||||
@ -170,7 +183,8 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
|
||||
"doctype": "Salary Slip",
|
||||
"field_map": {
|
||||
"total_earning": "gross_pay",
|
||||
"name": "salary_structure"
|
||||
"name": "salary_structure",
|
||||
"currency": "currency"
|
||||
}
|
||||
}
|
||||
}, target_doc, postprocess, ignore_child_tables=True, ignore_permissions=ignore_permissions)
|
||||
@ -188,7 +202,22 @@ def get_employees(salary_structure):
|
||||
filters={'salary_structure': salary_structure, 'docstatus': 1}, fields=['employee'])
|
||||
|
||||
if not employees:
|
||||
frappe.throw(_("There's no Employee with Salary Structure: {0}. \
|
||||
Assign {1} to an Employee to preview Salary Slip").format(salary_structure, salary_structure))
|
||||
frappe.throw(_("There's no Employee with Salary Structure: {0}. Assign {1} to an Employee to preview Salary Slip").format(
|
||||
salary_structure, salary_structure))
|
||||
|
||||
return list(set([d.employee for d in employees]))
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
|
||||
if len(filters) < 3:
|
||||
return {}
|
||||
|
||||
return frappe.db.sql("""
|
||||
select t1.salary_component
|
||||
from `tabSalary Component` t1, `tabSalary Component Account` t2
|
||||
where t1.salary_component = t2.parent
|
||||
and t1.type = %s
|
||||
and t2.company = %s
|
||||
order by salary_component
|
||||
""", (filters['type'], filters['company']) )
|
||||
|
@ -94,7 +94,8 @@ class TestSalaryStructure(unittest.TestCase):
|
||||
self.assertFalse(("\n" in row.formula) or ("\n" in row.condition))
|
||||
|
||||
def test_salary_structures_assignment(self):
|
||||
salary_structure = make_salary_structure("Salary Structure Sample", "Monthly")
|
||||
company_currency = erpnext.get_default_currency()
|
||||
salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", currency=company_currency)
|
||||
employee = "test_assign_stucture@salary.com"
|
||||
employee_doc_name = make_employee(employee)
|
||||
# clear the already assigned stuctures
|
||||
@ -107,8 +108,13 @@ class TestSalaryStructure(unittest.TestCase):
|
||||
self.assertEqual(salary_structure_assignment.base, 5000)
|
||||
self.assertEqual(salary_structure_assignment.variable, 200)
|
||||
|
||||
def test_multi_currency_salary_structure(self):
|
||||
make_employee("test_muti_currency_employee@salary.com")
|
||||
sal_struct = make_salary_structure("Salary Structure Multi Currency", "Monthly", currency='USD')
|
||||
self.assertEqual(sal_struct.currency, 'USD')
|
||||
|
||||
def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
|
||||
test_tax=False, company=None):
|
||||
test_tax=False, company=None, currency=erpnext.get_default_currency()):
|
||||
if test_tax:
|
||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
|
||||
|
||||
@ -120,7 +126,8 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
|
||||
"earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account")
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
details.update(other_details)
|
||||
@ -134,16 +141,16 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
|
||||
|
||||
if employee and not frappe.db.get_value("Salary Structure Assignment",
|
||||
{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
|
||||
create_salary_structure_assignment(employee, salary_structure, company=company)
|
||||
create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency)
|
||||
|
||||
return salary_structure_doc
|
||||
|
||||
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None):
|
||||
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()):
|
||||
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
|
||||
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
|
||||
|
||||
payroll_period = create_payroll_period()
|
||||
create_tax_slab(payroll_period, allow_tax_exemption=True)
|
||||
create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
|
||||
|
||||
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
|
||||
salary_structure_assignment.employee = employee
|
||||
@ -151,8 +158,15 @@ def create_salary_structure_assignment(employee, salary_structure, from_date=Non
|
||||
salary_structure_assignment.variable = 5000
|
||||
salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1)
|
||||
salary_structure_assignment.salary_structure = salary_structure
|
||||
salary_structure_assignment.currency = currency
|
||||
salary_structure_assignment.payroll_payable_account = get_payable_account(company)
|
||||
salary_structure_assignment.company = company or erpnext.get_default_company()
|
||||
salary_structure_assignment.save(ignore_permissions=True)
|
||||
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
|
||||
salary_structure_assignment.submit()
|
||||
return salary_structure_assignment
|
||||
|
||||
def get_payable_account(company=None):
|
||||
if not company:
|
||||
company = erpnext.get_default_company()
|
||||
return frappe.db.get_value("Company", company, "default_payroll_payable_account")
|
@ -6,9 +6,6 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
frm.set_query("employee", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_query("salary_structure", function() {
|
||||
@ -26,11 +23,25 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
docstatus: 1,
|
||||
disabled: 0
|
||||
disabled: 0,
|
||||
currency: frm.doc.currency
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("payroll_payable_account", function() {
|
||||
var company_currency = erpnext.get_currency(frm.doc.company);
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"root_type": "Liability",
|
||||
"is_group": 0,
|
||||
"account_currency": ["in", [frm.doc.currency, company_currency]],
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if(frm.doc.employee){
|
||||
frappe.call({
|
||||
@ -52,5 +63,13 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
else{
|
||||
frm.set_value("company", null);
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
if (frm.doc.company) {
|
||||
frappe.db.get_value("Company", frm.doc.company, "default_payroll_payable_account", (r) => {
|
||||
frm.set_value("payroll_payable_account", r.default_payroll_payable_account);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -11,11 +11,13 @@
|
||||
"employee_name",
|
||||
"department",
|
||||
"company",
|
||||
"payroll_payable_account",
|
||||
"column_break_6",
|
||||
"designation",
|
||||
"salary_structure",
|
||||
"from_date",
|
||||
"income_tax_slab",
|
||||
"currency",
|
||||
"section_break_7",
|
||||
"base",
|
||||
"column_break_9",
|
||||
@ -94,7 +96,7 @@
|
||||
"fieldname": "base",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Base",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
@ -104,7 +106,7 @@
|
||||
"fieldname": "variable",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Variable",
|
||||
"options": "Company:company:default_currency"
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@ -116,15 +118,35 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "salary_structure",
|
||||
"fieldname": "income_tax_slab",
|
||||
"fieldtype": "Link",
|
||||
"label": "Income Tax Slab",
|
||||
"options": "Income Tax Slab"
|
||||
},
|
||||
{
|
||||
"default": "Company:company:default_currency",
|
||||
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
|
||||
"fetch_from": "salary_structure.currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "employee",
|
||||
"fieldname": "payroll_payable_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payroll Payable Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 19:58:09.964692",
|
||||
"modified": "2020-11-30 18:07:48.251311",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Structure Assignment",
|
||||
|
@ -13,6 +13,8 @@ class DuplicateAssignment(frappe.ValidationError): pass
|
||||
class SalaryStructureAssignment(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_income_tax_slab()
|
||||
self.set_payroll_payable_account()
|
||||
|
||||
def validate_dates(self):
|
||||
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
|
||||
@ -31,6 +33,24 @@ class SalaryStructureAssignment(Document):
|
||||
frappe.throw(_("From Date {0} cannot be after employee's relieving Date {1}")
|
||||
.format(self.from_date, relieving_date))
|
||||
|
||||
def validate_income_tax_slab(self):
|
||||
if not self.income_tax_slab:
|
||||
return
|
||||
|
||||
income_tax_slab_currency = frappe.db.get_value('Income Tax Slab', self.income_tax_slab, 'currency')
|
||||
if self.currency != income_tax_slab_currency:
|
||||
frappe.throw(_("Currency of selected Income Tax Slab should be {0} instead of {1}").format(self.currency, income_tax_slab_currency))
|
||||
|
||||
def set_payroll_payable_account(self):
|
||||
if not self.payroll_payable_account:
|
||||
payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payable_account')
|
||||
if not payroll_payable_account:
|
||||
payroll_payable_account = frappe.db.get_value(
|
||||
"Account", {
|
||||
"account_name": _("Payroll Payable"), "company": self.company, "account_currency": frappe.db.get_value(
|
||||
"Company", self.company, "default_currency"), "is_group": 0})
|
||||
self.payroll_payable_account = payroll_payable_account
|
||||
|
||||
def get_assigned_salary_structure(employee, on_date):
|
||||
if not employee or not on_date:
|
||||
return None
|
||||
@ -43,3 +63,10 @@ def get_assigned_salary_structure(employee, on_date):
|
||||
'on_date': on_date,
|
||||
})
|
||||
return salary_structure[0][0] if salary_structure else None
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_employee_currency(employee):
|
||||
employee_currency = frappe.db.get_value('Salary Structure Assignment', {'employee': employee}, 'currency')
|
||||
if not employee_currency:
|
||||
frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(employee))
|
||||
return employee_currency
|
@ -19,13 +19,15 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "From Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "To Amount"
|
||||
"label": "To Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -53,7 +55,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 18:16:07.596493",
|
||||
"modified": "2020-10-19 13:44:39.549337",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Taxable Salary Slab",
|
||||
|
@ -8,34 +8,48 @@ frappe.query_reports["Salary Register"] = {
|
||||
"label": __("From"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(),-1),
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"options": "Currency",
|
||||
"label": __("Currency"),
|
||||
"default": erpnext.get_currency(frappe.defaults.get_default("Company")),
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname":"employee",
|
||||
"label": __("Employee"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee"
|
||||
"options": "Employee",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"width": "100px",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"docstatus",
|
||||
"label":__("Document Status"),
|
||||
"fieldtype":"Select",
|
||||
"options":["Draft", "Submitted", "Cancelled"],
|
||||
"default":"Submitted"
|
||||
"default": "Submitted",
|
||||
"width": "100px"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -2,18 +2,22 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe, erpnext
|
||||
from frappe.utils import flt
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
salary_slips = get_salary_slips(filters)
|
||||
currency = None
|
||||
if filters.get('currency'):
|
||||
currency = filters.get('currency')
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
salary_slips = get_salary_slips(filters, company_currency)
|
||||
if not salary_slips: return [], []
|
||||
|
||||
columns, earning_types, ded_types = get_columns(salary_slips)
|
||||
ss_earning_map = get_ss_earning_map(salary_slips)
|
||||
ss_ded_map = get_ss_ded_map(salary_slips)
|
||||
ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency)
|
||||
ss_ded_map = get_ss_ded_map(salary_slips,currency, company_currency)
|
||||
doj_map = get_employee_doj_map()
|
||||
|
||||
data = []
|
||||
@ -21,24 +25,30 @@ def execute(filters=None):
|
||||
row = [ss.name, ss.employee, ss.employee_name, doj_map.get(ss.employee), ss.branch, ss.department, ss.designation,
|
||||
ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
|
||||
|
||||
if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
|
||||
if not ss.department == None: columns[4] = columns[4].replace('-1','120')
|
||||
if not ss.designation == None: columns[5] = columns[5].replace('-1','120')
|
||||
if not ss.leave_without_pay == None: columns[9] = columns[9].replace('-1','130')
|
||||
if ss.branch is not None: columns[3] = columns[3].replace('-1','120')
|
||||
if ss.department is not None: columns[4] = columns[4].replace('-1','120')
|
||||
if ss.designation is not None: columns[5] = columns[5].replace('-1','120')
|
||||
if ss.leave_without_pay is not None: columns[9] = columns[9].replace('-1','130')
|
||||
|
||||
|
||||
for e in earning_types:
|
||||
row.append(ss_earning_map.get(ss.name, {}).get(e))
|
||||
|
||||
row += [ss.gross_pay]
|
||||
if currency == company_currency:
|
||||
row += [flt(ss.gross_pay) * flt(ss.exchange_rate)]
|
||||
else:
|
||||
row += [ss.gross_pay]
|
||||
|
||||
for d in ded_types:
|
||||
row.append(ss_ded_map.get(ss.name, {}).get(d))
|
||||
|
||||
row.append(ss.total_loan_repayment)
|
||||
|
||||
row += [ss.total_deduction, ss.net_pay]
|
||||
|
||||
if currency == company_currency:
|
||||
row += [flt(ss.total_deduction) * flt(ss.exchange_rate), flt(ss.net_pay) * flt(ss.exchange_rate)]
|
||||
else:
|
||||
row += [ss.total_deduction, ss.net_pay]
|
||||
row.append(currency or company_currency)
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
@ -46,10 +56,19 @@ def execute(filters=None):
|
||||
def get_columns(salary_slips):
|
||||
"""
|
||||
columns = [
|
||||
_("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
|
||||
_("Date of Joining") + "::80", _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
|
||||
_("Designation") + ":Link/Designation:120", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
|
||||
_("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Payment Days") + ":Float:120"
|
||||
_("Salary Slip ID") + ":Link/Salary Slip:150",
|
||||
_("Employee") + ":Link/Employee:120",
|
||||
_("Employee Name") + "::140",
|
||||
_("Date of Joining") + "::80",
|
||||
_("Branch") + ":Link/Branch:120",
|
||||
_("Department") + ":Link/Department:120",
|
||||
_("Designation") + ":Link/Designation:120",
|
||||
_("Company") + ":Link/Company:120",
|
||||
_("Start Date") + "::80",
|
||||
_("End Date") + "::80",
|
||||
_("Leave Without Pay") + ":Float:130",
|
||||
_("Payment Days") + ":Float:120",
|
||||
_("Currency") + ":Link/Currency:80"
|
||||
]
|
||||
"""
|
||||
columns = [
|
||||
@ -73,15 +92,15 @@ def get_columns(salary_slips):
|
||||
|
||||
return columns, salary_components[_("Earning")], salary_components[_("Deduction")]
|
||||
|
||||
def get_salary_slips(filters):
|
||||
def get_salary_slips(filters, company_currency):
|
||||
filters.update({"from_date": filters.get("from_date"), "to_date":filters.get("to_date")})
|
||||
conditions, filters = get_conditions(filters)
|
||||
conditions, filters = get_conditions(filters, company_currency)
|
||||
salary_slips = frappe.db.sql("""select * from `tabSalary Slip` where %s
|
||||
order by employee""" % conditions, filters, as_dict=1)
|
||||
|
||||
return salary_slips or []
|
||||
|
||||
def get_conditions(filters):
|
||||
def get_conditions(filters, company_currency):
|
||||
conditions = ""
|
||||
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
|
||||
|
||||
@ -92,6 +111,8 @@ def get_conditions(filters):
|
||||
if filters.get("to_date"): conditions += " and end_date <= %(to_date)s"
|
||||
if filters.get("company"): conditions += " and company = %(company)s"
|
||||
if filters.get("employee"): conditions += " and employee = %(employee)s"
|
||||
if filters.get("currency") and filters.get("currency") != company_currency:
|
||||
conditions += " and currency = %(currency)s"
|
||||
|
||||
return conditions, filters
|
||||
|
||||
@ -103,26 +124,32 @@ def get_employee_doj_map():
|
||||
FROM `tabEmployee`
|
||||
"""))
|
||||
|
||||
def get_ss_earning_map(salary_slips):
|
||||
ss_earnings = frappe.db.sql("""select parent, salary_component, amount
|
||||
from `tabSalary Detail` where parent in (%s)""" %
|
||||
def get_ss_earning_map(salary_slips, currency, company_currency):
|
||||
ss_earnings = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
|
||||
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
|
||||
|
||||
ss_earning_map = {}
|
||||
for d in ss_earnings:
|
||||
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
|
||||
ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
|
||||
if currency == company_currency:
|
||||
ss_earning_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
|
||||
else:
|
||||
ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
|
||||
|
||||
return ss_earning_map
|
||||
|
||||
def get_ss_ded_map(salary_slips):
|
||||
ss_deductions = frappe.db.sql("""select parent, salary_component, amount
|
||||
from `tabSalary Detail` where parent in (%s)""" %
|
||||
def get_ss_ded_map(salary_slips, currency, company_currency):
|
||||
ss_deductions = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
|
||||
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
|
||||
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
|
||||
|
||||
ss_ded_map = {}
|
||||
for d in ss_deductions:
|
||||
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
|
||||
ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
|
||||
if currency == company_currency:
|
||||
ss_ded_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
|
||||
else:
|
||||
ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
|
||||
|
||||
return ss_ded_map
|
||||
|
Loading…
x
Reference in New Issue
Block a user