Merge branch 'version-12-hotfix' into commonify-warehouse-autofill-hotfix

This commit is contained in:
Marica
2020-04-29 11:36:48 +05:30
committed by GitHub
94 changed files with 2712 additions and 3679 deletions

View File

@@ -9,6 +9,7 @@ from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowd
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class BudgetError(frappe.ValidationError): pass class BudgetError(frappe.ValidationError): pass
class DuplicateBudgetError(frappe.ValidationError): pass class DuplicateBudgetError(frappe.ValidationError): pass
@@ -98,30 +99,32 @@ def validate_expense_against_budget(args):
if not (args.get('account') and args.get('cost_center')) and args.item_code: if not (args.get('account') and args.get('cost_center')) and args.item_code:
args.cost_center, args.account = get_item_details(args) args.cost_center, args.account = get_item_details(args)
if not (args.cost_center or args.project) and not args.account: if not args.account:
return return
for budget_against in ['project', 'cost_center']: for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
if (args.get(budget_against) and args.account if (args.get(budget_against) and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})): and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
if args.project and budget_against == 'project': doctype = frappe.unscrub(budget_against)
condition = "and b.project=%s" % frappe.db.escape(args.project)
args.budget_against_field = "Project"
elif args.cost_center and budget_against == 'cost_center': if frappe.get_cached_value('DocType', doctype, 'is_tree'):
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tabCost Center` condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt) where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
args.budget_against_field = "Cost Center" args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
args.is_tree = False
args.budget_against = args.get(budget_against) args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql(""" budget_records = frappe.db.sql("""
select select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution, b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request, ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order,0) as for_purchase_order, ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded, b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded,
b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr, b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr,
@@ -132,9 +135,7 @@ def validate_expense_against_budget(args):
b.name=ba.parent and b.fiscal_year=%s b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1 and ba.account=%s and b.docstatus=1
{condition} {condition}
""".format(condition=condition, """.format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
budget_against_field=frappe.scrub(args.get("budget_against_field"))),
(args.fiscal_year, args.account), as_dict=True)
if budget_records: if budget_records:
validate_budget_records(args, budget_records) validate_budget_records(args, budget_records)
@@ -230,10 +231,10 @@ def get_ordered_amount(args, budget):
def get_other_condition(args, budget, for_doc): def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account) condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = frappe.scrub(args.get("budget_against_field")) budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field): if budget_against_field and args.get(budget_against_field):
condition += " and child.%s = '%s'" %(budget_against_field, args.get(budget_against_field)) condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
if args.get('fiscal_year'): if args.get('fiscal_year'):
date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date' date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date'
@@ -246,19 +247,30 @@ def get_other_condition(args, budget, for_doc):
return condition return condition
def get_actual_expense(args): def get_actual_expense(args):
if not args.budget_against_doctype:
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
budget_against_field = args.get('budget_against_field')
condition1 = " and gle.posting_date <= %(month_end_date)s" \ condition1 = " and gle.posting_date <= %(month_end_date)s" \
if args.get("month_end_date") else "" if args.get("month_end_date") else ""
if args.budget_against_field == "Cost Center":
lft_rgt = frappe.db.get_value(args.budget_against_field, if args.is_tree:
args.budget_against, ["lft", "rgt"], as_dict=1) lft_rgt = frappe.db.get_value(args.budget_against_doctype,
args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
args.update(lft_rgt) args.update(lft_rgt)
condition2 = """and exists(select name from `tabCost Center`
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
elif args.budget_against_field == "Project": condition2 = """and exists(select name from `tab{doctype}`
condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)" where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
budget_against_field=budget_against_field)
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
budget_against = budget_against_field)
return flt(frappe.db.sql(""" amount = flt(frappe.db.sql("""
select sum(gle.debit) - sum(gle.credit) select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle from `tabGL Entry` gle
where gle.account=%(account)s where gle.account=%(account)s
@@ -267,7 +279,9 @@ def get_actual_expense(args):
and gle.company=%(company)s and gle.company=%(company)s
and gle.docstatus=1 and gle.docstatus=1
{condition2} {condition2}
""".format(condition1=condition1, condition2=condition2), (args))[0][0]) """.format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
return amount
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {} distribution = {}

View File

@@ -13,7 +13,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase): class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self): def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -26,7 +26,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_crossed_stop1(self): def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -41,7 +41,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_exception_approver_role(self): def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -114,7 +114,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_crossed_stop2(self): def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -129,7 +129,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop1(self): def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -141,7 +141,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop2(self): def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -153,7 +153,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation1(self): def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -177,7 +177,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation2(self): def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -201,8 +201,8 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_against_group_cost_center(self): def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC") set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
@@ -241,25 +241,30 @@ class TestBudget(unittest.TestCase):
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project": if budget_against_field == "project":
budget_against = "_Test Project" budget_against = "_Test Project"
else: else:
budget_against = budget_against_CC or "_Test Cost Center - _TC" budget_against = budget_against_CC or "_Test Cost Center - _TC"
existing_expense = get_actual_expense(frappe._dict({
args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date, "monthly_end_date": posting_date,
"company": "_Test Company", "company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",
"budget_against_field": budget_against_field, "budget_against_field": budget_against_field,
"budget_against": budget_against })
}))
if not args.get(budget_against_field):
args[budget_against_field] = budget_against
existing_expense = get_actual_expense(args)
if existing_expense: if existing_expense:
if budget_against_field == "Cost Center": if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
elif budget_against_field == "Project": elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28")

View File

@@ -104,6 +104,21 @@ frappe.ui.form.on('Payment Entry', {
}; };
}); });
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
payment_term_list = payment_term_list.map(pt => pt.payment_term);
return {
filters: {
'name': ['in', payment_term_list]
}
}
}
});
frm.set_query("reference_name", "references", function(doc, cdt, cdn) { frm.set_query("reference_name", "references", function(doc, cdt, cdn) {
const child = locals[cdt][cdn]; const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company}; const filters = {"docstatus": 1, "company": doc.company};
@@ -1018,4 +1033,4 @@ frappe.ui.form.on('Payment Entry', {
}); });
} }
}, },
}) })

View File

@@ -71,9 +71,9 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.update_payment_schedule()
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.setup_party_account_field() self.setup_party_account_field()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
@@ -81,6 +81,7 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.delink_advance_entry_references() self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.set_payment_req_status() self.set_payment_req_status()
self.set_status() self.set_status()
@@ -94,10 +95,10 @@ class PaymentEntry(AccountsController):
def validate_duplicate_entry(self): def validate_duplicate_entry(self):
reference_names = [] reference_names = []
for d in self.get("references"): for d in self.get("references"):
if (d.reference_doctype, d.reference_name) in reference_names: if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}") frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}")
.format(d.idx, d.reference_doctype, d.reference_name)) .format(d.idx, d.reference_doctype, d.reference_name))
reference_names.append((d.reference_doctype, d.reference_name)) reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
def set_bank_account_data(self): def set_bank_account_data(self):
if self.bank_account: if self.bank_account:
@@ -285,6 +286,36 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry") frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.reference_name, dr_or_cr)) .format(d.reference_name, dr_or_cr))
def update_payment_schedule(self, cancel=0):
invoice_payment_amount_map = {}
invoice_paid_amount_map = {}
for reference in self.get('references'):
if reference.payment_term and reference.reference_name:
key = (reference.payment_term, reference.reference_name)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += reference.allocated_amount
if not invoice_paid_amount_map.get(reference.reference_name):
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
fields=['paid_amount', 'payment_amount', 'payment_term'])
for term in payment_schedule:
invoice_key = (term.payment_term, reference.reference_name)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
for key, amount in iteritems(invoice_payment_amount_map):
if cancel:
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
else:
outstanding = invoice_paid_amount_map.get(key)['outstanding']
if amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
def set_status(self): def set_status(self):
if self.docstatus == 2: if self.docstatus == 2:
self.status = 'Cancelled' self.status = 'Cancelled'
@@ -1012,15 +1043,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date))) frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date)))
else: else:
pe.append("references", { if (doc.doctype in ('Sales Invoice', 'Purchase Invoice')
'reference_doctype': dt, and frappe.get_value('Payment Terms Template',
'reference_name': dn, {'name': doc.payment_terms_template}, 'allocate_payment_based_on_payment_terms')):
"bill_no": doc.get("bill_no"),
"due_date": doc.get("due_date"), for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
'total_amount': grand_total, pe.append('references', reference)
'outstanding_amount': outstanding_amount, else:
'allocated_amount': outstanding_amount pe.append("references", {
}) 'reference_doctype': dt,
'reference_name': dn,
"bill_no": doc.get("bill_no"),
"due_date": doc.get("due_date"),
'total_amount': grand_total,
'outstanding_amount': outstanding_amount,
'allocated_amount': outstanding_amount
})
pe.setup_party_account_field() pe.setup_party_account_field()
pe.set_missing_values() pe.set_missing_values()
@@ -1029,6 +1067,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.set_amounts() pe.set_amounts()
return pe return pe
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
references.append({
'reference_doctype': dt,
'reference_name': dn,
'bill_no': doc.get('bill_no'),
'due_date': doc.get('due_date'),
'total_amount': grand_total,
'outstanding_amount': outstanding_amount,
'payment_term': payment_term.payment_term,
'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
payment_term.precision('payment_amount'))
})
return references
def get_paid_amount(dt, dn, party_type, party, account, due_date): def get_paid_amount(dt, dn, party_type, party, account, due_date):
if party_type=="Customer": if party_type=="Customer":

View File

@@ -149,6 +149,30 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_payment_terms(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template()
si.payment_terms_template = 'Test Receivable Template'
si.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18
})
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
pe.submit()
si.load_from_db()
self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_against_sales_invoice_to_check_status(self): def test_payment_against_sales_invoice_to_check_status(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
@@ -609,4 +633,38 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(expected_party_account_balance, party_account_balance) self.assertEqual(expected_party_account_balance, party_account_balance)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save() accounts_settings.save()
def create_payment_terms_template():
create_payment_term('Basic Amount Receivable')
create_payment_term('Tax Receivable')
if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
payment_term_template = frappe.get_doc({
'doctype': 'Payment Terms Template',
'template_name': 'Test Receivable Template',
'allocate_payment_based_on_payment_terms': 1,
'terms': [{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Basic Amount Receivable',
'invoice_portion': 84.746,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 1
},
{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Tax Receivable',
'invoice_portion': 15.254,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 2
}]
}).insert()
def create_payment_term(name):
if not frappe.db.exists('Payment Term', name):
frappe.get_doc({
'doctype': 'Payment Term',
'payment_term_name': name
}).insert()

View File

@@ -1,343 +1,107 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-06-01 16:55:32.196722", "creation": "2016-06-01 16:55:32.196722",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"reference_doctype",
"reference_name",
"due_date",
"bill_no",
"payment_term",
"column_break_4",
"total_amount",
"outstanding_amount",
"allocated_amount",
"exchange_rate"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "reference_doctype", "fieldname": "reference_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Type", "label": "Type",
"length": 0,
"no_copy": 0,
"options": "DocType", "options": "DocType",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Name", "label": "Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype", "options": "reference_doctype",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "due_date", "fieldname": "due_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Due Date", "label": "Due Date",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "bill_no", "fieldname": "bill_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier Invoice No", "label": "Supplier Invoice No",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0, "read_only": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "total_amount", "fieldname": "total_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Amount", "label": "Total Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "outstanding_amount", "fieldname": "outstanding_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Outstanding", "label": "Outstanding",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "allocated_amount", "fieldname": "allocated_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Allocated"
"label": "Allocated",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')", "depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
"fetch_if_empty": 0,
"fieldname": "exchange_rate", "fieldname": "exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Exchange Rate", "label": "Exchange Rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1, },
"remember_last_selected_value": 0, {
"report_hide": 0, "fieldname": "payment_term",
"reqd": 0, "fieldtype": "Link",
"search_index": 0, "label": "Payment Term",
"set_only_once": 0, "options": "Payment Term"
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "links": [],
"modified": "2019-05-01 13:24:56.586677", "modified": "2020-03-13 12:07:19.362539",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@@ -326,7 +326,7 @@ def make_payment_request(**args):
"reference_doctype": args.dt, "reference_doctype": args.dt,
"reference_name": args.dn, "reference_name": args.dn,
"party_type": args.get("party_type") or "Customer", "party_type": args.get("party_type") or "Customer",
"party": args.get("party") or ref_doc.customer, "party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account "bank_account": bank_account
}) })
@@ -420,7 +420,7 @@ def make_payment_entry(docname):
def update_payment_req_status(doc, method): def update_payment_req_status(doc, method):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
for ref in doc.references: for ref in doc.references:
payment_request_name = frappe.db.get_value("Payment Request", payment_request_name = frappe.db.get_value("Payment Request",
{"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name, {"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name,
@@ -430,7 +430,7 @@ def update_payment_req_status(doc, method):
ref_details = get_reference_details(ref.reference_doctype, ref.reference_name, doc.party_account_currency) ref_details = get_reference_details(ref.reference_doctype, ref.reference_name, doc.party_account_currency)
pay_req_doc = frappe.get_doc('Payment Request', payment_request_name) pay_req_doc = frappe.get_doc('Payment Request', payment_request_name)
status = pay_req_doc.status status = pay_req_doc.status
if status != "Paid" and not ref_details.outstanding_amount: if status != "Paid" and not ref_details.outstanding_amount:
status = 'Paid' status = 'Paid'
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount: elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:

View File

@@ -1,243 +1,82 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "creation": "2017-08-10 15:38:00.080575",
"allow_import": 0, "doctype": "DocType",
"allow_rename": 0, "editable_grid": 1,
"autoname": "", "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2017-08-10 15:38:00.080575", "payment_term",
"custom": 0, "description",
"docstatus": 0, "due_date",
"doctype": "DocType", "invoice_portion",
"document_type": "", "payment_amount",
"editable_grid": 1, "mode_of_payment",
"engine": "InnoDB", "paid_amount"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "payment_term",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Payment Term",
"columns": 2, "options": "Payment Term",
"fieldname": "payment_term", "print_hide": 1
"fieldtype": "Link", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Term",
"length": 0,
"no_copy": 0,
"options": "Payment Term",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "description",
"allow_on_submit": 0, "fieldtype": "Small Text",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Description"
"columns": 2, },
"fetch_from": "",
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "due_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Due Date",
"columns": 2, "reqd": 1
"fieldname": "due_date", },
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Due Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "invoice_portion",
"allow_on_submit": 0, "fieldtype": "Percent",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Invoice Portion",
"columns": 2, "print_hide": 1
"fetch_from": "", },
"fieldname": "invoice_portion",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Invoice Portion",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "payment_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Payment Amount",
"columns": 2, "options": "currency",
"fieldname": "payment_amount", "reqd": 1
"fieldtype": "Currency", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "mode_of_payment",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Mode of Payment",
"bold": 0, "options": "Mode of Payment"
"collapsible": 0, },
"columns": 0, {
"fieldname": "mode_of_payment", "fieldname": "paid_amount",
"fieldtype": "Link", "fieldtype": "Currency",
"hidden": 0, "label": "Paid Amount"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-03-13 17:58:24.729526",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Payment Schedule",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2018-09-06 17:35:44.580209", "sort_order": "DESC",
"modified_by": "Administrator", "track_changes": 1
"module": "Accounts",
"name": "Payment Schedule",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,164 +1,84 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_import": 1,
"allow_import": 1, "allow_rename": 1,
"allow_rename": 1, "autoname": "field:template_name",
"autoname": "field:template_name", "creation": "2017-08-10 15:34:28.058054",
"beta": 0, "doctype": "DocType",
"creation": "2017-08-10 15:34:28.058054", "editable_grid": 1,
"custom": 0, "engine": "InnoDB",
"docstatus": 0, "field_order": [
"doctype": "DocType", "template_name",
"document_type": "", "allocate_payment_based_on_payment_terms",
"editable_grid": 1, "terms"
"engine": "InnoDB", ],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "template_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Template Name",
"collapsible": 0, "unique": 1
"columns": 0, },
"fieldname": "template_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Template Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "terms",
"allow_on_submit": 0, "fieldtype": "Table",
"bold": 0, "label": "Payment Terms",
"collapsible": 0, "options": "Payment Terms Template Detail",
"columns": 0, "reqd": 1
"fieldname": "terms", },
"fieldtype": "Table", {
"hidden": 0, "default": "0",
"ignore_user_permissions": 0, "description": "If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term",
"ignore_xss_filter": 0, "fieldname": "allocate_payment_based_on_payment_terms",
"in_filter": 0, "fieldtype": "Check",
"in_global_search": 0, "label": "Allocate Payment Based On Payment Terms"
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Terms",
"length": 0,
"no_copy": 0,
"options": "Payment Terms Template Detail",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-04-01 15:35:18.112619",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Accounts",
"image_view": 0, "name": "Payment Terms Template",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-24 11:13:31.158613",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Terms Template",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "System Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Accounts User",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Accounts Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "modified",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "track_changes": 1
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@@ -241,6 +241,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
item_details.update({ item_details.update({
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items), 'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
'price_or_product_discount': pricing_rule.price_or_product_discount,
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other) 'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on'))) if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
}) })

View File

@@ -261,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
price_list: this.frm.doc.buying_price_list price_list: this.frm.doc.buying_price_list
}, function() { }, function() {
me.apply_pricing_rule(); me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0; me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1); me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
}) })
}, },
apply_tds: function(frm) {
var me = this;
if (!me.frm.doc.apply_tds) {
me.frm.set_value("tax_withholding_category", '');
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
} else {
me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
me.frm.set_df_property("tax_withholding_category", "hidden", 0);
}
},
credit_to: function() { credit_to: function() {
var me = this; var me = this;
if(this.frm.doc.credit_to) { if(this.frm.doc.credit_to) {

View File

@@ -16,6 +16,7 @@
"is_paid", "is_paid",
"is_return", "is_return",
"apply_tds", "apply_tds",
"tax_withholding_category",
"column_break1", "column_break1",
"company", "company",
"posting_date", "posting_date",
@@ -1284,13 +1285,21 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
"print_hide": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-17 13:05:25.199832", "modified": "2020-04-18 13:05:25.199832",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -959,7 +959,7 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds: if not self.apply_tds:
return return
tax_withholding_details = get_party_tax_withholding_details(self) tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
if not tax_withholding_details: if not tax_withholding_details:
return return

View File

@@ -25,18 +25,26 @@ frappe.ui.form.on("Sales Invoice", {
if(frm.doc.docstatus == 1 && !frm.is_dirty() if(frm.doc.docstatus == 1 && !frm.is_dirty()
&& !frm.doc.is_return && !frm.doc.ewaybill) { && !frm.doc.is_return && !frm.doc.ewaybill) {
frm.add_custom_button('e-Way Bill JSON', () => { frm.add_custom_button('E-Way Bill JSON', () => {
var w = window.open( frappe.call({
frappe.urllib.get_full_url( method: 'erpnext.regional.india.utils.generate_ewb_json',
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" args: {
+ "dt=" + encodeURIComponent(frm.doc.doctype) 'dt': frm.doc.doctype,
+ "&dn=" + encodeURIComponent(frm.doc.name) 'dn': [frm.doc.name]
) },
); callback: function(r) {
if (!w) { if (r.message) {
frappe.msgprint(__("Please enable pop-ups")); return; const args = {
} cmd: 'erpnext.regional.india.utils.download_ewb_json',
}, __("Make")); data: r.message,
docname: frm.doc.name
};
open_url_post(frappe.request.url, args);
}
}
});
}, __("Create"));
} }
}, },

View File

@@ -16,17 +16,23 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
} }
} }
var w = window.open( frappe.call({
frappe.urllib.get_full_url( method: 'erpnext.regional.india.utils.generate_ewb_json',
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" args: {
+ "dt=" + encodeURIComponent(doclist.doctype) 'dt': doclist.doctype,
+ "&dn=" + encodeURIComponent(docnames) 'dn': docnames
) },
); callback: function(r) {
if (!w) { if (r.message) {
frappe.msgprint(__("Please enable pop-ups")); return; const args = {
} cmd: 'erpnext.regional.india.utils.download_ewb_json',
data: r.message,
docname: docnames
};
open_url_post(frappe.request.url, args);
}
}
});
}; };
doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false); doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false);

View File

@@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.script_manager.trigger("is_pos"); me.frm.script_manager.trigger("is_pos");
me.frm.refresh_fields(); me.frm.refresh_fields();
} }
erpnext.queries.setup_warehouse_query(this.frm);
}, },
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
@@ -586,7 +587,9 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query("account_for_change_amount", function() { frm.set_query("account_for_change_amount", function() {
return { return {
filters: { filters: {
account_type: ['in', ["Cash", "Bank"]] account_type: ['in', ["Cash", "Bank"]],
company: frm.doc.company,
is_group: 0
} }
}; };
}); });
@@ -667,7 +670,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_account"].get_query = function() { frm.fields_dict["loyalty_redemption_account"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };
@@ -676,7 +680,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() { frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };

View File

@@ -1834,7 +1834,7 @@ class TestSalesInvoice(unittest.TestCase):
si.submit() si.submit()
data = get_ewb_data("Sales Invoice", si.name) data = get_ewb_data("Sales Invoice", [si.name])
self.assertEqual(data['version'], '1.0.1118') self.assertEqual(data['version'], '1.0.1118')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR') self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')

View File

@@ -6,23 +6,42 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import flt from frappe.utils import flt, getdate
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document): class TaxWithholdingCategory(Document):
pass pass
def get_party_tax_withholding_details(ref_doc): def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
pan_no = ''
suppliers = []
if not tax_withholding_category:
tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
if not tax_withholding_category: if not tax_withholding_category:
return return
if not pan_no:
pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
# Get others suppliers with the same PAN No
if pan_no:
suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
if not suppliers:
suppliers.append(ref_doc.supplier)
fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company) fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company) tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
if not tax_details: if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
.format(tax_withholding_category, ref_doc.company)) .format(tax_withholding_category, ref_doc.company))
tds_amount = get_tds_amount(ref_doc, tax_details, fy)
tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
tax_details, fy, ref_doc.posting_date, pan_no)
tax_row = get_tax_row(tax_details, tds_amount) tax_row = get_tax_row(tax_details, tds_amount)
return tax_row return tax_row
@@ -51,6 +70,7 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year):
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year.")) frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
def get_tax_row(tax_details, tds_amount): def get_tax_row(tax_details, tds_amount):
return { return {
"category": "Total", "category": "Total",
"add_deduct_tax": "Deduct", "add_deduct_tax": "Deduct",
@@ -60,25 +80,36 @@ def get_tax_row(tax_details, tds_amount):
"tax_amount": tds_amount "tax_amount": tds_amount
} }
def get_tds_amount(ref_doc, tax_details, fiscal_year_details): def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
fiscal_year, year_start_date, year_end_date = fiscal_year_details fiscal_year, year_start_date, year_end_date = fiscal_year_details
tds_amount = 0 tds_amount = 0
tds_deducted = 0 tds_deducted = 0
def _get_tds(amount): def _get_tds(amount, rate):
if amount <= 0: if amount <= 0:
return 0 return 0
return amount * tax_details.rate / 100 return amount * rate / 100
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
{
'pan_no': pan_no,
'fiscal_year': fiscal_year
}, 'name')
ldc = ''
if ldc_name:
ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
entries = frappe.db.sql(""" entries = frappe.db.sql("""
select voucher_no, credit select voucher_no, credit
from `tabGL Entry` from `tabGL Entry`
where party=%s and fiscal_year=%s and credit > 0 where company = %s and
""", (ref_doc.supplier, fiscal_year), as_dict=1) party in %s and fiscal_year=%s and credit > 0
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
vouchers = [d.voucher_no for d in entries] vouchers = [d.voucher_no for d in entries]
advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year) advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
tds_vouchers = vouchers + advance_vouchers tds_vouchers = vouchers + advance_vouchers
@@ -93,7 +124,20 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0 tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
if tds_deducted: if tds_deducted:
tds_amount = _get_tds(ref_doc.net_total) if ldc:
limit_consumed = frappe.db.get_value('Purchase Invoice',
{
'supplier': ('in', suppliers),
'apply_tds': 1,
'docstatus': 1
}, 'sum(net_total)')
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
else:
tds_amount = _get_tds(net_total, tax_details.rate)
else: else:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item', supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
fields = ['sum(net_amount)'], fields = ['sum(net_amount)'],
@@ -106,43 +150,79 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
fields = ['sum(credit_in_account_currency)'], fields = ['sum(credit_in_account_currency)'],
filters = { filters = {
'parent': ('in', vouchers), 'docstatus': 1, 'parent': ('in', vouchers), 'docstatus': 1,
'party': ref_doc.supplier, 'party': ('in', suppliers),
'reference_type': ('not in', ['Purchase Invoice']) 'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1) }, as_list=1)
supplier_credit_amount += (jv_supplier_credit_amt[0][0] supplier_credit_amount += (jv_supplier_credit_amt[0][0]
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0) if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
supplier_credit_amount += ref_doc.net_total supplier_credit_amount += net_total
debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date) debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
supplier_credit_amount -= debit_note_amount supplier_credit_amount -= debit_note_amount
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
tds_amount = _get_tds(supplier_credit_amount)
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
ldc.certificate_limit):
tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
tax_details)
else:
tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
return tds_amount return tds_amount
def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=None, to_date=None): def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
condition = "fiscal_year=%s" % fiscal_year condition = "fiscal_year=%s" % fiscal_year
if company:
condition += "and company =%s" % (company)
if from_date and to_date: if from_date and to_date:
condition = "company=%s and posting_date between %s and %s" % (company, from_date, to_date) condition += "and posting_date between %s and %s" % (company, from_date, to_date)
## Appending the same supplier again if length of suppliers list is 1
## since tuple of single element list contains None, For example ('Test Supplier 1', )
## and the below query fails
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return frappe.db.sql_list(""" return frappe.db.sql_list("""
select distinct voucher_no select distinct voucher_no
from `tabGL Entry` from `tabGL Entry`
where party=%s and %s and debit > 0 where party in %s and %s and debit > 0
""", (supplier, condition)) or [] """, (tuple(suppliers), condition)) or []
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None): def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
condition = "" condition = "and 1=1"
if company: if company:
condition = " and company=%s " % company condition = " and company=%s " % company
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return flt(frappe.db.sql(""" return flt(frappe.db.sql("""
select abs(sum(net_total)) select abs(sum(net_total))
from `tabPurchase Invoice` from `tabPurchase Invoice`
where supplier=%s %s and is_return=1 and docstatus=1 where supplier in %s and is_return=1 and docstatus=1
and posting_date between %s and %s and posting_date between %s and %s %s
""", (supplier, condition, year_start_date, year_end_date))) """, (tuple(suppliers), year_start_date, year_end_date, condition)))
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
return current_amount * rate/100
else:
ltds_amount = (certificate_limit - deducted_amount)
tds_amount = current_amount - ltds_amount
return ltds_amount * rate/100 + tds_amount * tax_details.rate/100
def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit):
valid = False
if ((getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and
certificate_limit > deducted_amount):
valid = True
return valid

View File

@@ -344,26 +344,28 @@ class ReceivablePayableReport(object):
def allocate_outstanding_based_on_payment_terms(self, row): def allocate_outstanding_based_on_payment_terms(self, row):
self.get_payment_terms(row) self.get_payment_terms(row)
for term in row.payment_terms: for term in row.payment_terms:
term.outstanding = term.invoiced
# update "paid" and "oustanding" for this term # update "paid" and "oustanding" for this term
self.allocate_closing_to_term(row, term, 'paid') if not term.paid:
self.allocate_closing_to_term(row, term, 'paid')
# update "credit_note" and "oustanding" for this term # update "credit_note" and "oustanding" for this term
if term.outstanding: if term.outstanding:
self.allocate_closing_to_term(row, term, 'credit_note') self.allocate_closing_to_term(row, term, 'credit_note')
row.payment_terms = sorted(row.payment_terms, key=lambda x: x['due_date'])
def get_payment_terms(self, row): def get_payment_terms(self, row):
# build payment_terms for row # build payment_terms for row
payment_terms_details = frappe.db.sql(""" payment_terms_details = frappe.db.sql("""
select select
si.name, si.party_account_currency, si.currency, si.conversion_rate, si.name, si.party_account_currency, si.currency, si.conversion_rate,
ps.due_date, ps.payment_amount, ps.description ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
from `tab{0}` si, `tabPayment Schedule` ps from `tab{0}` si, `tabPayment Schedule` ps
where where
si.name = ps.parent and si.name = ps.parent and
si.name = %s si.name = %s
order by ps.due_date order by ps.paid_amount desc, due_date
""".format(row.voucher_type), row.voucher_no, as_dict = 1) """.format(row.voucher_type), row.voucher_no, as_dict = 1)
@@ -389,11 +391,14 @@ class ReceivablePayableReport(object):
"invoiced": invoiced, "invoiced": invoiced,
"invoice_grand_total": row.invoiced, "invoice_grand_total": row.invoiced,
"payment_term": d.description, "payment_term": d.description,
"paid": 0.0, "paid": d.paid_amount,
"credit_note": 0.0, "credit_note": 0.0,
"outstanding": 0.0 "outstanding": invoiced - d.paid_amount
})) }))
if d.paid_amount:
row['paid'] -= d.paid_amount
def allocate_closing_to_term(self, row, term, key): def allocate_closing_to_term(self, row, term, key):
if row[key]: if row[key]:
if row[key] > term.outstanding: if row[key] > term.outstanding:

View File

@@ -55,27 +55,27 @@ def get_columns(group_wise_columns, filters):
columns = [] columns = []
column_map = frappe._dict({ column_map = frappe._dict({
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120", "parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
"posting_date": _("Posting Date") + ":Date", "posting_date": _("Posting Date") + ":Date:100",
"posting_time": _("Posting Time"), "posting_time": _("Posting Time") + ":Data:100",
"item_code": _("Item Code") + ":Link/Item", "item_code": _("Item Code") + ":Link/Item:100",
"item_name": _("Item Name"), "item_name": _("Item Name") + ":Data:100",
"item_group": _("Item Group") + ":Link/Item Group", "item_group": _("Item Group") + ":Link/Item Group:100",
"brand": _("Brand"), "brand": _("Brand") + ":Link/Brand:100",
"description": _("Description"), "description": _("Description") +":Data:100",
"warehouse": _("Warehouse") + ":Link/Warehouse", "warehouse": _("Warehouse") + ":Link/Warehouse:100",
"qty": _("Qty") + ":Float", "qty": _("Qty") + ":Float:80",
"base_rate": _("Avg. Selling Rate") + ":Currency/currency", "base_rate": _("Avg. Selling Rate") + ":Currency/currency:100",
"buying_rate": _("Valuation Rate") + ":Currency/currency", "buying_rate": _("Valuation Rate") + ":Currency/currency:100",
"base_amount": _("Selling Amount") + ":Currency/currency", "base_amount": _("Selling Amount") + ":Currency/currency:100",
"buying_amount": _("Buying Amount") + ":Currency/currency", "buying_amount": _("Buying Amount") + ":Currency/currency:100",
"gross_profit": _("Gross Profit") + ":Currency/currency", "gross_profit": _("Gross Profit") + ":Currency/currency:100",
"gross_profit_percent": _("Gross Profit %") + ":Percent", "gross_profit_percent": _("Gross Profit %") + ":Percent:100",
"project": _("Project") + ":Link/Project", "project": _("Project") + ":Link/Project:100",
"sales_person": _("Sales person"), "sales_person": _("Sales person"),
"allocated_amount": _("Allocated Amount") + ":Currency/currency", "allocated_amount": _("Allocated Amount") + ":Currency/currency:100",
"customer": _("Customer") + ":Link/Customer", "customer": _("Customer") + ":Link/Customer:100",
"customer_group": _("Customer Group") + ":Link/Customer Group", "customer_group": _("Customer Group") + ":Link/Customer Group:100",
"territory": _("Territory") + ":Link/Territory" "territory": _("Territory") + ":Link/Territory:100"
}) })
for col in group_wise_columns.get(scrub(filters.group_by)): for col in group_wise_columns.get(scrub(filters.group_by)):
@@ -85,7 +85,8 @@ def get_columns(group_wise_columns, filters):
"fieldname": "currency", "fieldname": "currency",
"label" : _("Currency"), "label" : _("Currency"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Currency" "options": "Currency",
"hidden": 1
}) })
return columns return columns
@@ -277,7 +278,7 @@ class GrossProfitGenerator(object):
from `tabPurchase Invoice Item` a from `tabPurchase Invoice Item` a
where a.item_code = %s and a.docstatus=1 where a.item_code = %s and a.docstatus=1
and modified <= %s and modified <= %s
order by a.modified desc limit 1""", (item_code,self.filters.to_date)) order by a.modified desc limit 1""", (item_code, self.filters.to_date))
else: else:
last_purchase_rate = frappe.db.sql(""" last_purchase_rate = frappe.db.sql("""
select (a.base_rate / a.conversion_factor) select (a.base_rate / a.conversion_factor)

View File

@@ -44,9 +44,14 @@ def get_result(filters):
out = [] out = []
for supplier in filters.supplier: for supplier in filters.supplier:
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category) tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year][0] rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year]
if rate:
rate = rate[0]
try: try:
account = [d.account for d in tds.accounts if d.company == filters.company][0] account = [d.account for d in tds.accounts if d.company == filters.company][0]
except IndexError: except IndexError:
account = [] account = []
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account, total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
@@ -76,7 +81,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
supplier_credit_amount = flt(sum([d.credit for d in entries])) supplier_credit_amount = flt(sum([d.credit for d in entries]))
vouchers = [d.voucher_no for d in entries] vouchers = [d.voucher_no for d in entries]
vouchers += get_advance_vouchers(supplier, company=company, vouchers += get_advance_vouchers([supplier], company=company,
from_date=from_date, to_date=to_date) from_date=from_date, to_date=to_date)
tds_deducted = 0 tds_deducted = 0
@@ -89,7 +94,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
""".format(', '.join(["'%s'" % d for d in vouchers])), """.format(', '.join(["'%s'" % d for d in vouchers])),
(account, from_date, to_date, company))[0][0]) (account, from_date, to_date, company))[0][0])
debit_note_amount = get_debit_note_amount(supplier, from_date, to_date, company=company) debit_note_amount = get_debit_note_amount([supplier], from_date, to_date, company=company)
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount

View File

@@ -27,15 +27,6 @@ frappe.ui.form.on("Purchase Order", {
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
frm.set_query("blanket_order", "items", function() {
return {
filters: {
"company": frm.doc.company,
"docstatus": 1
}
}
});
frm.set_query("expense_account", "items", function() { frm.set_query("expense_account", "items", function() {
return { return {
query: "erpnext.controllers.queries.get_expense_account", query: "erpnext.controllers.queries.get_expense_account",

View File

@@ -141,19 +141,18 @@ def get_conditions(filters):
conditions = "" conditions = ""
if filters.get("company"): if filters.get("company"):
conditions += " AND company=%s"% frappe.db.escape(filters.get('company')) conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"): if filters.get("cost_center") or filters.get("project"):
conditions += """ conditions += """
AND (cost_center=%s AND (child.`cost_center`=%s OR child.`project`=%s)
OR project=%s) """ % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
"""% (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"): if filters.get("from_date"):
conditions += " AND transaction_date>=%s"% filters.get('from_date') conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
if filters.get("to_date"): if filters.get("to_date"):
conditions += " AND transaction_date<=%s"% filters.get('to_date') conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
return conditions return conditions
def get_data(filters): def get_data(filters):
@@ -162,7 +161,6 @@ def get_data(filters):
mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions) mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions)
pr_records = get_mapped_pr_records() pr_records = get_mapped_pr_records()
pi_records = get_mapped_pi_records() pi_records = get_mapped_pi_records()
print(pi_records)
procurement_record=[] procurement_record=[]
if procurement_record_against_mr: if procurement_record_against_mr:
@@ -198,16 +196,16 @@ def get_mapped_mr_details(conditions):
mr_records = {} mr_records = {}
mr_details = frappe.db.sql(""" mr_details = frappe.db.sql("""
SELECT SELECT
mr.transaction_date, par.transaction_date,
mr.per_ordered, par.per_ordered,
mr_item.name, child.name,
mr_item.parent, child.parent,
mr_item.amount child.amount
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item FROM `tabMaterial Request` par, `tabMaterial Request Item` child
WHERE WHERE
mr.per_ordered>=0 par.per_ordered>=0
AND mr.name=mr_item.parent AND par.name=child.parent
AND mr.docstatus=1 AND par.docstatus=1
{conditions} {conditions}
""".format(conditions=conditions), as_dict=1) #nosec """.format(conditions=conditions), as_dict=1) #nosec
@@ -254,29 +252,29 @@ def get_mapped_pr_records():
def get_po_entries(conditions): def get_po_entries(conditions):
return frappe.db.sql(""" return frappe.db.sql("""
SELECT SELECT
po_item.name, child.name,
po_item.parent, child.parent,
po_item.cost_center, child.cost_center,
po_item.project, child.project,
po_item.warehouse, child.warehouse,
po_item.material_request, child.material_request,
po_item.material_request_item, child.material_request_item,
po_item.description, child.description,
po_item.stock_uom, child.stock_uom,
po_item.qty, child.qty,
po_item.amount, child.amount,
po_item.base_amount, child.base_amount,
po_item.schedule_date, child.schedule_date,
po.transaction_date, par.transaction_date,
po.supplier, par.supplier,
po.status, par.status,
po.owner par.owner
FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item FROM `tabPurchase Order` par, `tabPurchase Order Item` child
WHERE WHERE
po.docstatus = 1 par.docstatus = 1
AND po.name = po_item.parent AND par.name = child.parent
AND po.status not in ("Closed","Completed","Cancelled") AND par.status not in ("Closed","Completed","Cancelled")
{conditions} {conditions}
GROUP BY GROUP BY
po.name,po_item.item_code par.name, child.item_code
""".format(conditions=conditions), as_dict=1) #nosec """.format(conditions=conditions), as_dict=1) #nosec

View File

@@ -819,7 +819,7 @@ class AccountsController(TransactionBase):
else: else:
for d in self.get("payment_schedule"): for d in self.get("payment_schedule"):
if d.invoice_portion: if d.invoice_portion:
d.payment_amount = grand_total * flt(d.invoice_portion) / 100 d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
def set_due_date(self): def set_due_date(self):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]

View File

@@ -364,6 +364,19 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "parent_account"], fields = ["name", "parent_account"],
limit_start=start, limit_page_length=page_len, as_list=True) limit_start=start, limit_page_length=page_len, as_list=True)
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where
boi.parent = bo.name
and boi.item_code = {item_code}
and bo.blanket_order_type = '{blanket_order_type}'
and bo.company = {company}
and bo.docstatus = 1"""
.format(item_code = frappe.db.escape(filters.get("item")),
blanket_order_type = filters.get("blanket_order_type"),
company = frappe.db.escape(filters.get("company"))
))
@frappe.whitelist() @frappe.whitelist()
def get_income_account(doctype, txt, searchfield, start, page_len, filters): def get_income_account(doctype, txt, searchfield, start, page_len, filters):

View File

@@ -643,8 +643,7 @@ def get_itemised_tax_breakup_html(doc):
itemised_tax=itemised_tax, itemised_tax=itemised_tax,
itemised_taxable_amount=itemised_taxable_amount, itemised_taxable_amount=itemised_taxable_amount,
tax_accounts=tax_accounts, tax_accounts=tax_accounts,
conversion_rate=doc.conversion_rate, doc=doc
currency=doc.currency
) )
) )

View File

@@ -27,7 +27,7 @@ class EmailCampaign(Document):
for entry in campaign.get("campaign_schedules"): for entry in campaign.get("campaign_schedules"):
send_after_days.append(entry.send_after_days) send_after_days.append(entry.send_after_days)
try: try:
end_date = add_days(getdate(self.start_date), max(send_after_days)) self.end_date = add_days(getdate(self.start_date), max(send_after_days))
except ValueError: except ValueError:
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name)) frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))

View File

@@ -112,6 +112,8 @@ frappe.ui.form.on("Fees", {
args: { args: {
"dt": frm.doc.doctype, "dt": frm.doc.doctype,
"dn": frm.doc.name, "dn": frm.doc.name,
"party_type": "Student",
"party": frm.doc.student,
"recipient_id": frm.doc.student_email "recipient_id": frm.doc.student_email
}, },
callback: function(r) { callback: function(r) {

View File

@@ -75,7 +75,8 @@ class Fees(AccountsController):
self.make_gl_entries() self.make_gl_entries()
if self.send_payment_request and self.student_email: if self.send_payment_request and self.student_email:
pr = make_payment_request(dt="Fees", dn=self.name, recipient_id=self.student_email, pr = make_payment_request(party_type="Student", party=self.student, dt="Fees",
dn=self.name, recipient_id=self.student_email,
submit_doc=True, use_dummy_message=True) submit_doc=True, use_dummy_message=True)
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name))) frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))

View File

@@ -158,7 +158,7 @@ def get_item_dict(table, parent, parenttype):
def create_stock_entry(doc): def create_stock_entry(doc):
stock_entry = frappe.new_doc("Stock Entry") stock_entry = frappe.new_doc("Stock Entry")
stock_entry = set_stock_items(stock_entry, doc.name, "Clinical Procedure") stock_entry = set_stock_items(stock_entry, doc.name, "Clinical Procedure")
stock_entry.purpose = "Material Issue" stock_entry.stock_entry_type = "Material Issue"
stock_entry.from_warehouse = doc.warehouse stock_entry.from_warehouse = doc.warehouse
stock_entry.company = doc.company stock_entry.company = doc.company
expense_account = get_account(None, "expense_account", "Healthcare Settings", doc.company) expense_account = get_account(None, "expense_account", "Healthcare Settings", doc.company)

View File

@@ -352,7 +352,7 @@ def send_message(doc, message):
# jinja to string convertion happens here # jinja to string convertion happens here
message = frappe.render_template(message, context) message = frappe.render_template(message, context)
number = [patient.mobile] number = [patient_mobile]
send_sms(number, message) send_sms(number, message)

View File

@@ -268,7 +268,8 @@ doc_events = {
scheduler_events = { scheduler_events = {
"all": [ "all": [
"erpnext.projects.doctype.project.project.project_status_update_reminder" "erpnext.projects.doctype.project.project.project_status_update_reminder",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.set_appointment_reminder"
], ],
"hourly": [ "hourly": [
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails', 'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',

View File

@@ -136,9 +136,18 @@ def make_bank_entry(dt, dn):
def make_return_entry(employee, company, employee_advance_name, def make_return_entry(employee, company, employee_advance_name,
return_amount, advance_account, mode_of_payment=None): 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) 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
je = frappe.new_doc('Journal Entry') je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate() je.posting_date = nowdate()
je.voucher_type = 'Bank Entry' # 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.company = company je.company = company
je.remark = 'Return against Employee Advance: ' + employee_advance_name je.remark = 'Return against Employee Advance: ' + employee_advance_name

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Employee Other Income', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,138 @@
{
"actions": [],
"autoname": "HR-INCOME-.######",
"creation": "2020-03-18 15:04:40.767434",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"payroll_period",
"column_break_3",
"company",
"source",
"amount",
"amended_from"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"fieldname": "payroll_period",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Payroll Period",
"options": "Payroll Period",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "source",
"fieldtype": "Data",
"label": "Source"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "Company:company:default_currency",
"reqd": 1
},
{
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Employee Other Income",
"print_hide": 1,
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-19 18:06:45.361830",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Other Income",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class EmployeeOtherIncome(Document):
pass

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestEmployeeOtherIncome(unittest.TestCase):
pass

View File

@@ -1,620 +1,180 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_import": 1,
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 1, "autoname": "HR-TAX-DEC-.YYYY.-.#####",
"allow_rename": 1, "creation": "2018-04-13 16:53:36.175504",
"autoname": "HR-TAX-DEC-.YYYY.-.#####", "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2018-04-13 16:53:36.175504", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "employee",
"doctype": "DocType", "employee_name",
"document_type": "", "department",
"editable_grid": 1, "column_break_2",
"engine": "InnoDB", "payroll_period",
"company",
"amended_from",
"section_break_8",
"declarations",
"section_break_10",
"total_declared_amount",
"column_break_12",
"total_exemption_amount"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "employee",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Employee",
"collapsible": 0, "options": "Employee",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.employee_name",
"allow_in_quick_entry": 0, "fieldname": "employee_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Employee Name",
"collapsible": 0, "read_only": 1
"columns": 0, },
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.department",
"allow_in_quick_entry": 0, "fieldname": "department",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Department",
"collapsible": 0, "options": "Department",
"columns": 0, "read_only": 1
"fetch_from": "employee.department", },
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_2",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "payroll_period",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Payroll Period",
"collapsible": 0, "options": "Payroll Period",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "payroll_period",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.company",
"allow_in_quick_entry": 0, "fieldname": "company",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Company",
"collapsible": 0, "options": "Company"
"columns": 0, },
"fetch_from": "employee.company",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amended_from",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Amended From",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Employee Tax Exemption Declaration",
"columns": 0, "print_hide": 1,
"fetch_if_empty": 0, "read_only": 1
"fieldname": "amended_from", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Employee Tax Exemption Declaration",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_8",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "declarations",
"allow_in_quick_entry": 0, "fieldtype": "Table",
"allow_on_submit": 0, "label": "Declarations",
"bold": 0, "options": "Employee Tax Exemption Declaration Category"
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "declarations",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Declarations",
"length": 0,
"no_copy": 0,
"options": "Employee Tax Exemption Declaration Category",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_10",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "total_declared_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Total Declared Amount",
"bold": 0, "read_only": 1
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_declared_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Declared Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_12",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "total_exemption_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Total Exemption Amount",
"bold": 0, "read_only": 1
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "is_submittable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-03-18 14:56:25.625717",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "HR",
"in_create": 0, "name": "Employee Tax Exemption Declaration",
"is_submittable": 1, "owner": "Administrator",
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-05-11 16:13:50.472670",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Declaration",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "System Manager",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "HR Manager",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "HR User",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "Employee",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "modified",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "track_changes": 1
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -8,31 +8,17 @@ from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_annual_eligible_hra_exemption from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class DuplicateDeclarationError(frappe.ValidationError): pass
class EmployeeTaxExemptionDeclaration(Document): class EmployeeTaxExemptionDeclaration(Document):
def validate(self): def validate(self):
validate_tax_declaration(self.declarations) validate_tax_declaration(self.declarations)
self.validate_duplicate() validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
self.set_total_declared_amount() self.set_total_declared_amount()
self.set_total_exemption_amount() self.set_total_exemption_amount()
self.calculate_hra_exemption() self.calculate_hra_exemption()
def validate_duplicate(self):
duplicate = frappe.db.get_value("Employee Tax Exemption Declaration",
filters = {
"employee": self.employee,
"payroll_period": self.payroll_period,
"name": ["!=", self.name],
"docstatus": ["!=", 2]
}
)
if duplicate:
frappe.throw(_("Duplicate Tax Declaration of {0} for period {1}")
.format(self.employee, self.payroll_period), DuplicateDeclarationError)
def set_total_declared_amount(self): def set_total_declared_amount(self):
self.total_declared_amount = 0.0 self.total_declared_amount = 0.0
for d in self.declarations: for d in self.declarations:

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import unittest import unittest
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_tax_exemption_declaration.employee_tax_exemption_declaration import DuplicateDeclarationError from erpnext.hr.utils import DuplicateDeclarationError
class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@@ -1,635 +1,140 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_import": 1,
"allow_guest_to_view": 0, "allow_rename": 1,
"allow_import": 1, "autoname": "HR-TAX-PRF-.YYYY.-.#####",
"allow_rename": 1, "creation": "2018-04-13 17:24:11.456132",
"autoname": "HR-TAX-PRF-.YYYY.-.#####", "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2018-04-13 17:24:11.456132", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "employee",
"doctype": "DocType", "employee_name",
"document_type": "", "department",
"editable_grid": 1, "column_break_2",
"engine": "InnoDB", "submission_date",
"payroll_period",
"company",
"section_break_5",
"tax_exemption_proofs",
"section_break_10",
"total_actual_amount",
"column_break_12",
"exemption_amount",
"attachment_section",
"attachments",
"amended_from"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "employee",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Employee",
"columns": 0, "options": "Employee",
"fetch_if_empty": 0, "reqd": 1
"fieldname": "employee", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.employee_name",
"allow_in_quick_entry": 0, "fieldname": "employee_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Employee Name",
"collapsible": 0, "read_only": 1
"columns": 0, },
"fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.department",
"allow_in_quick_entry": 0, "fieldname": "department",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Department",
"collapsible": 0, "options": "Department",
"columns": 0, "read_only": 1
"fetch_from": "employee.department", },
"fetch_if_empty": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_2",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "Today",
"allow_in_quick_entry": 0, "fieldname": "submission_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "label": "Submission Date",
"collapsible": 0, "reqd": 1
"columns": 0, },
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "submission_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submission Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "payroll_period",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Payroll Period",
"columns": 0, "options": "Payroll Period",
"fetch_if_empty": 0, "reqd": 1
"fieldname": "payroll_period", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.company",
"allow_in_quick_entry": 0, "fieldname": "company",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Company",
"collapsible": 0, "options": "Company",
"columns": 0, "read_only": 1,
"fetch_from": "employee.company", "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "tax_exemption_proofs",
"allow_in_quick_entry": 0, "fieldtype": "Table",
"allow_on_submit": 0, "label": "Tax Exemption Proofs",
"bold": 0, "options": "Employee Tax Exemption Proof Submission Detail"
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tax_exemption_proofs",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tax Exemption Proofs",
"length": 0,
"no_copy": 0,
"options": "Employee Tax Exemption Proof Submission Detail",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_10",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "total_actual_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Total Actual Amount",
"bold": 0, "read_only": 1
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_actual_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Actual Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_12",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "exemption_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Total Exemption Amount",
"bold": 0, "read_only": 1
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "attachment_section",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "attachments",
"allow_in_quick_entry": 0, "fieldtype": "Attach",
"allow_on_submit": 0, "label": "Attachments"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amended_from",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Amended From",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Employee Tax Exemption Proof Submission",
"columns": 0, "print_hide": 1,
"fetch_if_empty": 0, "read_only": 1
"fieldname": "attachment_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "attachments",
"fieldtype": "Attach",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attachments",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Employee Tax Exemption Proof Submission",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "is_submittable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-03-18 14:55:51.420016",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "HR",
"in_create": 0, "name": "Employee Tax Exemption Proof Submission",
"is_submittable": 1, "owner": "Administrator",
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-05-13 12:17:18.045171",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Proof Submission",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,

View File

@@ -7,7 +7,8 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, calculate_hra_exemption_for_period from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionProofSubmission(Document): class EmployeeTaxExemptionProofSubmission(Document):
def validate(self): def validate(self):
@@ -15,6 +16,7 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.set_total_actual_amount() self.set_total_actual_amount()
self.set_total_exemption_amount() self.set_total_exemption_amount()
self.calculate_hra_exemption() self.calculate_hra_exemption()
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
def set_total_actual_amount(self): def set_total_actual_amount(self):
self.total_actual_amount = flt(self.get("house_rent_payment_amount")) self.total_actual_amount = flt(self.get("house_rent_payment_amount"))
@@ -32,4 +34,4 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.exemption_amount += hra_exemption["total_eligible_hra_exemption"] self.exemption_amount += hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"] self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.monthly_house_rent = hra_exemption["monthly_house_rent"] self.monthly_house_rent = hra_exemption["monthly_house_rent"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"] self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]

View File

@@ -0,0 +1,6 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Income Tax Slab', {
});

View File

@@ -0,0 +1,160 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2020-03-17 16:50:35.564915",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"effective_from",
"company",
"column_break_3",
"allow_tax_exemption",
"standard_tax_exemption_amount",
"disabled",
"amended_from",
"taxable_salary_slabs_section",
"slabs",
"taxes_and_charges_on_income_tax_section",
"other_taxes_and_charges"
],
"fields": [
{
"fieldname": "effective_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Effective from",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled, Tax Exemption Declaration will be considered for income tax calculation.",
"fieldname": "allow_tax_exemption",
"fieldtype": "Check",
"label": "Allow Tax Exemption"
},
{
"fieldname": "taxable_salary_slabs_section",
"fieldtype": "Section Break",
"label": "Taxable Salary Slabs"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Income Tax Slab",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "slabs",
"fieldtype": "Table",
"label": "Taxable Salary Slabs",
"options": "Taxable Salary Slab",
"reqd": 1
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "allow_tax_exemption",
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"label": "Standard Tax Exemption Amount",
"options": "Company:company:default_currency"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"collapsible": 1,
"fieldname": "taxes_and_charges_on_income_tax_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges on Income Tax"
},
{
"fieldname": "other_taxes_and_charges",
"fieldtype": "Table",
"label": "Other Taxes and Charges",
"options": "Income Tax Slab Other Charges"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-24 12:28:36.805904",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class IncomeTaxSlab(Document):
pass

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestIncomeTaxSlab(unittest.TestCase):
pass

View File

@@ -0,0 +1,75 @@
{
"actions": [],
"creation": "2020-04-24 11:46:59.041180",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"description",
"column_break_2",
"percent",
"conditions_section",
"min_taxable_income",
"column_break_7",
"max_taxable_income"
],
"fields": [
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "min_taxable_income",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Min Taxable Income",
"options": "Company:company:default_currency"
},
{
"columns": 4,
"fieldname": "description",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"columns": 2,
"fieldname": "percent",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Percent",
"reqd": 1
},
{
"fieldname": "conditions_section",
"fieldtype": "Section Break",
"label": "Conditions"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "max_taxable_income",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Max Taxable Income",
"options": "Company:company:default_currency"
}
],
"istable": 1,
"links": [],
"modified": "2020-04-24 13:27:43.598967",
"modified_by": "Administrator",
"module": "HR",
"name": "Income Tax Slab Other Charges",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class IncomeTaxSlabOtherCharges(Document):
pass

View File

@@ -30,16 +30,16 @@ class LeaveAllocation(Document):
def validate_leave_allocation_days(self): def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "company") company = frappe.db.get_value("Employee", self.employee, "company")
leave_period = get_leave_period(self.from_date, self.to_date, company) leave_period = get_leave_period(self.from_date, self.to_date, company)
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") max_leaves_allowed = flt(frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed"))
if max_leaves_allowed > 0: if max_leaves_allowed > 0:
leave_allocated = 0 leave_allocated = 0
if leave_period: if leave_period:
leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
leave_period[0].from_date, leave_period[0].to_date) leave_period[0].from_date, leave_period[0].to_date)
leave_allocated += self.new_leaves_allocated leave_allocated += flt(self.new_leaves_allocated)
if leave_allocated > max_leaves_allowed: if leave_allocated > max_leaves_allowed:
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period") frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
.format(self.leave_type, self.employee)) .format(self.leave_type, self.employee))
def on_submit(self): def on_submit(self):
self.create_leave_ledger_entry() self.create_leave_ledger_entry()

View File

@@ -1,401 +1,102 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_import": 1,
"allow_guest_to_view": 0, "autoname": "Prompt",
"allow_import": 1, "creation": "2018-04-13 15:18:53.698553",
"allow_rename": 0, "doctype": "DocType",
"autoname": "Prompt", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2018-04-13 15:18:53.698553", "field_order": [
"custom": 0, "company",
"docstatus": 0, "column_break_2",
"doctype": "DocType", "start_date",
"document_type": "", "end_date",
"editable_grid": 1, "section_break_5",
"engine": "InnoDB", "periods"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "company",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Company",
"collapsible": 0, "options": "Company",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_2",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "start_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "label": "Start Date",
"bold": 0, "reqd": 1
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "end_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "label": "End Date",
"bold": 0, "reqd": 1
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Section Break",
"allow_on_submit": 0, "hidden": 1,
"bold": 0, "label": "Payroll Periods"
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payroll Periods",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "periods",
"allow_in_quick_entry": 0, "fieldtype": "Table",
"allow_on_submit": 0, "label": "Payroll Periods",
"bold": 0, "options": "Payroll Period Date"
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "periods",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payroll Periods",
"length": 0,
"no_copy": 0,
"options": "Payroll Period Date",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Taxable Salary Slabs",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxable_salary_slabs",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Taxable Salary Slabs",
"length": 0,
"no_copy": 0,
"options": "Taxable Salary Slab",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Standard Tax Exemption Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-03-18 18:13:23.859980",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "HR",
"image_view": 0, "name": "Payroll Period",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-26 01:45:03.160929",
"modified_by": "Administrator",
"module": "HR",
"name": "Payroll Period",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "System Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "HR Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "HR User",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "modified",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "track_changes": 1
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -45,8 +45,9 @@ class PayrollPeriod(Document):
+ _(") for {0}").format(self.company) + _(") for {0}").format(self.company)
frappe.throw(msg) frappe.throw(msg)
def get_payroll_period_days(start_date, end_date, employee): def get_payroll_period_days(start_date, end_date, employee, company=None):
company = frappe.db.get_value("Employee", employee, "company") if not company:
company = frappe.db.get_value("Employee", employee, "company")
payroll_period = frappe.db.sql(""" payroll_period = frappe.db.sql("""
select name, start_date, end_date select name, start_date, end_date
from `tabPayroll Period` from `tabPayroll Period`

View File

@@ -1,264 +1,263 @@
{ {
"allow_import": 1, "actions": [],
"allow_rename": 1, "allow_import": 1,
"autoname": "field:salary_component", "allow_rename": 1,
"creation": "2016-06-30 15:42:43.631931", "autoname": "field:salary_component",
"doctype": "DocType", "creation": "2016-06-30 15:42:43.631931",
"document_type": "Setup", "doctype": "DocType",
"editable_grid": 1, "document_type": "Setup",
"engine": "InnoDB", "editable_grid": 1,
"field_order": [ "engine": "InnoDB",
"salary_component", "field_order": [
"salary_component_abbr", "salary_component",
"type", "salary_component_abbr",
"description", "type",
"column_break_4", "description",
"is_payable", "column_break_4",
"depends_on_payment_days", "depends_on_payment_days",
"is_tax_applicable", "is_tax_applicable",
"deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date",
"round_to_the_nearest_integer", "variable_based_on_taxable_salary",
"statistical_component", "exempted_from_income_tax",
"do_not_include_in_total", "round_to_the_nearest_integer",
"disabled", "statistical_component",
"flexible_benefits", "do_not_include_in_total",
"is_flexible_benefit", "disabled",
"max_benefit_amount", "flexible_benefits",
"column_break_9", "is_flexible_benefit",
"pay_against_benefit_claim", "max_benefit_amount",
"only_tax_impact", "column_break_9",
"create_separate_payment_entry_against_benefit_claim", "pay_against_benefit_claim",
"section_break_11", "only_tax_impact",
"variable_based_on_taxable_salary", "create_separate_payment_entry_against_benefit_claim",
"section_break_5", "section_break_5",
"accounts", "accounts",
"condition_and_formula", "condition_and_formula",
"condition", "condition",
"amount", "amount",
"amount_based_on_formula", "amount_based_on_formula",
"formula", "formula",
"column_break_28", "column_break_28",
"help" "help"
], ],
"fields": [ "fields": [
{ {
"fieldname": "salary_component", "fieldname": "salary_component",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Name", "label": "Name",
"reqd": 1, "reqd": 1,
"unique": 1 "unique": 1
}, },
{ {
"fieldname": "salary_component_abbr", "fieldname": "salary_component_abbr",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Abbr", "label": "Abbr",
"print_width": "120px", "print_width": "120px",
"reqd": 1, "reqd": 1,
"width": "120px" "width": "120px"
}, },
{ {
"fieldname": "type", "fieldname": "type",
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Type", "label": "Type",
"options": "Earning\nDeduction", "options": "Earning\nDeduction",
"reqd": 1 "reqd": 1
}, },
{ {
"default": "1", "default": "1",
"depends_on": "eval:doc.type == \"Earning\"", "depends_on": "eval:doc.type == \"Earning\"",
"fieldname": "is_tax_applicable", "fieldname": "is_tax_applicable",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Tax Applicable" "label": "Is Tax Applicable"
}, },
{ {
"default": "1", "default": "1",
"fieldname": "is_payable", "fieldname": "depends_on_payment_days",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Payable" "label": "Depends on Payment Days",
}, "print_hide": 1
{ },
"default": "1", {
"fieldname": "depends_on_payment_days", "default": "0",
"fieldtype": "Check", "fieldname": "do_not_include_in_total",
"label": "Depends on Payment Days", "fieldtype": "Check",
"print_hide": 1 "label": "Do Not Include in Total"
}, },
{ {
"default": "0", "default": "0",
"fieldname": "do_not_include_in_total", "depends_on": "eval:doc.is_tax_applicable && doc.type=='Earning'",
"fieldtype": "Check", "fieldname": "deduct_full_tax_on_selected_payroll_date",
"label": "Do Not Include in Total" "fieldtype": "Check",
}, "label": "Deduct Full Tax on Selected Payroll Date"
{ },
"default": "0", {
"depends_on": "is_tax_applicable", "fieldname": "column_break_4",
"fieldname": "deduct_full_tax_on_selected_payroll_date", "fieldtype": "Column Break"
"fieldtype": "Check", },
"label": "Deduct Full Tax on Selected Payroll Date" {
}, "default": "0",
{ "fieldname": "disabled",
"fieldname": "column_break_4", "fieldtype": "Check",
"fieldtype": "Column Break" "label": "Disabled"
}, },
{ {
"default": "0", "fieldname": "description",
"fieldname": "disabled", "fieldtype": "Small Text",
"fieldtype": "Check", "in_list_view": 1,
"label": "Disabled" "label": "Description"
}, },
{ {
"fieldname": "description", "default": "0",
"fieldtype": "Small Text", "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ",
"in_list_view": 1, "fieldname": "statistical_component",
"label": "Description" "fieldtype": "Check",
}, "label": "Statistical Component"
{ },
"default": "0", {
"description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1",
"fieldname": "statistical_component", "fieldname": "flexible_benefits",
"fieldtype": "Check", "fieldtype": "Section Break",
"label": "Statistical Component" "label": "Flexible Benefits"
}, },
{ {
"depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1", "default": "0",
"fieldname": "flexible_benefits", "fieldname": "is_flexible_benefit",
"fieldtype": "Section Break", "fieldtype": "Check",
"label": "Flexible Benefits" "label": "Is Flexible Benefit"
}, },
{ {
"default": "0", "depends_on": "is_flexible_benefit",
"fieldname": "is_flexible_benefit", "fieldname": "max_benefit_amount",
"fieldtype": "Check", "fieldtype": "Currency",
"label": "Is Flexible Benefit" "label": "Max Benefit Amount (Yearly)"
}, },
{ {
"depends_on": "is_flexible_benefit", "fieldname": "column_break_9",
"fieldname": "max_benefit_amount", "fieldtype": "Column Break"
"fieldtype": "Currency", },
"label": "Max Benefit Amount (Yearly)" {
}, "default": "0",
{ "depends_on": "is_flexible_benefit",
"fieldname": "column_break_9", "fieldname": "pay_against_benefit_claim",
"fieldtype": "Column Break" "fieldtype": "Check",
}, "label": "Pay Against Benefit Claim"
{ },
"default": "0", {
"depends_on": "is_flexible_benefit", "default": "0",
"fieldname": "pay_against_benefit_claim", "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1",
"fieldtype": "Check", "fieldname": "only_tax_impact",
"label": "Pay Against Benefit Claim" "fieldtype": "Check",
}, "label": "Only Tax Impact (Cannot Claim But Part of Taxable Income)"
{ },
"default": "0", {
"depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1", "default": "0",
"fieldname": "only_tax_impact", "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1",
"fieldtype": "Check", "fieldname": "create_separate_payment_entry_against_benefit_claim",
"label": "Only Tax Impact (Cannot Claim But Part of Taxable Income)" "fieldtype": "Check",
}, "label": "Create Separate Payment Entry Against Benefit Claim"
{ },
"default": "0", {
"depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1", "default": "0",
"fieldname": "create_separate_payment_entry_against_benefit_claim", "depends_on": "eval:doc.type == \"Deduction\"",
"fieldtype": "Check", "fieldname": "variable_based_on_taxable_salary",
"label": "Create Separate Payment Entry Against Benefit Claim" "fieldtype": "Check",
}, "label": "Variable Based On Taxable Salary"
{ },
"depends_on": "eval:doc.type=='Deduction'", {
"fieldname": "section_break_11", "depends_on": "eval:doc.statistical_component != 1",
"fieldtype": "Section Break" "fieldname": "section_break_5",
}, "fieldtype": "Section Break",
{ "label": "Accounts"
"default": "0", },
"fieldname": "variable_based_on_taxable_salary", {
"fieldtype": "Check", "fieldname": "accounts",
"label": "Variable Based On Taxable Salary" "fieldtype": "Table",
}, "label": "Accounts",
{ "options": "Salary Component Account"
"depends_on": "eval:doc.statistical_component != 1", },
"fieldname": "section_break_5", {
"fieldtype": "Section Break", "collapsible": 1,
"label": "Accounts" "depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1",
}, "fieldname": "condition_and_formula",
{ "fieldtype": "Section Break",
"fieldname": "accounts", "label": "Condition and Formula"
"fieldtype": "Table", },
"label": "Accounts", {
"options": "Salary Component Account" "fieldname": "condition",
}, "fieldtype": "Code",
{ "label": "Condition"
"collapsible": 1, },
"depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1", {
"fieldname": "condition_and_formula", "default": "0",
"fieldtype": "Section Break", "fieldname": "amount_based_on_formula",
"label": "Condition and Formula" "fieldtype": "Check",
}, "label": "Amount based on formula"
{ },
"fieldname": "condition", {
"fieldtype": "Code", "depends_on": "amount_based_on_formula",
"label": "Condition" "fieldname": "formula",
}, "fieldtype": "Code",
{ "label": "Formula"
"default": "0", },
"fieldname": "amount_based_on_formula", {
"fieldtype": "Check", "depends_on": "eval:doc.amount_based_on_formula!==1",
"label": "Amount based on formula" "fieldname": "amount",
}, "fieldtype": "Currency",
{ "label": "Amount"
"depends_on": "amount_based_on_formula", },
"fieldname": "formula", {
"fieldtype": "Code", "fieldname": "column_break_28",
"label": "Formula" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval:doc.amount_based_on_formula!==1", "fieldname": "help",
"fieldname": "amount", "fieldtype": "HTML",
"fieldtype": "Currency", "label": "Help",
"label": "Amount" "options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
}, },
{ {
"fieldname": "column_break_28", "default": "0",
"fieldtype": "Column Break" "fieldname": "round_to_the_nearest_integer",
}, "fieldtype": "Check",
{ "label": "Round to the Nearest Integer"
"fieldname": "help", },
"fieldtype": "HTML", {
"label": "Help", "default": "0",
"options": "<h3>Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>" "depends_on": "eval:doc.type == \"Deduction\" && !doc.variable_based_on_taxable_salary",
}, "description": "If checked, the full amount will be deducted from taxable income before calculating income tax. Otherwise, it can be exempted via Employee Tax Exemption Declaration.",
{ "fieldname": "exempted_from_income_tax",
"default": "0", "fieldtype": "Check",
"fieldname": "round_to_the_nearest_integer", "label": "Exempted from Income Tax"
"fieldtype": "Check", }
"label": "Round to the Nearest Integer" ],
} "icon": "fa fa-flag",
], "links": [],
"icon": "fa fa-flag", "modified": "2020-04-24 14:50:28.994054",
"modified": "2019-06-05 11:34:14.231228", "modified_by": "Administrator",
"modified_by": "Administrator", "module": "HR",
"module": "HR", "name": "Salary Component",
"name": "Salary Component", "owner": "Administrator",
"owner": "Administrator", "permissions": [
"permissions": [ {
{ "create": 1,
"create": 1, "delete": 1,
"delete": 1, "email": 1,
"email": 1, "export": 1,
"export": 1, "print": 1,
"print": 1, "read": 1,
"read": 1, "report": 1,
"report": 1, "role": "HR User",
"role": "HR User", "share": 1,
"share": 1, "write": 1
"write": 1 },
}, {
{ "read": 1,
"read": 1, "role": "Employee"
"role": "Employee" }
} ],
], "sort_field": "modified",
"sort_field": "modified", "sort_order": "DESC"
"sort_order": "DESC" }
}

View File

@@ -3,14 +3,12 @@
"doctype": "Salary Component", "doctype": "Salary Component",
"salary_component": "_Test Basic Salary", "salary_component": "_Test Basic Salary",
"type": "Earning", "type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1 "is_tax_applicable": 1
}, },
{ {
"doctype": "Salary Component", "doctype": "Salary Component",
"salary_component": "_Test Allowance", "salary_component": "_Test Allowance",
"type": "Earning", "type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1 "is_tax_applicable": 1
}, },
{ {
@@ -27,14 +25,12 @@
"doctype": "Salary Component", "doctype": "Salary Component",
"salary_component": "Basic", "salary_component": "Basic",
"type": "Earning", "type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1 "is_tax_applicable": 1
}, },
{ {
"doctype": "Salary Component", "doctype": "Salary Component",
"salary_component": "Leave Encashment", "salary_component": "Leave Encashment",
"type": "Earning", "type": "Earning",
"is_payable": 1,
"is_tax_applicable": 1 "is_tax_applicable": 1
} }
] ]

View File

@@ -18,6 +18,5 @@ def create_salary_component(component_name, **args):
"doctype": "Salary Component", "doctype": "Salary Component",
"salary_component": component_name, "salary_component": component_name,
"type": args.get("type") or "Earning", "type": args.get("type") or "Earning",
"is_payable": args.get("is_payable") or 1,
"is_tax_applicable": args.get("is_tax_applicable") or 1 "is_tax_applicable": args.get("is_tax_applicable") or 1
}).insert() }).insert()

View File

@@ -1,765 +1,216 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2016-06-30 15:32:36.385111",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2016-06-30 15:32:36.385111", "salary_component",
"custom": 0, "abbr",
"docstatus": 0, "statistical_component",
"doctype": "DocType", "column_break_3",
"document_type": "", "deduct_full_tax_on_selected_payroll_date",
"editable_grid": 1, "depends_on_payment_days",
"is_tax_applicable",
"exempted_from_income_tax",
"is_flexible_benefit",
"variable_based_on_taxable_salary",
"section_break_2",
"condition",
"amount_based_on_formula",
"formula",
"amount",
"do_not_include_in_total",
"default_amount",
"additional_amount",
"tax_on_flexible_benefit",
"tax_on_additional_salary",
"section_break_11",
"condition_and_formula_help"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "salary_component",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Component",
"collapsible": 0, "options": "Salary Component",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "salary_component",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Component",
"length": 0,
"no_copy": 0,
"options": "Salary Component",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 1,
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_on_submit": 0, "fetch_from": "salary_component.salary_component_abbr",
"bold": 0, "fieldname": "abbr",
"collapsible": 0, "fieldtype": "Data",
"columns": 1, "in_list_view": 1,
"default": "", "label": "Abbr",
"depends_on": "eval:doc.parenttype=='Salary Structure'", "print_hide": 1,
"fetch_from": "salary_component.salary_component_abbr", "read_only": 1
"fetch_if_empty": 0, },
"fieldname": "abbr",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Abbr",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_3",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ",
"allow_on_submit": 0, "fetch_from": "salary_component.statistical_component",
"bold": 0, "fieldname": "statistical_component",
"collapsible": 0, "fieldtype": "Check",
"columns": 0, "in_list_view": 1,
"description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", "label": "Statistical Component"
"fetch_from": "salary_component.statistical_component", },
"fetch_if_empty": 0,
"fieldname": "statistical_component",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Statistical Component",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parentfield=='earnings'",
"allow_on_submit": 0, "fetch_from": "salary_component.is_tax_applicable",
"bold": 0, "fieldname": "is_tax_applicable",
"collapsible": 0, "fieldtype": "Check",
"columns": 0, "label": "Is Tax Applicable",
"fetch_from": "salary_component.is_tax_applicable", "print_hide": 1,
"fetch_if_empty": 0, "read_only": 1
"fieldname": "is_tax_applicable", },
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Tax Applicable",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parentfield=='earnings'",
"allow_on_submit": 0, "fetch_from": "salary_component.is_flexible_benefit",
"bold": 0, "fieldname": "is_flexible_benefit",
"collapsible": 0, "fieldtype": "Check",
"columns": 0, "label": "Is Flexible Benefit",
"fetch_from": "salary_component.is_flexible_benefit", "print_hide": 1,
"fetch_if_empty": 0, "read_only": 1
"fieldname": "is_flexible_benefit", },
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Flexible Benefit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parentfield=='deductions'",
"allow_on_submit": 0, "fetch_from": "salary_component.variable_based_on_taxable_salary",
"bold": 0, "fieldname": "variable_based_on_taxable_salary",
"collapsible": 0, "fieldtype": "Check",
"columns": 0, "label": "Variable Based On Taxable Salary",
"default": "", "print_hide": 1,
"fetch_from": "salary_component.variable_based_on_taxable_salary", "read_only": 1
"fetch_if_empty": 0, },
"fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Variable Based On Taxable Salary",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "fetch_from": "salary_component.depends_on_payment_days",
"allow_on_submit": 0, "fieldname": "depends_on_payment_days",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "label": "Depends on Payment Days",
"columns": 0, "print_hide": 1,
"depends_on": "", "read_only": 1
"fetch_from": "salary_component.depends_on_payment_days", },
"fetch_if_empty": 0,
"fieldname": "depends_on_payment_days",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Depends on Payment Days",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "fieldname": "deduct_full_tax_on_selected_payroll_date",
"allow_on_submit": 0, "fieldtype": "Check",
"bold": 0, "label": "Deduct Full Tax on Selected Payroll Date",
"collapsible": 0, "print_hide": 1,
"columns": 0, "read_only": 1
"fetch_if_empty": 0, },
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.is_flexible_benefit != 1",
"allow_in_quick_entry": 0, "fieldname": "section_break_2",
"allow_on_submit": 0, "fieldtype": "Section Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_flexible_benefit != 1",
"fetch_if_empty": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_on_submit": 1,
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_on_submit": 1, "fieldname": "condition",
"bold": 0, "fieldtype": "Code",
"collapsible": 0, "label": "Condition"
"columns": 0, },
"depends_on": "eval:doc.parenttype=='Salary Structure'",
"fetch_if_empty": 0,
"fieldname": "condition",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Condition",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_on_submit": 0, "fieldname": "amount_based_on_formula",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "label": "Amount based on formula"
"columns": 0, },
"default": "0",
"depends_on": "eval:doc.parenttype=='Salary Structure'",
"fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "amount_based_on_formula",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amount based on formula",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_on_submit": 1,
"allow_in_quick_entry": 0, "depends_on": "eval:doc.amount_based_on_formula!==0 && doc.parenttype==='Salary Structure'",
"allow_on_submit": 1, "fieldname": "formula",
"bold": 0, "fieldtype": "Code",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Formula"
"default": "", },
"depends_on": "eval:doc.amount_based_on_formula!==0 && doc.parenttype==='Salary Structure'",
"description": "",
"fetch_if_empty": 0,
"fieldname": "formula",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Formula",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.amount_based_on_formula!==1 || doc.parenttype==='Salary Slip'",
"allow_in_quick_entry": 0, "fieldname": "amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Amount",
"columns": 0, "options": "Company:company:default_currency"
"depends_on": "eval:doc.amount_based_on_formula!==1 || doc.parenttype==='Salary Slip'", },
"fetch_if_empty": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "fieldname": "do_not_include_in_total",
"allow_on_submit": 0, "fieldtype": "Check",
"bold": 0, "label": "Do not include in total"
"collapsible": 0, },
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "do_not_include_in_total",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Do not include in total",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_in_quick_entry": 0, "fieldname": "default_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Default Amount",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "print_hide": 1
"depends_on": "eval:doc.parenttype=='Salary Structure'", },
"fetch_if_empty": 0,
"fieldname": "default_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "additional_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "hidden": 1,
"bold": 0, "label": "Additional Amount",
"collapsible": 0, "no_copy": 1,
"columns": 0, "print_hide": 1,
"default": "", "read_only": 1
"fetch_from": "", },
"fetch_if_empty": 0,
"fieldname": "additional_amount",
"fieldtype": "Currency",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Additional Amount",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='deductions' && doc.variable_based_on_taxable_salary == 1",
"allow_in_quick_entry": 0, "fieldname": "tax_on_flexible_benefit",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Tax on flexible benefit",
"collapsible": 0, "read_only": 1
"columns": 0, },
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='deductions' && doc.variable_based_on_taxable_salary == 1",
"fetch_if_empty": 0,
"fieldname": "tax_on_flexible_benefit",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tax on flexible benefit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='deductions' && doc.variable_based_on_taxable_salary == 1",
"allow_in_quick_entry": 0, "fieldname": "tax_on_additional_salary",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Tax on additional salary",
"collapsible": 0, "read_only": 1
"columns": 0, },
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='deductions' && doc.variable_based_on_taxable_salary == 1",
"fetch_if_empty": 0,
"fieldname": "tax_on_additional_salary",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tax on additional salary",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_in_quick_entry": 0, "fieldname": "section_break_11",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.parenttype=='Salary Structure'",
"fetch_if_empty": 0,
"fieldname": "section_break_11",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "eval:doc.parenttype=='Salary Structure'",
"allow_in_quick_entry": 0, "fieldname": "condition_and_formula_help",
"allow_on_submit": 0, "fieldtype": "HTML",
"bold": 0, "label": "Condition and Formula Help",
"collapsible": 0, "options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
"columns": 0, },
"depends_on": "eval:doc.parenttype=='Salary Structure'", {
"fetch_if_empty": 0, "default": "0",
"fieldname": "condition_and_formula_help", "depends_on": "eval:doc.parentfield=='deductions'",
"fieldtype": "HTML", "fetch_from": "salary_component.exempted_from_income_tax",
"hidden": 0, "fieldname": "exempted_from_income_tax",
"ignore_user_permissions": 0, "fieldtype": "Check",
"ignore_xss_filter": 0, "label": "Exempted from Income Tax",
"in_filter": 0, "read_only": 1
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Condition and Formula Help",
"length": 0,
"no_copy": 0,
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-04-24 20:00:16.475295",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "HR",
"in_create": 0, "name": "Salary Detail",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2019-05-11 17:33:08.508653", "sort_order": "DESC"
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -450,7 +450,8 @@ class SalarySlip(TransactionBase):
'is_flexible_benefit': struct_row.is_flexible_benefit, 'is_flexible_benefit': struct_row.is_flexible_benefit,
'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary, 'variable_based_on_taxable_salary': struct_row.variable_based_on_taxable_salary,
'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date, 'deduct_full_tax_on_selected_payroll_date': struct_row.deduct_full_tax_on_selected_payroll_date,
'additional_amount': amount if struct_row.get("is_additional_component") else 0 'additional_amount': amount if struct_row.get("is_additional_component") else 0,
'exempted_from_income_tax': struct_row.exempted_from_income_tax
}) })
else: else:
if struct_row.get("is_additional_component"): if struct_row.get("is_additional_component"):
@@ -481,10 +482,12 @@ class SalarySlip(TransactionBase):
return self.calculate_variable_tax(payroll_period, tax_component) return self.calculate_variable_tax(payroll_period, tax_component)
def calculate_variable_tax(self, payroll_period, tax_component): def calculate_variable_tax(self, payroll_period, tax_component):
# get Tax slab from salary structure assignment for the employee and payroll period
tax_slab = self.get_income_tax_slabs(payroll_period)
# get remaining numbers of sub-period (period for which one salary is processed) # get remaining numbers of sub-period (period for which one salary is processed)
remaining_sub_periods = get_period_factor(self.employee, remaining_sub_periods = get_period_factor(self.employee,
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1] self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
# get taxable_earnings, paid_taxes for previous period # get taxable_earnings, paid_taxes for previous period
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date, self.start_date) previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date, self.start_date)
previous_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component) previous_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component)
@@ -506,23 +509,27 @@ class SalarySlip(TransactionBase):
unclaimed_taxable_benefits += current_taxable_earnings_for_payment_days.flexi_benefits unclaimed_taxable_benefits += current_taxable_earnings_for_payment_days.flexi_benefits
# Total exemption amount based on tax exemption declaration # Total exemption amount based on tax exemption declaration
total_exemption_amount, other_incomes = self.get_total_exemption_amount_and_other_incomes(payroll_period) total_exemption_amount = self.get_total_exemption_amount(payroll_period, tax_slab)
#Employee Other Incomes
other_incomes = self.get_income_form_other_sources(payroll_period) or 0.0
# Total taxable earnings including additional and other incomes # Total taxable earnings including additional and other incomes
total_taxable_earnings = previous_taxable_earnings + current_structured_taxable_earnings + future_structured_taxable_earnings \ total_taxable_earnings = previous_taxable_earnings + current_structured_taxable_earnings + future_structured_taxable_earnings \
+ current_additional_earnings + other_incomes + unclaimed_taxable_benefits - total_exemption_amount + current_additional_earnings + other_incomes + unclaimed_taxable_benefits - total_exemption_amount
# Total taxable earnings without additional earnings with full tax # Total taxable earnings without additional earnings with full tax
total_taxable_earnings_without_full_tax_addl_components = total_taxable_earnings - current_additional_earnings_with_full_tax total_taxable_earnings_without_full_tax_addl_components = total_taxable_earnings - current_additional_earnings_with_full_tax
# Structured tax amount # Structured tax amount
total_structured_tax_amount = self.calculate_tax_by_tax_slab(payroll_period, total_taxable_earnings_without_full_tax_addl_components) total_structured_tax_amount = self.calculate_tax_by_tax_slab(
total_taxable_earnings_without_full_tax_addl_components, tax_slab)
current_structured_tax_amount = (total_structured_tax_amount - previous_total_paid_taxes) / remaining_sub_periods current_structured_tax_amount = (total_structured_tax_amount - previous_total_paid_taxes) / remaining_sub_periods
# Total taxable earnings with additional earnings with full tax # Total taxable earnings with additional earnings with full tax
full_tax_on_additional_earnings = 0.0 full_tax_on_additional_earnings = 0.0
if current_additional_earnings_with_full_tax: if current_additional_earnings_with_full_tax:
total_tax_amount = self.calculate_tax_by_tax_slab(payroll_period, total_taxable_earnings) total_tax_amount = self.calculate_tax_by_tax_slab(total_taxable_earnings, tax_slab)
full_tax_on_additional_earnings = total_tax_amount - total_structured_tax_amount full_tax_on_additional_earnings = total_tax_amount - total_structured_tax_amount
current_tax_amount = current_structured_tax_amount + full_tax_on_additional_earnings current_tax_amount = current_structured_tax_amount + full_tax_on_additional_earnings
@@ -531,12 +538,30 @@ class SalarySlip(TransactionBase):
return current_tax_amount return current_tax_amount
def get_income_tax_slabs(self, payroll_period):
income_tax_slab, ss_assignment_name = frappe.db.get_value("Salary Structure Assignment",
{"employee": self.employee, "salary_structure": self.salary_structure, "docstatus": 1}, ["income_tax_slab", 'name'])
if not income_tax_slab:
frappe.throw(_("Income Tax Slab not set in Salary Structure Assignment: {0}").format(ss_assignment_name))
income_tax_slab_doc = frappe.get_doc("Income Tax Slab", income_tax_slab)
if income_tax_slab_doc.disabled:
frappe.throw(_("Income Tax Slab: {0} is disabled").format(income_tax_slab))
if getdate(income_tax_slab_doc.effective_from) > getdate(payroll_period.start_date):
frappe.throw(_("Income Tax Slab must be effective on or before Payroll Period Start Date: {0}")
.format(payroll_period.start_date))
return income_tax_slab_doc
def get_taxable_earnings_for_prev_period(self, start_date, end_date): def get_taxable_earnings_for_prev_period(self, start_date, end_date):
taxable_earnings = frappe.db.sql(""" taxable_earnings = frappe.db.sql("""
select sum(sd.amount) select sum(sd.amount)
from from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name `tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where where
sd.parentfield='earnings' sd.parentfield='earnings'
and sd.is_tax_applicable=1 and sd.is_tax_applicable=1
and is_flexible_benefit=0 and is_flexible_benefit=0
@@ -549,7 +574,28 @@ class SalarySlip(TransactionBase):
"from_date": start_date, "from_date": start_date,
"to_date": end_date "to_date": end_date
}) })
return flt(taxable_earnings[0][0]) if taxable_earnings else 0 taxable_earnings = flt(taxable_earnings[0][0]) if taxable_earnings else 0
exempted_amount = frappe.db.sql("""
select sum(sd.amount)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
sd.parentfield='deductions'
and sd.exempted_from_income_tax=1
and is_flexible_benefit=0
and ss.docstatus=1
and ss.employee=%(employee)s
and ss.start_date between %(from_date)s and %(to_date)s
and ss.end_date between %(from_date)s and %(to_date)s
""", {
"employee": self.employee,
"from_date": start_date,
"to_date": end_date
})
exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0
return taxable_earnings - exempted_amount
def get_tax_paid_in_period(self, start_date, end_date, tax_component): def get_tax_paid_in_period(self, start_date, end_date, tax_component):
# find total_tax_paid, tax paid for benefit, additional_salary # find total_tax_paid, tax paid for benefit, additional_salary
@@ -609,6 +655,13 @@ class SalarySlip(TransactionBase):
else: else:
taxable_earnings += amount taxable_earnings += amount
for ded in self.deductions:
if ded.exempted_from_income_tax:
amount = ded.amount
if based_on_payment_days:
amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0]
taxable_earnings -= flt(amount)
return frappe._dict({ return frappe._dict({
"taxable_earnings": taxable_earnings, "taxable_earnings": taxable_earnings,
"additional_income": additional_income, "additional_income": additional_income,
@@ -671,40 +724,63 @@ class SalarySlip(TransactionBase):
return total_benefits_paid - total_benefits_claimed return total_benefits_paid - total_benefits_claimed
def get_total_exemption_amount_and_other_incomes(self, payroll_period): def get_total_exemption_amount(self, payroll_period, tax_slab):
total_exemption_amount, other_incomes = 0, 0 total_exemption_amount = 0
if self.deduct_tax_for_unsubmitted_tax_exemption_proof: if tax_slab.allow_tax_exemption:
exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission", if self.deduct_tax_for_unsubmitted_tax_exemption_proof:
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission",
["exemption_amount", "income_from_other_sources"]) {"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
if exemption_proof: ["exemption_amount"])
total_exemption_amount, other_incomes = exemption_proof if exemption_proof:
else: total_exemption_amount = exemption_proof
declaration = frappe.db.get_value("Employee Tax Exemption Declaration", else:
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, declaration = frappe.db.get_value("Employee Tax Exemption Declaration",
["total_exemption_amount", "income_from_other_sources"]) {"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
if declaration: ["total_exemption_amount"])
total_exemption_amount, other_incomes = declaration if declaration:
total_exemption_amount = declaration
return total_exemption_amount, other_incomes total_exemption_amount += flt(tax_slab.standard_tax_exemption_amount)
def calculate_tax_by_tax_slab(self, payroll_period, annual_taxable_earning): return total_exemption_amount
payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
annual_taxable_earning -= flt(payroll_period_obj.standard_tax_exemption_amount) def get_income_form_other_sources(self, payroll_period):
return frappe.get_all("Employee Other Income",
filters={
"employee": self.employee,
"payroll_period": payroll_period.name,
"company": self.company,
"docstatus": 1
},
fields="SUM(amount) as total_amount"
)[0].total_amount
def calculate_tax_by_tax_slab(self, annual_taxable_earning, tax_slab):
data = self.get_data_for_eval() data = self.get_data_for_eval()
data.update({"annual_taxable_earning": annual_taxable_earning}) data.update({"annual_taxable_earning": annual_taxable_earning})
taxable_amount = 0 tax_amount = 0
for slab in payroll_period_obj.taxable_salary_slabs: for slab in tax_slab.slabs:
if slab.condition and not self.eval_tax_slab_condition(slab.condition, data): if slab.condition and not self.eval_tax_slab_condition(slab.condition, data):
continue continue
if not slab.to_amount and annual_taxable_earning > slab.from_amount: if not slab.to_amount and annual_taxable_earning > slab.from_amount:
taxable_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01 tax_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01
continue continue
if annual_taxable_earning > slab.from_amount and annual_taxable_earning < slab.to_amount: if annual_taxable_earning > slab.from_amount and annual_taxable_earning < slab.to_amount:
taxable_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01 tax_amount += (annual_taxable_earning - slab.from_amount) * slab.percent_deduction *.01
elif annual_taxable_earning > slab.from_amount and annual_taxable_earning > slab.to_amount: elif annual_taxable_earning > slab.from_amount and annual_taxable_earning > slab.to_amount:
taxable_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01 tax_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01
return taxable_amount
# other taxes and charges on income tax
for d in tax_slab.other_taxes_and_charges:
if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount:
continue
if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount:
continue
tax_amount += tax_amount * flt(d.percent) / 100
return tax_amount
def eval_tax_slab_condition(self, condition, data): def eval_tax_slab_condition(self, condition, data):
try: try:

View File

@@ -47,10 +47,7 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.payment_days, no_of_days[0]) self.assertEqual(ss.payment_days, no_of_days[0])
self.assertEqual(ss.earnings[0].amount, 50000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 78000) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 67418.0)
def test_salary_slip_with_holidays_excluded(self): def test_salary_slip_with_holidays_excluded(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
@@ -67,10 +64,7 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.earnings[0].amount, 50000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[0].default_amount, 50000) self.assertEqual(ss.earnings[0].default_amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 78000) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 67418.0)
def test_payment_days(self): def test_payment_days(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
@@ -80,8 +74,8 @@ class TestSalarySlip(unittest.TestCase):
# set joinng date in the same month # set joinng date in the same month
make_employee("test_employee@salary.com") make_employee("test_employee@salary.com")
if getdate(nowdate()).day >= 15: if getdate(nowdate()).day >= 15:
date_of_joining = getdate(add_days(nowdate(),-10))
relieving_date = getdate(add_days(nowdate(),-10)) relieving_date = getdate(add_days(nowdate(),-10))
date_of_joining = getdate(add_days(nowdate(),-10))
elif getdate(nowdate()).day < 15 and getdate(nowdate()).day >= 5: elif getdate(nowdate()).day < 15 and getdate(nowdate()).day >= 5:
date_of_joining = getdate(add_days(nowdate(),-3)) date_of_joining = getdate(add_days(nowdate(),-3))
relieving_date = getdate(add_days(nowdate(),-3)) relieving_date = getdate(add_days(nowdate(),-3))
@@ -131,9 +125,7 @@ class TestSalarySlip(unittest.TestCase):
def test_email_salary_slip(self): def test_email_salary_slip(self):
frappe.db.sql("delete from `tabEmail Queue`") frappe.db.sql("delete from `tabEmail Queue`")
hr_settings = frappe.get_doc("HR Settings", "HR Settings") frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 1)
hr_settings.email_salary_slip_to_employee = 1
hr_settings.save()
make_employee("test_employee@salary.com") make_employee("test_employee@salary.com")
ss = make_employee_salary_slip("test_employee@salary.com", "Monthly") ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
@@ -183,8 +175,11 @@ class TestSalarySlip(unittest.TestCase):
# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""") frappe.db.sql("""delete from `tabSalary Component`""")
payroll_period = create_payroll_period() payroll_period = create_payroll_period()
create_tax_slab(payroll_period)
create_tax_slab(payroll_period, allow_tax_exemption=True)
employee = make_employee("test_tax@salary.slip") employee = make_employee("test_tax@salary.slip")
delete_docs = [ delete_docs = [
"Salary Slip", "Salary Slip",
@@ -210,8 +205,7 @@ class TestSalarySlip(unittest.TestCase):
payroll_period, deduct_random=False) payroll_period, deduct_random=False)
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
# total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200 annual_tax = 113589.0
annual_tax = 113568
try: try:
self.assertEqual(tax_paid, annual_tax) self.assertEqual(tax_paid, annual_tax)
except AssertionError: except AssertionError:
@@ -235,8 +229,7 @@ class TestSalarySlip(unittest.TestCase):
raise raise
# Submit proof for total 120000 # Submit proof for total 120000
data["proof-1"] = create_proof_submission(employee, payroll_period, 50000) data["proof"] = create_proof_submission(employee, payroll_period, 120000)
data["proof-2"] = create_proof_submission(employee, payroll_period, 70000)
# Submit benefit claim for total 50000 # Submit benefit claim for total 50000
data["benefit-1"] = create_benefit_claim(employee, payroll_period, 15000, "Medical Allowance") data["benefit-1"] = create_benefit_claim(employee, payroll_period, 15000, "Medical Allowance")
@@ -250,7 +243,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 416000, 166000 @ 5% ie. 8300 # total taxable income 416000, 166000 @ 5% ie. 8300
try: try:
self.assertEqual(tax_paid, 88608) self.assertEqual(tax_paid, 82389.0)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@@ -265,7 +258,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200 # total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
try: try:
self.assertEqual(tax_paid, 121211) self.assertEqual(tax_paid, annual_tax)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@@ -307,6 +300,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
if not salary_structure: if not salary_structure:
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
employee = frappe.db.get_value("Employee", {"user_id": user}) employee = frappe.db.get_value("Employee", {"user_id": user})
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee) 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})}) salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
@@ -431,17 +425,15 @@ def make_deduction_salary_component(setup=False, test_tax=False):
{ {
"salary_component": 'Professional Tax', "salary_component": 'Professional Tax',
"abbr":'PT', "abbr":'PT',
"condition": 'base > 10000',
"formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1 "amount": 200,
"exempted_from_income_tax": 1
}, },
{ {
"salary_component": 'TDS', "salary_component": 'TDS',
"abbr":'T', "abbr":'T',
"formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1,
"depends_on_payment_days": 0, "depends_on_payment_days": 0,
"variable_based_on_taxable_salary": 1, "variable_based_on_taxable_salary": 1,
"round_to_the_nearest_integer": 1 "round_to_the_nearest_integer": 1
@@ -452,9 +444,7 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"salary_component": 'TDS', "salary_component": 'TDS',
"abbr":'T', "abbr":'T',
"condition": 'employment_type=="Intern"', "condition": 'employment_type=="Intern"',
"formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1,
"round_to_the_nearest_integer": 1 "round_to_the_nearest_integer": 1
}) })
if setup or test_tax: if setup or test_tax:
@@ -510,29 +500,47 @@ def create_benefit_claim(employee, payroll_period, amount, component):
}).submit() }).submit()
return claim_date return claim_date
def create_tax_slab(payroll_period): def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False):
data = [ if frappe.db.exists("Income Tax Slab", "Tax Slab: " + payroll_period.name):
return
slabs = [
{ {
"from_amount": 250000, "from_amount": 250000,
"to_amount": 500000, "to_amount": 500000,
"percent_deduction": 5.2, "percent_deduction": 5,
"condition": "annual_taxable_earning > 500000" "condition": "annual_taxable_earning > 500000"
}, },
{ {
"from_amount": 500001, "from_amount": 500001,
"to_amount": 1000000, "to_amount": 1000000,
"percent_deduction": 20.8 "percent_deduction": 20
}, },
{ {
"from_amount": 1000001, "from_amount": 1000001,
"percent_deduction": 31.2 "percent_deduction": 30
} }
] ]
payroll_period.taxable_salary_slabs = []
for item in data: income_tax_slab = frappe.new_doc("Income Tax Slab")
payroll_period.append("taxable_salary_slabs", item) income_tax_slab.name = "Tax Slab: " + payroll_period.name
payroll_period.standard_tax_exemption_amount = 52500 income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
payroll_period.save()
if allow_tax_exemption:
income_tax_slab.allow_tax_exemption = 1
income_tax_slab.standard_tax_exemption_amount = 50000
for item in slabs:
income_tax_slab.append("slabs", item)
income_tax_slab.append("other_taxes_and_charges", {
"description": "cess",
"percent": 4
})
income_tax_slab.save()
if not dont_submit:
income_tax_slab.submit()
def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True): def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
deducted_dates = [] deducted_dates = []

View File

@@ -82,6 +82,7 @@ frappe.ui.form.on('Salary Structure', {
{fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")}, {fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
{fieldname:'base_variable', fieldtype:'Section Break'}, {fieldname:'base_variable', fieldtype:'Section Break'},
{fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1}, {fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1},
{fieldname:'income_tax_slab', fieldtype:'Link', label: __('Income Tax Slab'), options: 'Income Tax Slab'},
{fieldname:'base_col_br', fieldtype:'Column Break'}, {fieldname:'base_col_br', fieldtype:'Column Break'},
{fieldname:'base', fieldtype:'Currency', label: __('Base')}, {fieldname:'base', fieldtype:'Currency', label: __('Base')},
{fieldname:'variable', fieldtype:'Currency', label: __('Variable')} {fieldname:'variable', fieldtype:'Currency', label: __('Variable')}

View File

@@ -16,6 +16,7 @@ class SalaryStructure(Document):
self.validate_amount() self.validate_amount()
self.strip_condition_and_formula_fields() self.strip_condition_and_formula_fields()
self.validate_max_benefits_with_flexi() self.validate_max_benefits_with_flexi()
self.validate_component_based_on_tax_slab()
def set_missing_values(self): def set_missing_values(self):
overwritten_fields = ["depends_on_payment_days", "variable_based_on_taxable_salary", "is_tax_applicable", "is_flexible_benefit"] overwritten_fields = ["depends_on_payment_days", "variable_based_on_taxable_salary", "is_tax_applicable", "is_flexible_benefit"]
@@ -34,6 +35,12 @@ class SalaryStructure(Document):
for fieldname in overwritten_fields_if_missing: for fieldname in overwritten_fields_if_missing:
d.set(fieldname, component_default_value.get(fieldname)) d.set(fieldname, component_default_value.get(fieldname))
def validate_component_based_on_tax_slab(self):
for row in self.deductions:
if row.variable_based_on_taxable_salary and (row.amount or row.formula):
frappe.throw(_("Row #{0}: Cannot set amount or formula for Salary Component {1} with Variable Based On Taxable Salary")
.format(row.idx, row.salary_component))
def validate_amount(self): def validate_amount(self):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet: if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative")) frappe.throw(_("Net pay cannot be negative"))
@@ -82,21 +89,23 @@ class SalaryStructure(Document):
@frappe.whitelist() @frappe.whitelist()
def assign_salary_structure(self, company=None, grade=None, department=None, designation=None,employee=None, def assign_salary_structure(self, company=None, grade=None, department=None, designation=None,employee=None,
from_date=None, base=None,variable=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) employees = self.get_employees(company= company, grade= grade,department= department,designation= designation,name=employee)
if employees: if employees:
if len(employees) > 20: if len(employees) > 20:
frappe.enqueue(assign_salary_structure_for_employees, timeout=600, frappe.enqueue(assign_salary_structure_for_employees, timeout=600,
employees=employees, salary_structure=self,from_date=from_date, base=base,variable=variable) employees=employees, salary_structure=self,from_date=from_date,
base=base, variable=variable, income_tax_slab=income_tax_slab)
else: else:
assign_salary_structure_for_employees(employees, self, from_date=from_date, base=base,variable=variable) assign_salary_structure_for_employees(employees, self, from_date=from_date,
base=base, variable=variable, income_tax_slab=income_tax_slab)
else: else:
frappe.msgprint(_("No Employee Found")) frappe.msgprint(_("No Employee Found"))
def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None,variable=None): def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None, variable=None, income_tax_slab=None):
salary_structures_assignments = [] salary_structures_assignments = []
existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date) existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date)
count=0 count=0
@@ -105,7 +114,8 @@ def assign_salary_structure_for_employees(employees, salary_structure, from_date
continue continue
count +=1 count +=1
salary_structures_assignment = create_salary_structures_assignment(employee, salary_structure, from_date, base, variable) salary_structures_assignment = create_salary_structures_assignment(employee,
salary_structure, from_date, base, variable, income_tax_slab)
salary_structures_assignments.append(salary_structures_assignment) salary_structures_assignments.append(salary_structures_assignment)
frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures...")) frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures..."))
@@ -113,7 +123,7 @@ def assign_salary_structure_for_employees(employees, salary_structure, from_date
frappe.msgprint(_("Structures have been assigned successfully")) frappe.msgprint(_("Structures have been assigned successfully"))
def create_salary_structures_assignment(employee, salary_structure, from_date, base, variable): def create_salary_structures_assignment(employee, salary_structure, from_date, base, variable, income_tax_slab=None):
assignment = frappe.new_doc("Salary Structure Assignment") assignment = frappe.new_doc("Salary Structure Assignment")
assignment.employee = employee assignment.employee = employee
assignment.salary_structure = salary_structure.name assignment.salary_structure = salary_structure.name
@@ -121,6 +131,7 @@ def create_salary_structures_assignment(employee, salary_structure, from_date, b
assignment.from_date = from_date assignment.from_date = from_date
assignment.base = base assignment.base = base
assignment.variable = variable assignment.variable = variable
assignment.income_tax_slab = income_tax_slab
assignment.save(ignore_permissions = True) assignment.save(ignore_permissions = True)
assignment.submit() assignment.submit()
return assignment.name return assignment.name

View File

@@ -9,8 +9,9 @@ from frappe.utils.make_random import get_random
from frappe.utils import nowdate, add_days, add_years, getdate, add_months from frappe.utils import nowdate, add_days, add_years, getdate, add_months
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\ from erpnext.hr.doctype.salary_slip.test_salary_slip import make_earning_salary_component,\
make_deduction_salary_component, make_employee_salary_slip make_deduction_salary_component, make_employee_salary_slip, create_tax_slab
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_payroll_period
test_dependencies = ["Fiscal Year"] test_dependencies = ["Fiscal Year"]
@@ -70,10 +71,8 @@ class TestSalaryStructure(unittest.TestCase):
self.assertEqual(sal_slip.get("earnings")[1].amount, 3000) self.assertEqual(sal_slip.get("earnings")[1].amount, 3000)
self.assertEqual(sal_slip.get("earnings")[2].amount, 25000) self.assertEqual(sal_slip.get("earnings")[2].amount, 25000)
self.assertEqual(sal_slip.get("gross_pay"), 78000) self.assertEqual(sal_slip.get("gross_pay"), 78000)
self.assertEqual(sal_slip.get("deductions")[0].amount, 5000) self.assertEqual(sal_slip.get("deductions")[0].amount, 200)
self.assertEqual(sal_slip.get("deductions")[1].amount, 5000) self.assertEqual(sal_slip.get("net_pay"), 78000 - sal_slip.get("total_deduction"))
self.assertEqual(sal_slip.get("total_deduction"), 10000)
self.assertEqual(sal_slip.get("net_pay"), 68000)
def test_whitespaces_in_formula_conditions_fields(self): def test_whitespaces_in_formula_conditions_fields(self):
salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True) salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True)
@@ -111,6 +110,7 @@ class TestSalaryStructure(unittest.TestCase):
def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, test_tax=False): def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None, test_tax=False):
if test_tax: if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
if not frappe.db.exists('Salary Structure', salary_structure): if not frappe.db.exists('Salary Structure', salary_structure):
details = { details = {
"doctype": "Salary Structure", "doctype": "Salary Structure",
@@ -123,7 +123,8 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
} }
if other_details and isinstance(other_details, dict): if other_details and isinstance(other_details, dict):
details.update(other_details) details.update(other_details)
salary_structure_doc = frappe.get_doc(details).insert() salary_structure_doc = frappe.get_doc(details)
salary_structure_doc.insert()
if not dont_submit: if not dont_submit:
salary_structure_doc.submit() salary_structure_doc.submit()
else: else:
@@ -138,13 +139,18 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
def create_salary_structure_assignment(employee, salary_structure, from_date=None): def create_salary_structure_assignment(employee, salary_structure, from_date=None):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}): if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(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)
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee salary_structure_assignment.employee = employee
salary_structure_assignment.base = 50000 salary_structure_assignment.base = 50000
salary_structure_assignment.variable = 5000 salary_structure_assignment.variable = 5000
salary_structure_assignment.from_date = from_date or add_months(nowdate(), -1) salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1)
salary_structure_assignment.salary_structure = salary_structure salary_structure_assignment.salary_structure = salary_structure
salary_structure_assignment.company = erpnext.get_default_company() salary_structure_assignment.company = erpnext.get_default_company()
salary_structure_assignment.save(ignore_permissions=True) salary_structure_assignment.save(ignore_permissions=True)
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
salary_structure_assignment.submit() salary_structure_assignment.submit()
return salary_structure_assignment return salary_structure_assignment

View File

@@ -20,6 +20,16 @@ frappe.ui.form.on('Salary Structure Assignment', {
} }
} }
}); });
frm.set_query("income_tax_slab", function() {
return {
filters: {
company: frm.doc.company,
docstatus: 1,
disabled: 0
}
}
});
}, },
employee: function(frm) { employee: function(frm) {
if(frm.doc.employee){ if(frm.doc.employee){

View File

@@ -10,11 +10,12 @@
"employee", "employee",
"employee_name", "employee_name",
"department", "department",
"designation", "company",
"column_break_6", "column_break_6",
"designation",
"salary_structure", "salary_structure",
"from_date", "from_date",
"company", "income_tax_slab",
"section_break_7", "section_break_7",
"base", "base",
"column_break_9", "column_break_9",
@@ -113,11 +114,17 @@
"options": "Salary Structure Assignment", "options": "Salary Structure Assignment",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "income_tax_slab",
"fieldtype": "Link",
"label": "Income Tax Slab",
"options": "Income Tax Slab"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-31 17:05:28.637510", "modified": "2020-04-25 18:24:23.617088",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Structure Assignment", "name": "Salary Structure Assignment",

View File

@@ -8,9 +8,6 @@ from frappe.utils import flt
from erpnext.hr.doctype.leave_application.leave_application \ from erpnext.hr.doctype.leave_application.leave_application \
import get_leave_balance_on, get_leaves_for_period import get_leave_balance_on, get_leaves_for_period
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary \
import get_department_leave_approver_map
def execute(filters=None): def execute(filters=None):
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc") leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
@@ -28,6 +25,8 @@ def get_columns(leave_types):
for leave_type in leave_types: for leave_type in leave_types:
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160") columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
columns.append(_(leave_type) + " " + _("Allocated") + ":Float:160")
columns.append(_(leave_type) + " " + _("Expired") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160") columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160") columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
@@ -68,18 +67,97 @@ def get_data(filters, leave_types):
row = [employee.name, employee.employee_name, employee.department] row = [employee.name, employee.employee_name, employee.department]
for leave_type in leave_types: for leave_type in leave_types:
# leaves taken
leaves_taken = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1
# opening balance
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
# closing balance row += calculate_leaves_details(filters, leave_type, employee)
closing = max(opening - leaves_taken, 0)
row += [opening, leaves_taken, closing]
data.append(row) data.append(row)
return data
return data def calculate_leaves_details(filters, leave_type, employee):
ledger_entries = get_leave_ledger_entries(filters.from_date, filters.to_date, employee.name, leave_type)
#Leaves Deducted consist of both expired and leaves taken
leaves_deducted = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1
# removing expired leaves
leaves_taken = leaves_deducted - remove_expired_leave(ledger_entries)
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
new_allocation , expired_allocation = get_allocated_and_expired_leaves(ledger_entries, filters.from_date, filters.to_date)
#removing leaves taken from expired_allocation
expired_leaves = max(expired_allocation - leaves_taken, 0)
#Formula for calculating closing balance
closing = max(opening + new_allocation - (leaves_taken + expired_leaves), 0)
return [opening, new_allocation, expired_leaves, leaves_taken, closing]
def remove_expired_leave(records):
expired_within_period = 0
for record in records:
if record.is_expired:
expired_within_period += record.leaves
return expired_within_period * -1
def get_allocated_and_expired_leaves(records, from_date, to_date):
from frappe.utils import getdate
new_allocation = 0
expired_leaves = 0
for record in records:
if record.to_date <= getdate(to_date) and record.leaves>0:
expired_leaves += record.leaves
if record.from_date >= getdate(from_date) and record.leaves>0:
new_allocation += record.leaves
return new_allocation, expired_leaves
def get_leave_ledger_entries(from_date, to_date, employee, leave_type):
records= frappe.db.sql("""
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
AND docstatus=1
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
""", {
"from_date": from_date,
"to_date": to_date,
"employee": employee,
"leave_type": leave_type
}, as_dict=1)
return records
def get_department_leave_approver_map(department=None):
conditions=''
if department:
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all('Department Approver', filters={
'parentfield': 'leave_approvers',
'parent': ('in', department_list)
}, fields=['parent', 'approver'], as_list=1)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers

View File

@@ -6,6 +6,7 @@ import frappe
from frappe.utils import flt from frappe.utils import flt
from frappe import _ from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import calculate_leaves_details , get_department_leave_approver_map
def execute(filters=None): def execute(filters=None):
if filters.to_date <= filters.from_date: if filters.to_date <= filters.from_date:
@@ -38,17 +39,27 @@ def get_columns():
'label': _('Opening Balance'), 'label': _('Opening Balance'),
'fieldtype': 'float', 'fieldtype': 'float',
'fieldname': 'opening_balance', 'fieldname': 'opening_balance',
'width': 160, 'width': 120,
}, {
'label': _('New Allocation'),
'fieldtype': 'Float',
'fieldname': 'new_allocation',
'width': 120,
}, {
'label': _('Expired Leaves'),
'fieldtype': 'Float',
'fieldname': 'expired_leaves',
'width': 120,
}, { }, {
'label': _('Leaves Taken'), 'label': _('Leaves Taken'),
'fieldtype': 'float', 'fieldtype': 'float',
'fieldname': 'leaves_taken', 'fieldname': 'leaves_taken',
'width': 160, 'width': 120,
}, { }, {
'label': _('Closing Balance'), 'label': _('Closing Balance'),
'fieldtype': 'float', 'fieldtype': 'float',
'fieldname': 'closing_balance', 'fieldname': 'closing_balance',
'width': 160, 'width': 120,
}] }]
return columns return columns
@@ -72,7 +83,7 @@ def get_data(filters):
'leave_type': leave_type 'leave_type': leave_type
}) })
for employee in active_employees: for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver) leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
@@ -82,16 +93,13 @@ def get_data(filters):
'employee_name': employee.employee_name 'employee_name': employee.employee_name
}) })
leaves_taken = get_leaves_for_period(employee.name, leave_type, leave_details = calculate_leaves_details(filters, leave_type, employee)
filters.from_date, filters.to_date) * -1 row.opening_balance = flt(leave_details[0])
row.new_allocation = flt(leave_details[1])
row.expired_leaves = flt(leave_details[2])
row.leaves_taken = flt(leave_details[3])
row.closing_balance = flt(leave_details[4])
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
row.opening_balance = opening
row.leaves_taken = leaves_taken
row.closing_balance = closing
row.indent = 1
data.append(row) data.append(row)
return data return data
@@ -108,23 +116,3 @@ def get_conditions(filters):
return conditions return conditions
def get_department_leave_approver_map(department=None):
conditions=''
if department:
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all('Department Approver', filters={
'parentfield': 'leave_approvers',
'parent': ('in', department_list)
}, fields=['parent', 'approver'], as_list=1)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers

View File

@@ -9,6 +9,8 @@ from frappe.model.document import Document
from frappe.desk.form import assign_to from frappe.desk.form import assign_to
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class DuplicateDeclarationError(frappe.ValidationError): pass
class EmployeeBoardingController(Document): class EmployeeBoardingController(Document):
''' '''
Create the project and the task for the boarding process Create the project and the task for the boarding process
@@ -226,6 +228,17 @@ def get_employee_leave_policy(employee):
else: else:
frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee)) frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee))
def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
existing_record = frappe.db.exists(doctype, {
"payroll_period": payroll_period,
"employee": employee,
'docstatus': ['<', 2],
'name': ['!=', docname]
})
if existing_record:
frappe.throw(_("{0} already exists for employee {1} and period {2}")
.format(doctype, employee, payroll_period), DuplicateDeclarationError)
def validate_tax_declaration(declarations): def validate_tax_declaration(declarations):
subcategories = [] subcategories = []
for d in declarations: for d in declarations:

View File

@@ -236,12 +236,13 @@ class BOM(WebsiteGenerator):
if rate: if rate:
d.rate = rate d.rate = rate
d.amount = flt(d.rate) * flt(d.qty) d.amount = flt(d.rate) * flt(d.qty)
d.db_update()
if self.docstatus == 1: if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True self.flags.ignore_validate_update_after_submit = True
self.calculate_cost() self.calculate_cost()
if save: if save:
self.save() self.db_update()
self.update_exploded_items() self.update_exploded_items()
# update parent BOMs # update parent BOMs

View File

@@ -82,7 +82,7 @@ def enqueue_replace_bom(args):
@frappe.whitelist() @frappe.whitelist()
def enqueue_update_cost(): def enqueue_update_cost():
frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost") frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000)
frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")) frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes."))
def update_latest_price_in_all_boms(): def update_latest_price_in_all_boms():
@@ -98,6 +98,9 @@ def replace_bom(args):
doc.replace_bom() doc.replace_bom()
def update_cost(): def update_cost():
frappe.db.auto_commit_on_many_writes = 1
bom_list = get_boms_in_bottom_up_order() bom_list = get_boms_in_bottom_up_order()
for bom in bom_list: for bom in bom_list:
frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True)
frappe.db.auto_commit_on_many_writes = 0

View File

@@ -5,6 +5,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
test_records = frappe.get_test_records('BOM') test_records = frappe.get_test_records('BOM')
@@ -27,4 +30,31 @@ class TestBOMUpdateTool(unittest.TestCase):
# reverse, as it affects other testcases # reverse, as it affects other testcases
update_tool.current_bom = bom_doc.name update_tool.current_bom = bom_doc.name
update_tool.new_bom = current_bom update_tool.new_bom = current_bom
update_tool.replace_bom() update_tool.replace_bom()
def test_bom_cost(self):
for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]:
item_doc = create_item(item, valuation_rate=100)
if item_doc.valuation_rate != 100.00:
frappe.db.set_value("Item", item_doc.name, "valuation_rate", 100)
bom_no = frappe.db.get_value('BOM', {'item': 'BOM Cost Test Item 1'}, "name")
if not bom_no:
doc = make_bom(item = 'BOM Cost Test Item 1',
raw_materials =['BOM Cost Test Item 2', 'BOM Cost Test Item 3'], currency="INR")
else:
doc = frappe.get_doc("BOM", bom_no)
self.assertEquals(doc.total_cost, 200)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200)
update_cost()
doc.load_from_db()
self.assertEquals(doc.total_cost, 300)
frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100)
update_cost()
doc.load_from_db()
self.assertEquals(doc.total_cost, 200)

View File

@@ -192,9 +192,10 @@ def make_bom(**args):
args = frappe._dict(args) args = frappe._dict(args)
bom = frappe.get_doc({ bom = frappe.get_doc({
'doctype': "BOM", 'doctype': 'BOM',
'is_default': 1, 'is_default': 1,
'item': args.item, 'item': args.item,
'currency': args.currency or 'USD',
'quantity': args.quantity or 1, 'quantity': args.quantity or 1,
'company': args.company or '_Test Company' 'company': args.company or '_Test Company'
}) })
@@ -211,4 +212,5 @@ def make_bom(**args):
}) })
bom.insert(ignore_permissions=True) bom.insert(ignore_permissions=True)
bom.submit() bom.submit()
return bom

View File

@@ -18,10 +18,10 @@ def get_columns():
"""return columns""" """return columns"""
columns = [ columns = [
_("Item") + ":Link/Item:150", _("Item") + ":Link/Item:150",
_("Description") + "::500", _("Description") + "::300",
_("Qty per BOM Line") + ":Float:100", _("BOM Qty") + ":Float:160",
_("Required Qty") + ":Float:100", _("Required Qty") + ":Float:120",
_("In Stock Qty") + ":Float:100", _("In Stock Qty") + ":Float:120",
_("Enough Parts to Build") + ":Float:200", _("Enough Parts to Build") + ":Float:200",
] ]
@@ -59,13 +59,14 @@ def get_bom_stock(filters):
bom_item.item_code, bom_item.item_code,
bom_item.description , bom_item.description ,
bom_item.{qty_field}, bom_item.{qty_field},
bom_item.{qty_field} * {qty_to_produce}, bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
sum(ledger.actual_qty) as actual_qty, sum(ledger.actual_qty) as actual_qty,
sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce}))) sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))
FROM FROM
{table} AS bom_item `tabBOM` AS bom INNER JOIN {table} AS bom_item
ON bom.name = bom_item.parent
LEFT JOIN `tabBin` AS ledger LEFT JOIN `tabBin` AS ledger
ON bom_item.item_code = ledger.item_code ON bom_item.item_code = ledger.item_code
{conditions} {conditions}
WHERE WHERE
bom_item.parent = '{bom}' and bom_item.parenttype='BOM' bom_item.parent = '{bom}' and bom_item.parenttype='BOM'

View File

@@ -651,6 +651,7 @@ erpnext.patches.v12_0.update_price_or_product_discount
erpnext.patches.v12_0.add_export_type_field_in_party_master erpnext.patches.v12_0.add_export_type_field_in_party_master
erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22 erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
erpnext.patches.v12_0.create_irs_1099_field_united_states erpnext.patches.v12_0.create_irs_1099_field_united_states
erpnext.patches.v12_0.add_permission_in_lower_deduction
erpnext.patches.v12_0.set_permission_einvoicing erpnext.patches.v12_0.set_permission_einvoicing
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
erpnext.patches.v12_0.recalculate_requested_qty_in_bin erpnext.patches.v12_0.recalculate_requested_qty_in_bin
@@ -658,3 +659,5 @@ erpnext.patches.v12_0.rename_mws_settings_fields
erpnext.patches.v12_0.set_correct_status_for_expense_claim erpnext.patches.v12_0.set_correct_status_for_expense_claim
erpnext.patches.v12_0.set_updated_purpose_in_pick_list erpnext.patches.v12_0.set_updated_purpose_in_pick_list
erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123

View File

@@ -6,4 +6,5 @@ def execute():
if not company: if not company:
return return
frappe.reload_doc("regional", "doctype", "lower_deduction_certificate")
add_permissions() add_permissions()

View File

@@ -5,8 +5,7 @@ def execute():
frappe.reload_doc('hr', 'doctype', 'salary_detail') frappe.reload_doc('hr', 'doctype', 'salary_detail')
frappe.reload_doc('hr', 'doctype', 'salary_component') frappe.reload_doc('hr', 'doctype', 'salary_component')
frappe.db.sql("update `tabSalary Component` set is_payable=1, is_tax_applicable=1 where type='Earning'") frappe.db.sql("update `tabSalary Component` set is_tax_applicable=1 where type='Earning'")
frappe.db.sql("update `tabSalary Component` set is_payable=0 where type='Deduction'")
frappe.db.sql("""update `tabSalary Component` set variable_based_on_taxable_salary=1 frappe.db.sql("""update `tabSalary Component` set variable_based_on_taxable_salary=1
where type='Deduction' and name in ('TDS', 'Tax Deducted at Source')""") where type='Deduction' and name in ('TDS', 'Tax Deducted at Source')""")

View File

@@ -0,0 +1,13 @@
import frappe
from frappe.permissions import add_permission, update_permission_property
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
frappe.reload_doc('regional', 'doctype', 'Lower Deduction Certificate')
add_permission('Lower Deduction Certificate', 'Accounts Manager', 0)
update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'write', 1)
update_permission_property('Lower Deduction Certificate', 'Accounts Manager', 0, 'create', 1)

View File

@@ -0,0 +1,24 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import add_days, getdate, today
def execute():
if frappe.db.exists('DocType', 'Email Campaign'):
email_campaign = frappe.get_all('Email Campaign')
for campaign in email_campaign:
doc = frappe.get_doc("Email Campaign",campaign["name"])
send_after_days = []
camp = frappe.get_doc("Campaign", doc.campaign_name)
for entry in camp.get("campaign_schedules"):
send_after_days.append(entry.send_after_days)
if send_after_days:
end_date = add_days(getdate(doc.start_date), max(send_after_days))
doc.db_set("end_date", end_date)
today_date = getdate(today())
if doc.start_date > today_date:
doc.db_set("status", "Scheduled")
elif end_date >= today_date:
doc.db_set("status", "In Progress")
elif end_date < today_date:
doc.db_set("status", "Completed")

View File

View File

@@ -0,0 +1,99 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
if not frappe.db.table_exists("Payroll Period"):
return
for doctype in ("income_tax_slab", "salary_structure_assignment", "employee_other_income", "income_tax_slab_other_charges"):
frappe.reload_doc("hr", "doctype", doctype)
for company in frappe.get_all("Company"):
payroll_periods = frappe.db.sql("""
SELECT
name, start_date, end_date, standard_tax_exemption_amount
FROM
`tabPayroll Period`
WHERE company=%s
ORDER BY start_date DESC
""", company.name, as_dict = 1)
for i, period in enumerate(payroll_periods):
income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.name = "Tax Slab:" + period.name
if i == 0:
income_tax_slab.disabled = 0
else:
income_tax_slab.disabled = 1
income_tax_slab.effective_from = period.start_date
income_tax_slab.company = company.name
income_tax_slab.allow_tax_exemption = 1
income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount
income_tax_slab.flags.ignore_mandatory = True
income_tax_slab.submit()
frappe.db.sql(
""" UPDATE `tabTaxable Salary Slab`
SET parent = %s , parentfield = 'slabs' , parenttype = "Income Tax Slab"
WHERE parent = %s
""", (income_tax_slab.name, period.name), as_dict = 1)
if i == 0:
frappe.db.sql("""
UPDATE
`tabSalary Structure Assignment`
set
income_tax_slab = %s
where
company = %s
and from_date >= %s
and docstatus < 2
""", (income_tax_slab.name, company.name, period.start_date))
# move other incomes to separate document
migrated = []
proofs = frappe.get_all("Employee Tax Exemption Proof Submission",
filters = {'docstatus': 1},
fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']
)
for proof in proofs:
if proof.income_from_other_sources:
employee_other_income = frappe.new_doc("Employee Other Income")
employee_other_income.employee = proof.employee
employee_other_income.payroll_period = proof.payroll_period
employee_other_income.company = proof.company
employee_other_income.amount = proof.income_from_other_sources
try:
employee_other_income.submit()
migrated.append([proof.employee, proof.payroll_period])
except:
pass
declerations = frappe.get_all("Employee Tax Exemption Declaration",
filters = {'docstatus': 1},
fields =['payroll_period', 'employee', 'company', 'income_from_other_sources']
)
for declaration in declerations:
if declaration.income_from_other_sources \
and [declaration.employee, declaration.payroll_period] not in migrated:
employee_other_income = frappe.new_doc("Employee Other Income")
employee_other_income.employee = declaration.employee
employee_other_income.payroll_period = declaration.payroll_period
employee_other_income.company = declaration.company
employee_other_income.amount = declaration.income_from_other_sources
try:
employee_other_income.submit()
except:
pass

View File

@@ -175,6 +175,20 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}; };
} }
if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) {
this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) {
var item = locals[cdt][cdn];
return {
query: "erpnext.controllers.queries.get_blanket_orders",
filters: {
"company": doc.company,
"blanket_order_type": doc.doctype === "Sales Order" ? "Selling" : "Purchasing",
"item": item.item_code
}
}
});
}
}, },
onload: function() { onload: function() {
var me = this; var me = this;
@@ -1374,7 +1388,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.frm.doc.items.forEach(d => { me.frm.doc.items.forEach(d => {
if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) { if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) {
for(var k in data) { for(var k in data) {
if (in_list(fields, k) && data[k]) { if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'price' || k === 'pricing_rules')) {
frappe.model.set_value(d.doctype, d.name, k, data[k]); frappe.model.set_value(d.doctype, d.name, k, data[k]);
} }
} }

View File

@@ -24,7 +24,7 @@ doctypes_with_dimensions.forEach((doctype) => {
onload: function(frm) { onload: function(frm) {
erpnext.dimension_filters.forEach((dimension) => { erpnext.dimension_filters.forEach((dimension) => {
frappe.model.with_doctype(dimension['document_type'], () => { frappe.model.with_doctype(dimension['document_type'], () => {
if (frappe.meta.has_field(dimension['document_type'], 'is_group')) { if(frappe.meta.has_field(dimension['document_type'], 'is_group')) {
frm.set_query(dimension['fieldname'], { frm.set_query(dimension['fieldname'], {
"is_group": 0 "is_group": 0
}); });
@@ -42,19 +42,21 @@ doctypes_with_dimensions.forEach((doctype) => {
update_dimension: function(frm) { update_dimension: function(frm) {
erpnext.dimension_filters.forEach((dimension) => { erpnext.dimension_filters.forEach((dimension) => {
if (frm.is_new()) { if(frm.is_new()) {
if (frm.doc.company && Object.keys(default_dimensions || {}).length > 0 if(frm.doc.company && Object.keys(default_dimensions || {}).length > 0
&& default_dimensions[frm.doc.company]) { && default_dimensions[frm.doc.company]) {
if (frappe.meta.has_field(doctype, dimension['fieldname'])) { let default_dimension = default_dimensions[frm.doc.company][dimension['document_type']];
frm.set_value(dimension['fieldname'],
default_dimensions[frm.doc.company][dimension['document_type']]);
}
$.each(frm.doc.items || frm.doc.accounts || [], function(i, row) { if(default_dimension) {
frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
default_dimensions[frm.doc.company][dimension['document_type']]) frm.set_value(dimension['fieldname'], default_dimension);
}); }
$.each(frm.doc.items || frm.doc.accounts || [], function(i, row) {
frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], default_dimension);
});
}
} }
} }
}); });
@@ -71,20 +73,6 @@ child_docs.forEach((doctype) => {
}); });
}, },
accounts_add: function(frm, cdt, cdn) {
erpnext.dimension_filters.forEach((dimension) => {
var row = frappe.get_doc(cdt, cdn);
frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]);
});
},
items_add: function(frm, cdt, cdn) {
erpnext.dimension_filters.forEach((dimension) => {
var row = frappe.get_doc(cdt, cdn);
frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]);
});
},
accounts_add: function(frm, cdt, cdn) { accounts_add: function(frm, cdt, cdn) {
erpnext.dimension_filters.forEach((dimension) => { erpnext.dimension_filters.forEach((dimension) => {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Lower Deduction Certificate', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,138 @@
{
"actions": [],
"autoname": "field:certificate_no",
"creation": "2020-03-10 23:12:10.072631",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"certificate_details_section",
"section_code",
"fiscal_year",
"column_break_3",
"certificate_no",
"section_break_3",
"supplier",
"column_break_7",
"pan_no",
"validity_details_section",
"valid_from",
"column_break_10",
"valid_upto",
"section_break_9",
"rate",
"column_break_14",
"certificate_limit"
],
"fields": [
{
"fieldname": "certificate_no",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Certificate No",
"reqd": 1,
"unique": 1
},
{
"fieldname": "section_code",
"fieldtype": "Select",
"label": "Section Code",
"options": "192\n193\n194\n194A\n194C\n194D\n194H\n194I\n194J\n194LA\n194LBB\n194LBC\n195",
"reqd": 1
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "Deductee Details"
},
{
"fieldname": "supplier",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Supplier",
"options": "Supplier",
"reqd": 1
},
{
"fetch_from": "supplier.pan",
"fetch_if_empty": 1,
"fieldname": "pan_no",
"fieldtype": "Data",
"in_list_view": 1,
"label": "PAN No",
"reqd": 1
},
{
"fieldname": "validity_details_section",
"fieldtype": "Section Break",
"label": "Validity Details"
},
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto",
"reqd": 1
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"fieldname": "rate",
"fieldtype": "Percent",
"label": "Rate Of TDS As Per Certificate",
"reqd": 1
},
{
"fieldname": "certificate_limit",
"fieldtype": "Currency",
"label": "Certificate Limit",
"reqd": 1
},
{
"fieldname": "certificate_details_section",
"fieldtype": "Section Break",
"label": "Certificate Details"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"fieldname": "valid_from",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Valid From",
"reqd": 1
},
{
"fieldname": "fiscal_year",
"fieldtype": "Link",
"label": "Fiscal Year",
"options": "Fiscal Year",
"reqd": 1
}
],
"links": [],
"modified": "2020-04-23 23:04:41.203721",
"modified_by": "Administrator",
"module": "Regional",
"name": "Lower Deduction Certificate",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import getdate
from frappe.model.document import Document
from erpnext.accounts.utils import get_fiscal_year
class LowerDeductionCertificate(Document):
def validate(self):
if getdate(self.valid_upto) < getdate(self.valid_from):
frappe.throw(_("Valid Upto date cannot be before Valid From date"))
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
if not (fiscal_year.year_start_date <= getdate(self.valid_from) \
<= fiscal_year.year_end_date):
frappe.throw(_("Valid From date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))
if not (fiscal_year.year_start_date <= getdate(self.valid_upto) \
<= fiscal_year.year_end_date):
frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year)))

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestLowerDeductionCertificate(unittest.TestCase):
pass

View File

@@ -77,7 +77,7 @@ def add_custom_roles_for_reports():
)).insert() )).insert()
def add_permissions(): def add_permissions():
for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report'): for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
add_permission(doctype, 'All', 0) add_permission(doctype, 'All', 0)
for role in ('Accounts Manager', 'Accounts User', 'System Manager'): for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
add_permission(doctype, role, 0) add_permission(doctype, role, 0)
@@ -530,12 +530,18 @@ def make_fixtures(company=None):
def set_salary_components(docs): def set_salary_components(docs):
docs.extend([ docs.extend([
{'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'}, {'doctype': 'Salary Component', 'salary_component': 'Professional Tax',
{'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'}, 'description': 'Professional Tax', 'type': 'Deduction', 'exempted_from_income_tax': 1},
{'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'}, {'doctype': 'Salary Component', 'salary_component': 'Provident Fund',
{'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}, 'description': 'Provident fund', 'type': 'Deduction', 'is_tax_applicable': 1},
{'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'}, {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance',
{'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'} 'description': 'House Rent Allowance', 'type': 'Earning', 'is_tax_applicable': 1},
{'doctype': 'Salary Component', 'salary_component': 'Basic',
'description': 'Basic', 'type': 'Earning', 'is_tax_applicable': 1},
{'doctype': 'Salary Component', 'salary_component': 'Arrear',
'description': 'Arrear', 'type': 'Earning', 'is_tax_applicable': 1},
{'doctype': 'Salary Component', 'salary_component': 'Leave Encashment',
'description': 'Leave Encashment', 'type': 'Earning', 'is_tax_applicable': 1}
]) ])
def set_tax_withholding_category(company): def set_tax_withholding_category(company):

View File

@@ -360,8 +360,6 @@ def get_ewb_data(dt, dn):
if dt != 'Sales Invoice': if dt != 'Sales Invoice':
frappe.throw(_('e-Way Bill JSON can only be generated from Sales Invoice')) frappe.throw(_('e-Way Bill JSON can only be generated from Sales Invoice'))
dn = dn.split(',')
ewaybills = [] ewaybills = []
for doc_name in dn: for doc_name in dn:
doc = frappe.get_doc(dt, doc_name) doc = frappe.get_doc(dt, doc_name)
@@ -439,16 +437,22 @@ def get_ewb_data(dt, dn):
@frappe.whitelist() @frappe.whitelist()
def generate_ewb_json(dt, dn): def generate_ewb_json(dt, dn):
dn = json.loads(dn)
return get_ewb_data(dt, dn)
data = get_ewb_data(dt, dn) @frappe.whitelist()
def download_ewb_json():
data = frappe._dict(frappe.local.form_dict)
frappe.local.response.filecontent = json.dumps(data, indent=4, sort_keys=True) frappe.local.response.filecontent = json.dumps(data['data'], indent=4, sort_keys=True)
frappe.local.response.type = 'download' frappe.local.response.type = 'download'
if len(data['billLists']) > 1: billList = json.loads(data['data'])['billLists']
if len(billList) > 1:
doc_name = 'Bulk' doc_name = 'Bulk'
else: else:
doc_name = dn doc_name = data['docname']
frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(doc_name, frappe.utils.random_string(5)) frappe.local.response.filename = '{0}_e-WayBill_Data_{1}.json'.format(doc_name, frappe.utils.random_string(5))

View File

@@ -65,15 +65,6 @@ frappe.ui.form.on("Sales Order", {
} }
}); });
frm.set_query("blanket_order", "items", function() {
return {
filters: {
"company": frm.doc.company,
"docstatus": 1
}
}
});
erpnext.queries.setup_warehouse_query(frm); erpnext.queries.setup_warehouse_query(frm);
}, },

View File

@@ -0,0 +1,12 @@
from frappe import _
def get_data():
return {
'fieldname': 'quality_inspection_template',
'transactions': [
{
'label': _('Quality Inspection'),
'items': ['Quality Inspection']
}
]
}

View File

@@ -305,10 +305,9 @@ frappe.ui.form.on('Stock Entry', {
callback: function(r) { callback: function(r) {
if (!r.exe && r.message){ if (!r.exe && r.message){
frappe.model.set_value(cdt, cdn, "serial_no", r.message); frappe.model.set_value(cdt, cdn, "serial_no", r.message);
}
if (callback) { if (callback) {
callback(); callback();
}
} }
} }
}); });
@@ -425,9 +424,10 @@ frappe.ui.form.on('Stock Entry', {
item.amount = flt(item.basic_amount + flt(item.additional_cost), item.amount = flt(item.basic_amount + flt(item.additional_cost),
precision("amount", item)); precision("amount", item));
item.valuation_rate = flt(flt(item.basic_rate) if (flt(item.transfer_qty)) {
+ (flt(item.additional_cost) / flt(item.transfer_qty)), item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)),
precision("valuation_rate", item)); precision("valuation_rate", item));
}
} }
refresh_field('items'); refresh_field('items');

View File

@@ -170,6 +170,8 @@ def get_item_warehouse_map(filters, sle):
from_date = getdate(filters.get("from_date")) from_date = getdate(filters.get("from_date"))
to_date = getdate(filters.get("to_date")) to_date = getdate(filters.get("to_date"))
float_precision = cint(frappe.db.get_default("float_precision")) or 3
for d in sle: for d in sle:
key = (d.company, d.item_code, d.warehouse) key = (d.company, d.item_code, d.warehouse)
if key not in iwb_map: if key not in iwb_map:
@@ -184,7 +186,7 @@ def get_item_warehouse_map(filters, sle):
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)] qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
if d.voucher_type == "Stock Reconciliation": if d.voucher_type == "Stock Reconciliation":
qty_diff = flt(d.qty_after_transaction) - qty_dict.bal_qty qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty)
else: else:
qty_diff = flt(d.actual_qty) qty_diff = flt(d.actual_qty)
@@ -195,7 +197,7 @@ def get_item_warehouse_map(filters, sle):
qty_dict.opening_val += value_diff qty_dict.opening_val += value_diff
elif d.posting_date >= from_date and d.posting_date <= to_date: elif d.posting_date >= from_date and d.posting_date <= to_date:
if qty_diff > 0: if flt(qty_diff, float_precision) >= 0:
qty_dict.in_qty += qty_diff qty_dict.in_qty += qty_diff
qty_dict.in_val += value_diff qty_dict.in_val += value_diff
else: else:
@@ -206,16 +208,15 @@ def get_item_warehouse_map(filters, sle):
qty_dict.bal_qty += qty_diff qty_dict.bal_qty += qty_diff
qty_dict.bal_val += value_diff qty_dict.bal_val += value_diff
iwb_map = filter_items_with_no_transactions(iwb_map) iwb_map = filter_items_with_no_transactions(iwb_map, float_precision)
return iwb_map return iwb_map
def filter_items_with_no_transactions(iwb_map): def filter_items_with_no_transactions(iwb_map, float_precision):
for (company, item, warehouse) in sorted(iwb_map): for (company, item, warehouse) in sorted(iwb_map):
qty_dict = iwb_map[(company, item, warehouse)] qty_dict = iwb_map[(company, item, warehouse)]
no_transactions = True no_transactions = True
float_precision = cint(frappe.db.get_default("float_precision")) or 3
for key, val in iteritems(qty_dict): for key, val in iteritems(qty_dict):
val = flt(val, float_precision) val = flt(val, float_precision)
qty_dict[key] = val qty_dict[key] = val

View File

@@ -16,7 +16,11 @@
<tr> <tr>
<td>{{ item }}</td> <td>{{ item }}</td>
<td class='text-right'> <td class='text-right'>
{{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, currency) }} {% if doc.get('is_return') %}
{{ frappe.utils.fmt_money((itemised_taxable_amount.get(item, 0))|abs, None, doc.currency) }}
{% else %}
{{ frappe.utils.fmt_money(itemised_taxable_amount.get(item, 0), None, doc.currency) }}
{% endif %}
</td> </td>
{% for tax_account in tax_accounts %} {% for tax_account in tax_accounts %}
{% set tax_details = taxes.get(tax_account) %} {% set tax_details = taxes.get(tax_account) %}
@@ -25,7 +29,11 @@
{% if tax_details.tax_rate or not tax_details.tax_amount %} {% if tax_details.tax_rate or not tax_details.tax_amount %}
({{ tax_details.tax_rate }}%) ({{ tax_details.tax_rate }}%)
{% endif %} {% endif %}
{{ frappe.utils.fmt_money(tax_details.tax_amount / conversion_rate, None, currency) }} {% if doc.get('is_return') %}
{{ frappe.utils.fmt_money((tax_details.tax_amount / doc.conversion_rate)|abs, None, doc.currency) }}
{% else %}
{{ frappe.utils.fmt_money(tax_details.tax_amount / doc.conversion_rate, None, doc.currency) }}
{% endif %}
</td> </td>
{% else %} {% else %}
<td></td> <td></td>