Merge branch 'develop' of https://github.com/frappe/erpnext into develop

This commit is contained in:
Abhishek Balam 2020-04-29 12:02:13 +05:30
commit 0c4ce4dbba
106 changed files with 2701 additions and 2146 deletions

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
}) })

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

@ -13,6 +13,7 @@
"supplier_name", "supplier_name",
"tax_id", "tax_id",
"due_date", "due_date",
"tax_withholding_category",
"column_break1", "column_break1",
"company", "company",
"posting_date", "posting_date",
@ -1294,13 +1295,21 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Internal Supplier", "label": "Is Internal Supplier",
"read_only": 1 "read_only": 1
},
{
"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

@ -1002,7 +1002,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

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-05-21 16:16:04", "creation": "2013-05-21 16:16:04",
"doctype": "DocType", "doctype": "DocType",
@ -14,11 +15,11 @@
"col_break1", "col_break1",
"account_head", "account_head",
"description", "description",
"section_break_10",
"rate",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"section_break_10",
"rate",
"section_break_9", "section_break_9",
"tax_amount", "tax_amount",
"tax_amount_after_discount_amount", "tax_amount_after_discount_amount",
@ -27,8 +28,7 @@
"base_tax_amount", "base_tax_amount",
"base_total", "base_total",
"base_tax_amount_after_discount_amount", "base_tax_amount_after_discount_amount",
"item_wise_tax_detail", "item_wise_tax_detail"
"parenttype"
], ],
"fields": [ "fields": [
{ {
@ -53,6 +53,7 @@
}, },
{ {
"columns": 2, "columns": 2,
"default": "On Net Total",
"fieldname": "charge_type", "fieldname": "charge_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
@ -196,15 +197,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "parenttype",
"fieldtype": "Data",
"hidden": 1,
"label": "Parenttype",
"oldfieldname": "parenttype",
"oldfieldtype": "Data",
"print_hide": 1
},
{ {
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -217,11 +209,14 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-25 23:08:38.281025", "links": [],
"modified": "2020-03-12 14:53:47.679439",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Taxes and Charges", "name": "Purchase Taxes and Charges",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -26,16 +26,24 @@ frappe.ui.form.on("Sales Invoice", {
&& !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',
data: r.message,
docname: frm.doc.name
};
open_url_post(frappe.request.url, args);
} }
}
});
}, __("Create")); }, __("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

@ -587,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
} }
}; };
}); });
@ -668,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
} }
} }
}; };
@ -677,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

@ -1892,7 +1892,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

@ -84,6 +84,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
def get_profit_loss_data(fiscal_year, companies, columns, filters): def get_profit_loss_data(fiscal_year, companies, columns, filters):
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
company_currency = get_company_currency(filters)
data = [] data = []
data.extend(income or []) data.extend(income or [])
@ -93,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, True) report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
return data, None, chart, report_summary return data, None, chart, report_summary

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

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",
@ -365,9 +356,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request", source_doctype: "Material Request",
target: me.frm, target: me.frm,
setters: { setters: {},
company: me.frm.doc.company
},
get_query_filters: { get_query_filters: {
material_request_type: "Purchase", material_request_type: "Purchase",
docstatus: 1, docstatus: 1,
@ -384,7 +373,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
source_doctype: "Supplier Quotation", source_doctype: "Supplier Quotation",
target: me.frm, target: me.frm,
setters: { setters: {
company: me.frm.doc.company supplier: me.frm.doc.supplier
}, },
get_query_filters: { get_query_filters: {
docstatus: 1, docstatus: 1,

View File

@ -54,11 +54,6 @@
"items_section", "items_section",
"scan_barcode", "scan_barcode",
"items", "items",
"section_break_48",
"pricing_rules",
"raw_material_details",
"set_reserve_warehouse",
"supplied_items",
"sb_last_purchase", "sb_last_purchase",
"total_qty", "total_qty",
"base_total", "base_total",
@ -67,6 +62,11 @@
"total_net_weight", "total_net_weight",
"total", "total",
"net_total", "net_total",
"section_break_48",
"pricing_rules",
"raw_material_details",
"set_reserve_warehouse",
"supplied_items",
"taxes_section", "taxes_section",
"tax_category", "tax_category",
"column_break_50", "column_break_50",
@ -105,23 +105,25 @@
"payment_schedule_section", "payment_schedule_section",
"payment_terms_template", "payment_terms_template",
"payment_schedule", "payment_schedule",
"tracking_section",
"per_billed",
"column_break_75",
"per_received",
"terms_section_break", "terms_section_break",
"tc_name", "tc_name",
"terms", "terms",
"more_info", "more_info",
"status", "status",
"ref_sq", "ref_sq",
"column_break_74",
"party_account_currency", "party_account_currency",
"inter_company_order_reference", "inter_company_order_reference",
"column_break_74",
"per_received",
"per_billed",
"column_break5", "column_break5",
"letter_head", "letter_head",
"select_print_heading", "select_print_heading",
"column_break_86", "column_break_86",
"group_same_items",
"language", "language",
"group_same_items",
"subscription_section", "subscription_section",
"from_date", "from_date",
"to_date", "to_date",
@ -220,7 +222,7 @@
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Reqd By Date" "label": "Required By"
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@ -432,6 +434,7 @@
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{ {
"description": "Sets 'Warehouse' in each row of the Items table.",
"fieldname": "set_warehouse", "fieldname": "set_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Set Target Warehouse", "label": "Set Target Warehouse",
@ -827,6 +830,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"fieldname": "payment_schedule_section", "fieldname": "payment_schedule_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Payment Terms" "label": "Payment Terms"
@ -917,7 +921,8 @@
"fieldname": "inter_company_order_reference", "fieldname": "inter_company_order_reference",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Inter Company Order Reference", "label": "Inter Company Order Reference",
"options": "Sales Order" "options": "Sales Order",
"read_only": 1
}, },
{ {
"fieldname": "column_break_74", "fieldname": "column_break_74",
@ -930,8 +935,6 @@
"in_list_view": 1, "in_list_view": 1,
"label": "% Received", "label": "% Received",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "per_received",
"oldfieldtype": "Currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -942,8 +945,6 @@
"in_list_view": 1, "in_list_view": 1,
"label": "% Billed", "label": "% Billed",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "per_billed",
"oldfieldtype": "Currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -998,6 +999,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"collapsible": 1,
"fieldname": "subscription_section", "fieldname": "subscription_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Subscription Section" "label": "Subscription Section"
@ -1050,13 +1052,23 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Set Reserve Warehouse", "label": "Set Reserve Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
"label": "Tracking"
},
{
"fieldname": "column_break_75",
"fieldtype": "Column Break"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-17 13:04:28.185197", "modified": "2020-04-24 12:13:14.186280",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@ -18,10 +18,6 @@
"col_break1", "col_break1",
"image", "image",
"image_view", "image_view",
"manufacture_details",
"manufacturer",
"column_break_14",
"manufacturer_part_no",
"quantity_and_rate", "quantity_and_rate",
"qty", "qty",
"stock_uom", "stock_uom",
@ -44,7 +40,6 @@
"base_amount", "base_amount",
"pricing_rules", "pricing_rules",
"is_free_item", "is_free_item",
"is_fixed_asset",
"section_break_29", "section_break_29",
"net_rate", "net_rate",
"net_amount", "net_amount",
@ -52,11 +47,6 @@
"base_net_rate", "base_net_rate",
"base_net_amount", "base_net_amount",
"billed_amt", "billed_amt",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_40",
"weight_uom",
"warehouse_and_reference", "warehouse_and_reference",
"warehouse", "warehouse",
"delivered_by_supplier", "delivered_by_supplier",
@ -80,20 +70,31 @@
"column_break_60", "column_break_60",
"received_qty", "received_qty",
"returned_qty", "returned_qty",
"manufacture_details",
"manufacturer",
"column_break_14",
"manufacturer_part_no",
"more_info_section_break",
"is_fixed_asset",
"item_tax_rate",
"accounting_details", "accounting_details",
"expense_account", "expense_account",
"column_break_68", "column_break_68",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_40",
"weight_uom",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"section_break_72", "section_break_72",
"page_break", "page_break"
"item_tax_rate"
], ],
"fields": [ "fields": [
{ {
"bold": 1, "bold": 1,
"columns": 3, "columns": 2,
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
@ -133,7 +134,7 @@
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Reqd By Date", "label": "Required By",
"oldfieldname": "schedule_date", "oldfieldname": "schedule_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"print_hide": 1, "print_hide": 1,
@ -216,15 +217,16 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"columns": 1,
"fieldname": "uom", "fieldname": "uom",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "UOM", "label": "UOM",
"oldfieldname": "uom", "oldfieldname": "uom",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "UOM", "options": "UOM",
"print_width": "100px", "print_width": "100px",
"reqd": 1, "reqd": 1
"width": "100px"
}, },
{ {
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
@ -685,6 +687,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"collapsible": 1,
"fieldname": "manufacture_details", "fieldname": "manufacture_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Manufacture" "label": "Manufacture"
@ -717,12 +720,17 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Fixed Asset", "label": "Is Fixed Asset",
"read_only": 1 "read_only": 1
},
{
"fieldname": "more_info_section_break",
"fieldtype": "Section Break",
"label": "More Information"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-04-07 18:35:17.558928", "modified": "2020-04-21 11:55:58.643393",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@ -1,20 +1,26 @@
{ {
"actions": [],
"creation": "2013-02-22 01:27:42", "creation": "2013-02-22 01:27:42",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB",
"field_order": [ "field_order": [
"main_item_code", "main_item_code",
"rm_item_code",
"required_qty",
"supplied_qty",
"rate",
"amount",
"column_break_6",
"bom_detail_no", "bom_detail_no",
"reference_name",
"conversion_factor",
"stock_uom", "stock_uom",
"reserve_warehouse" "conversion_factor",
"column_break_6",
"rm_item_code",
"reference_name",
"reserve_warehouse",
"section_break2",
"rate",
"col_break2",
"amount",
"section_break1",
"required_qty",
"col_break1",
"supplied_qty"
], ],
"fields": [ "fields": [
{ {
@ -120,15 +126,34 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Supplied Qty", "label": "Supplied Qty",
"read_only": 1 "read_only": 1
},
{
"fieldname": "section_break1",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break1",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break2",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break2",
"fieldtype": "Column Break"
} }
], ],
"hide_toolbar": 1, "hide_toolbar": 1,
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-08-20 13:37:32.702068", "links": [],
"modified": "2020-03-12 15:43:53.862897",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item Supplied", "name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com", "owner": "dhanalekshmi@webnotestech.com",
"permissions": [] "permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
} }

View File

@ -23,11 +23,7 @@ def get_data():
}, },
{ {
'label': _('Payments'), 'label': _('Payments'),
'items': ['Payment Entry'] 'items': ['Payment Entry', 'Bank Account']
},
{
'label': _('Bank'),
'items': ['Bank Account']
}, },
{ {
'label': _('Pricing'), 'label': _('Pricing'),

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

@ -371,6 +371,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

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.utils import cint, flt, cstr from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
from frappe import _ from frappe import _
import frappe.defaults import frappe.defaults
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
@ -55,6 +55,13 @@ class StockController(AccountsController):
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
.format(d.idx, serial_no_data.name, d.batch_no)) .format(d.idx, serial_no_data.name, d.batch_no))
if d.qty > 0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):

View File

@ -12,13 +12,18 @@
}, },
{ {
"hidden": 0, "hidden": 0,
"label": "Settings", "label": "Maintenance",
"links": "[\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sends Mails to lead or contact based on a Campaign schedule\",\n \"label\": \"Email Campaign\",\n \"name\": \"Email Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Send mass SMS to your contacts\",\n \"label\": \"SMS Center\",\n \"name\": \"SMS Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Logs for maintaining sms delivery status\",\n \"label\": \"SMS Log\",\n \"name\": \"SMS Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup SMS gateway settings\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"description\": \"Plan for maintenance visits.\",\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Visit report for maintenance call.\",\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
"label": "Maintenance", "label": "Campaign",
"links": "[\n {\n \"description\": \"Plan for maintenance visits.\",\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Visit report for maintenance call.\",\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sends Mails to lead or contact based on a Campaign schedule\",\n \"label\": \"Email Campaign\",\n \"name\": \"Email Campaign\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and Schedule social media posts\",\n \"label\": \"Social Media Post\",\n \"name\": \"Social Media Post\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Settings",
"links": "[\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Send mass SMS to your contacts\",\n \"label\": \"SMS Center\",\n \"name\": \"SMS Center\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Logs for maintaining sms delivery status\",\n \"label\": \"SMS Log\",\n \"name\": \"SMS Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup SMS gateway settings\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Twitter Settings\",\n \"name\": \"Twitter Settings\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"LinkedIn Settings\",\n \"name\": \"LinkedIn Settings\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Modules", "category": "Modules",
@ -33,7 +38,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "CRM", "label": "CRM",
"modified": "2020-04-01 11:28:51.219999", "modified": "2020-04-27 22:32:26.682911",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "CRM", "name": "CRM",
@ -42,7 +47,7 @@
"pin_to_top": 0, "pin_to_top": 0,
"shortcuts": [ "shortcuts": [
{ {
"format": "Open", "format": "{} Open",
"label": "Lead", "label": "Lead",
"link_to": "Lead", "link_to": "Lead",
"stats_filter": "{\"status\":\"Open\"}", "stats_filter": "{\"status\":\"Open\"}",

View File

@ -16,16 +16,19 @@ frappe.ui.form.on('Twitter Settings', {
} }
}, },
refresh: function(frm){ refresh: function(frm){
let msg,color; let msg, color, flag=false;
if (frm.doc.session_status == "Active"){ if (frm.doc.session_status == "Active"){
msg = __("Session Active"); msg = __("Session Active");
color = 'green'; color = 'green';
flag = true;
} }
else { else if(frm.doc.consumer_key && frm.doc.consumer_secret) {
msg = __("Session Not Active. Save doc to login."); msg = __("Session Not Active. Save doc to login.");
color = 'red'; color = 'red';
flag = true;
} }
if (flag){
frm.dashboard.set_headline_alert( frm.dashboard.set_headline_alert(
`<div class="row"> `<div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
@ -33,6 +36,7 @@ frappe.ui.form.on('Twitter Settings', {
</div> </div>
</div>` </div>`
); );
}
}, },
login: function(frm){ login: function(frm){
if (frm.doc.consumer_key && frm.doc.consumer_secret){ if (frm.doc.consumer_key && frm.doc.consumer_secret){

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

@ -6,6 +6,9 @@ def get_data():
'heatmap': True, 'heatmap': True,
'heatmap_message': _('This is based on the attendance of this Student'), 'heatmap_message': _('This is based on the attendance of this Student'),
'fieldname': 'student', 'fieldname': 'student',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [ 'transactions': [
{ {
'label': _('Admission'), 'label': _('Admission'),
@ -29,7 +32,7 @@ def get_data():
}, },
{ {
'label': _('Fee'), 'label': _('Fee'),
'items': ['Fees'] 'items': ['Fees', 'Bank Account']
} }
] ]
} }

View File

@ -43,7 +43,7 @@ def validate_customer_created(patient):
def get_fee_validity(patient_appointments): def get_fee_validity(patient_appointments):
if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
return return []
items_to_invoice = [] items_to_invoice = []
for appointment in patient_appointments: for appointment in patient_appointments:
@ -110,7 +110,7 @@ def get_lab_tests_to_invoice(patient):
filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1}
) )
for lab_test in lab_tests: for lab_test in lab_tests:
item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable']) item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable'])
if is_billable: if is_billable:
lab_tests_to_invoice.append({ lab_tests_to_invoice.append({
'reference_type': 'Lab Test', 'reference_type': 'Lab Test',

View File

@ -87,11 +87,12 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval:doc.status==\"On Leave\"", "depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"fieldname": "leave_type", "fieldname": "leave_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Leave Type", "label": "Leave Type",
"mandatory_depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"oldfieldname": "leave_type", "oldfieldname": "leave_type",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Leave Type" "options": "Leave Type"
@ -100,6 +101,7 @@
"fieldname": "leave_application", "fieldname": "leave_application",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Leave Application", "label": "Leave Application",
"no_copy": 1,
"options": "Leave Application", "options": "Leave Application",
"read_only": 1 "read_only": 1
}, },
@ -175,7 +177,8 @@
"icon": "fa fa-ok", "icon": "fa fa-ok",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2020-02-19 14:25:32.945842", "links": [],
"modified": "2020-04-11 11:40:14.319496",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Attendance", "name": "Attendance",

View File

@ -7,33 +7,15 @@ import frappe
from frappe.utils import getdate, nowdate from frappe.utils import getdate, nowdate
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, get_datetime_str from frappe.utils import cstr, get_datetime, formatdate
from frappe.utils import update_progress_bar
class Attendance(Document): class Attendance(Document):
def validate_duplicate_record(self): def validate(self):
res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s from erpnext.controllers.status_updater import validate_status
and name != %s and docstatus != 2""", validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
(self.employee, getdate(self.attendance_date), self.name)) self.validate_attendance_date()
if res: self.validate_duplicate_record()
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) self.check_leave_record()
def check_leave_record(self):
leave_record = frappe.db.sql("""select leave_type, half_day, half_day_date from `tabLeave Application`
where employee = %s and %s between from_date and to_date and status = 'Approved'
and docstatus = 1""", (self.employee, self.attendance_date), as_dict=True)
if leave_record:
for d in leave_record:
if d.half_day_date == getdate(self.attendance_date):
self.status = 'Half Day'
frappe.msgprint(_("Employee {0} on Half day on {1}").format(self.employee, self.attendance_date))
else:
self.status = 'On Leave'
self.leave_type = d.leave_type
frappe.msgprint(_("Employee {0} is on Leave on {1}").format(self.employee, self.attendance_date))
if self.status == "On Leave" and not leave_record:
frappe.throw(_("No leave record found for employee {0} for {1}").format(self.employee, self.attendance_date))
def validate_attendance_date(self): def validate_attendance_date(self):
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
@ -44,19 +26,52 @@ class Attendance(Document):
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining): elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date")) frappe.throw(_("Attendance date can not be less than employee's joining date"))
def validate_duplicate_record(self):
res = frappe.db.sql("""
select name from `tabAttendance`
where employee = %s
and attendance_date = %s
and name != %s
and docstatus != 2
""", (self.employee, getdate(self.attendance_date), self.name))
if res:
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee))
def check_leave_record(self):
leave_record = frappe.db.sql("""
select leave_type, half_day, half_day_date
from `tabLeave Application`
where employee = %s
and %s between from_date and to_date
and status = 'Approved'
and docstatus = 1
""", (self.employee, self.attendance_date), as_dict=True)
if leave_record:
for d in leave_record:
self.leave_type = d.leave_type
if d.half_day_date == getdate(self.attendance_date):
self.status = 'Half Day'
frappe.msgprint(_("Employee {0} on Half day on {1}")
.format(self.employee, formatdate(self.attendance_date)))
else:
self.status = 'On Leave'
frappe.msgprint(_("Employee {0} is on Leave on {1}")
.format(self.employee, formatdate(self.attendance_date)))
if self.status in ("On Leave", "Half Day"):
if not leave_record:
frappe.msgprint(_("No leave record found for employee {0} on {1}")
.format(self.employee, formatdate(self.attendance_date)), alert=1)
elif self.leave_type:
self.leave_type = None
self.leave_application = None
def validate_employee(self): def validate_employee(self):
emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'", emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
self.employee) self.employee)
if not emp: if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee)) frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
self.validate_attendance_date()
self.validate_duplicate_record()
self.check_leave_record()
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):
events = [] events = []
@ -90,18 +105,20 @@ def add_attendance(events, start, end, conditions=None):
if e not in events: if e not in events:
events.append(e) events.append(e)
def mark_attendance(employee, attendance_date, status, shift=None): def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
employee_doc = frappe.get_doc('Employee', employee)
if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}): if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
doc_dict = { company = frappe.db.get_value('Employee', employee, 'company')
attendance = frappe.get_doc({
'doctype': 'Attendance', 'doctype': 'Attendance',
'employee': employee, 'employee': employee,
'attendance_date': attendance_date, 'attendance_date': attendance_date,
'status': status, 'status': status,
'company': employee_doc.company, 'company': company,
'shift': shift 'shift': shift,
} 'leave_type': leave_type
attendance = frappe.get_doc(doc_dict).insert() })
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit() attendance.submit()
return attendance.name return attendance.name

View File

@ -6,6 +6,9 @@ def get_data():
'heatmap': True, 'heatmap': True,
'heatmap_message': _('This is based on the attendance of this Employee'), 'heatmap_message': _('This is based on the attendance of this Employee'),
'fieldname': 'employee', 'fieldname': 'employee',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [ 'transactions': [
{ {
'label': _('Leave and Attendance'), 'label': _('Leave and Attendance'),
@ -33,7 +36,7 @@ def get_data():
}, },
{ {
'label': _('Payroll'), 'label': _('Payroll'),
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus'] 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
}, },
{ {
'label': _('Training'), 'label': _('Training'),

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,534 +1,116 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "HR-TAX-DEC-.YYYY.-.#####", "autoname": "HR-TAX-DEC-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 16:53:36.175504", "creation": "2018-04-13 16:53:36.175504",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"department",
"column_break_2",
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee", "fieldname": "employee",
"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": "Employee", "label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee", "options": "Employee",
"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_from": "employee.employee_name", "fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name", "fieldname": "employee_name",
"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": "Employee Name", "label": "Employee Name",
"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,
"fetch_from": "employee.department", "fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "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", "label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department", "options": "Department",
"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_2", "fieldname": "column_break_2",
"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": 0,
"fetch_if_empty": 0,
"fieldname": "payroll_period", "fieldname": "payroll_period",
"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": "Payroll Period", "label": "Payroll Period",
"length": 0,
"no_copy": 0,
"options": "Payroll Period", "options": "Payroll Period",
"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_from": "employee.company", "fetch_from": "employee.company",
"fetch_if_empty": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "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", "label": "Company",
"length": 0, "options": "Company"
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "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", "label": "Amended From",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Employee Tax Exemption Declaration", "options": "Employee Tax Exemption Declaration",
"permlevel": 0,
"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": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8", "fieldname": "section_break_8",
"fieldtype": "Section Break", "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_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "declarations", "fieldname": "declarations",
"fieldtype": "Table", "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", "label": "Declarations",
"length": 0, "options": "Employee Tax Exemption Declaration Category"
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10", "fieldname": "section_break_10",
"fieldtype": "Section Break", "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_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_declared_amount", "fieldname": "total_declared_amount",
"fieldtype": "Currency", "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", "label": "Total Declared Amount",
"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,
"fetch_if_empty": 0,
"fieldname": "column_break_12", "fieldname": "column_break_12",
"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": 0,
"fetch_if_empty": 0,
"fieldname": "total_exemption_amount", "fieldname": "total_exemption_amount",
"fieldtype": "Currency", "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", "label": "Total Exemption Amount",
"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,
"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,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "links": [],
"istable": 0, "modified": "2020-03-18 14:56:25.625717",
"max_attachments": 0,
"modified": "2019-05-11 16:13:50.472670",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Declaration", "name": "Employee Tax Exemption Declaration",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -538,14 +120,10 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
@ -557,14 +135,10 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
@ -576,14 +150,10 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
@ -595,26 +165,16 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Employee", "role": "Employee",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"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

@ -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

@ -21,8 +21,6 @@
"total_actual_amount", "total_actual_amount",
"column_break_12", "column_break_12",
"exemption_amount", "exemption_amount",
"other_incomes_section",
"income_from_other_sources",
"attachment_section", "attachment_section",
"attachments", "attachments",
"amended_from" "amended_from"
@ -111,16 +109,6 @@
"label": "Total Exemption Amount", "label": "Total Exemption Amount",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"label": "Other Incomes"
},
{
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"label": "Income From Other Sources"
},
{ {
"fieldname": "attachment_section", "fieldname": "attachment_section",
"fieldtype": "Section Break" "fieldtype": "Section Break"
@ -142,7 +130,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-02 19:02:15.398486", "modified": "2020-03-18 14:55:51.420016",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",

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"))

View File

@ -17,7 +17,7 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
return; return;
} }
return frappe.call({ return frappe.call({
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account", method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
args: { args: {
"expense_claim_type": d.expense_type, "expense_claim_type": d.expense_type,
"company": doc.company "company": doc.company
@ -25,6 +25,7 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
d.default_account = r.message.account; d.default_account = r.message.account;
d.cost_center = r.message.cost_center;
} }
} }
}); });

View File

@ -2,9 +2,9 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import get_fullname, flt, cstr from frappe.utils import get_fullname, flt, cstr, get_link_to_form
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name from erpnext.hr.utils import set_employee_name
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
@ -192,7 +192,8 @@ class ExpenseClaim(AccountsController):
def validate_account_details(self): def validate_account_details(self):
for data in self.expenses: for data in self.expenses:
if not data.cost_center: if not data.cost_center:
frappe.throw(_("Cost center is required to book an expense claim")) frappe.throw(_("Row {0}: {1} is required in the expenses table to book an expense claim.")
.format(data.idx, frappe.bold("Cost Center")))
if self.is_paid: if self.is_paid:
if not self.mode_of_payment: if not self.mode_of_payment:
@ -308,13 +309,23 @@ def make_bank_entry(dt, dn):
return je.as_dict() return je.as_dict()
@frappe.whitelist()
def get_expense_claim_account_and_cost_center(expense_claim_type, company):
data = get_expense_claim_account(expense_claim_type, company)
cost_center = erpnext.get_default_cost_center(company)
return {
"account": data.get("account"),
"cost_center": cost_center
}
@frappe.whitelist() @frappe.whitelist()
def get_expense_claim_account(expense_claim_type, company): def get_expense_claim_account(expense_claim_type, company):
account = frappe.db.get_value("Expense Claim Account", account = frappe.db.get_value("Expense Claim Account",
{"parent": expense_claim_type, "company": company}, "default_account") {"parent": expense_claim_type, "company": company}, "default_account")
if not account: if not account:
frappe.throw(_("Please set default account in Expense Claim Type {0}") frappe.throw(_("Set the default account for the {0} {1}")
.format(expense_claim_type)) .format(frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)))
return { return {
"account": account "account": account

View File

@ -13,10 +13,12 @@
"stop_birthday_reminders", "stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim", "expense_approver_mandatory_in_expense_claim",
"payroll_settings", "payroll_settings",
"payroll_based_on",
"max_working_hours_against_timesheet",
"include_holidays_in_total_working_days", "include_holidays_in_total_working_days",
"disable_rounded_total", "disable_rounded_total",
"max_working_hours_against_timesheet",
"column_break_11", "column_break_11",
"daily_wages_fraction_for_half_day",
"email_salary_slip_to_employee", "email_salary_slip_to_employee",
"encrypt_salary_slips_in_emails", "encrypt_salary_slips_in_emails",
"password_policy", "password_policy",
@ -184,13 +186,27 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application", "label": "Role Allowed to Create Backdated Leave Application",
"options": "Role" "options": "Role"
},
{
"default": "Leave",
"fieldname": "payroll_based_on",
"fieldtype": "Select",
"label": "Calculate Working Days in Payroll based on",
"options": "Leave\nAttendance"
},
{
"default": "0.5",
"description": "The fraction of daily wages to be paid for half-day attendance",
"fieldname": "daily_wages_fraction_for_half_day",
"fieldtype": "Float",
"label": "Daily Wages Fraction for Half Day"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-01-06 18:46:30.189815", "modified": "2020-04-13 21:20:59.382394",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

View File

@ -15,6 +15,9 @@ class HRSettings(Document):
self.set_naming_series() self.set_naming_series()
self.validate_password_policy() self.validate_password_policy()
if not self.daily_wages_fraction_for_half_day:
self.daily_wages_fraction_for_half_day = 0.5
def set_naming_series(self): def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number", set_by_naming_series("Employee", "employee_number",

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,13 +30,13 @@ 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))

View File

@ -374,7 +374,8 @@ class LeaveApplication(Document):
leaves=self.total_leave_days * -1, leaves=self.total_leave_days * -1,
from_date=self.from_date, from_date=self.from_date,
to_date=self.to_date, to_date=self.to_date,
is_lwp=lwp is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee)
) )
create_leave_ledger_entry(self, args, submit) create_leave_ledger_entry(self, args, submit)
@ -384,7 +385,9 @@ class LeaveApplication(Document):
from_date=self.from_date, from_date=self.from_date,
to_date=expiry_date, to_date=expiry_date,
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1, leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
is_lwp=lwp is_lwp=lwp,
holiday_list=get_holiday_list_for_employee(self.employee),
) )
create_leave_ledger_entry(self, args, submit) create_leave_ledger_entry(self, args, submit)
@ -410,7 +413,7 @@ def get_allocation_expiry(employee, leave_type, to_date, from_date):
return expiry[0]['to_date'] if expiry else None return expiry[0]['to_date'] if expiry else None
@frappe.whitelist() @frappe.whitelist()
def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None, holiday_list = None):
number_of_days = 0 number_of_days = 0
if cint(half_day) == 1: if cint(half_day) == 1:
if from_date == to_date: if from_date == to_date:
@ -424,7 +427,7 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
number_of_days = date_diff(to_date, from_date) + 1 number_of_days = date_diff(to_date, from_date) + 1
if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"): if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date)) number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date, holiday_list=holiday_list))
return number_of_days return number_of_days
@frappe.whitelist() @frappe.whitelist()
@ -575,7 +578,7 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
{'name': leave_entry.transaction_name}, ['half_day_date']) {'name': leave_entry.transaction_name}, ['half_day_date'])
leave_days += get_number_of_leave_days(employee, leave_type, leave_days += get_number_of_leave_days(employee, leave_type,
leave_entry.from_date, leave_entry.to_date, half_day, half_day_date) * -1 leave_entry.from_date, leave_entry.to_date, half_day, half_day_date, holiday_list=leave_entry.holiday_list) * -1
return leave_days return leave_days
@ -589,7 +592,7 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date. ''' ''' Returns leave entries between from_date and to_date. '''
return frappe.db.sql(""" return frappe.db.sql("""
SELECT SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, holiday_list,
is_carry_forward, is_expired is_carry_forward, is_expired
FROM `tabLeave Ledger Entry` FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s WHERE employee=%(employee)s AND leave_type=%(leave_type)s
@ -607,8 +610,9 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
}, as_dict=1) }, as_dict=1)
@frappe.whitelist() @frappe.whitelist()
def get_holidays(employee, from_date, to_date): def get_holidays(employee, from_date, to_date, holiday_list = None):
'''get holidays between two dates for the given employee''' '''get holidays between two dates for the given employee'''
if not holiday_list:
holiday_list = get_holiday_list_for_employee(employee) holiday_list = get_holiday_list_for_employee(employee)
holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2 holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-05-09 15:47:39.760406", "creation": "2019-05-09 15:47:39.760406",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
@ -12,6 +13,7 @@
"column_break_7", "column_break_7",
"from_date", "from_date",
"to_date", "to_date",
"holiday_list",
"is_carry_forward", "is_carry_forward",
"is_expired", "is_expired",
"is_lwp", "is_lwp",
@ -98,11 +100,18 @@
"fieldname": "is_lwp", "fieldname": "is_lwp",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Leave Without Pay" "label": "Is Leave Without Pay"
},
{
"fieldname": "holiday_list",
"fieldtype": "Link",
"label": "Holiday List",
"options": "Holiday List"
} }
], ],
"in_create": 1, "in_create": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-08-20 14:40:04.130799", "links": [],
"modified": "2020-02-27 14:40:10.502605",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Ledger Entry", "name": "Leave Ledger Entry",

View File

@ -1,401 +1,102 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"autoname": "Prompt", "autoname": "Prompt",
"beta": 0,
"creation": "2018-04-13 15:18:53.698553", "creation": "2018-04-13 15:18:53.698553",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"company",
"column_break_2",
"start_date",
"end_date",
"section_break_5",
"periods"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company", "fieldname": "company",
"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": "Company", "label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company", "options": "Company",
"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": "column_break_2", "fieldname": "column_break_2",
"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": 0,
"fetch_if_empty": 0,
"fieldname": "start_date", "fieldname": "start_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": "Start Date", "label": "Start Date",
"length": 0, "reqd": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "end_date", "fieldname": "end_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": "End Date", "label": "End Date",
"length": 0, "reqd": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5", "fieldname": "section_break_5",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0, "label": "Payroll Periods"
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "periods", "fieldname": "periods",
"fieldtype": "Table", "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", "label": "Payroll Periods",
"length": 0, "options": "Payroll Period Date"
"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,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-26 01:45:03.160929",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Payroll Period", "name": "Payroll Period",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"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

@ -45,7 +45,8 @@ 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):
if not company:
company = frappe.db.get_value("Employee", employee, "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

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:salary_component", "autoname": "field:salary_component",
@ -13,10 +14,11 @@
"type", "type",
"description", "description",
"column_break_4", "column_break_4",
"is_payable",
"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",
"variable_based_on_taxable_salary",
"exempted_from_income_tax",
"round_to_the_nearest_integer", "round_to_the_nearest_integer",
"statistical_component", "statistical_component",
"do_not_include_in_total", "do_not_include_in_total",
@ -28,8 +30,6 @@
"pay_against_benefit_claim", "pay_against_benefit_claim",
"only_tax_impact", "only_tax_impact",
"create_separate_payment_entry_against_benefit_claim", "create_separate_payment_entry_against_benefit_claim",
"section_break_11",
"variable_based_on_taxable_salary",
"section_break_5", "section_break_5",
"accounts", "accounts",
"condition_and_formula", "condition_and_formula",
@ -73,12 +73,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Tax Applicable" "label": "Is Tax Applicable"
}, },
{
"default": "1",
"fieldname": "is_payable",
"fieldtype": "Check",
"label": "Is Payable"
},
{ {
"default": "1", "default": "1",
"fieldname": "depends_on_payment_days", "fieldname": "depends_on_payment_days",
@ -94,7 +88,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "is_tax_applicable", "depends_on": "eval:doc.is_tax_applicable && doc.type=='Earning'",
"fieldname": "deduct_full_tax_on_selected_payroll_date", "fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Deduct Full Tax on Selected Payroll Date" "label": "Deduct Full Tax on Selected Payroll Date"
@ -165,13 +159,9 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Create Separate Payment Entry Against Benefit Claim" "label": "Create Separate Payment Entry Against Benefit Claim"
}, },
{
"depends_on": "eval:doc.type=='Deduction'",
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.type == \"Deduction\"",
"fieldname": "variable_based_on_taxable_salary", "fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Variable Based On Taxable Salary" "label": "Variable Based On Taxable Salary"
@ -233,10 +223,19 @@
"fieldname": "round_to_the_nearest_integer", "fieldname": "round_to_the_nearest_integer",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Round to the Nearest Integer" "label": "Round to the Nearest Integer"
},
{
"default": "0",
"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",
"fieldtype": "Check",
"label": "Exempted from Income Tax"
} }
], ],
"icon": "fa fa-flag", "icon": "fa fa-flag",
"modified": "2019-06-05 11:34:14.231228", "links": [],
"modified": "2020-04-24 14:50:28.994054",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Component", "name": "Salary Component",

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

@ -12,6 +12,7 @@
"deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date",
"depends_on_payment_days", "depends_on_payment_days",
"is_tax_applicable", "is_tax_applicable",
"exempted_from_income_tax",
"is_flexible_benefit", "is_flexible_benefit",
"variable_based_on_taxable_salary", "variable_based_on_taxable_salary",
"section_break_2", "section_break_2",
@ -62,6 +63,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_tax_applicable", "fetch_from": "salary_component.is_tax_applicable",
"fieldname": "is_tax_applicable", "fieldname": "is_tax_applicable",
"fieldtype": "Check", "fieldtype": "Check",
@ -71,6 +73,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_flexible_benefit", "fetch_from": "salary_component.is_flexible_benefit",
"fieldname": "is_flexible_benefit", "fieldname": "is_flexible_benefit",
"fieldtype": "Check", "fieldtype": "Check",
@ -80,6 +83,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.variable_based_on_taxable_salary", "fetch_from": "salary_component.variable_based_on_taxable_salary",
"fieldname": "variable_based_on_taxable_salary", "fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check", "fieldtype": "Check",
@ -187,11 +191,20 @@
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Condition and Formula Help", "label": "Condition and Formula Help",
"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>" "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>"
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.exempted_from_income_tax",
"fieldname": "exempted_from_income_tax",
"fieldtype": "Check",
"label": "Exempted from Income Tax",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-31 17:15:25.646689", "modified": "2020-04-24 20:00:16.475295",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Detail", "name": "Salary Detail",

View File

@ -51,7 +51,7 @@ frappe.ui.form.on("Salary Slip", {
}, },
end_date: function(frm) { end_date: function(frm) {
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
set_end_date: function(frm){ set_end_date: function(frm){
@ -86,7 +86,7 @@ frappe.ui.form.on("Salary Slip", {
salary_slip_based_on_timesheet: function(frm) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
payroll_frequency: function(frm) { payroll_frequency: function(frm) {
@ -95,15 +95,14 @@ frappe.ui.form.on("Salary Slip", {
}, },
employee: function(frm) { employee: function(frm) {
frm.events.get_emp_and_leave_details(frm); frm.events.get_emp_and_working_day_details(frm);
}, },
leave_without_pay: function(frm){ leave_without_pay: function(frm){
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) { if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({ return frappe.call({
method: 'process_salary_based_on_leave', method: 'process_salary_based_on_working_days',
doc: frm.doc, doc: frm.doc,
args: {"lwp": frm.doc.leave_without_pay},
callback: function(r, rt) { callback: function(r, rt) {
frm.refresh(); frm.refresh();
} }
@ -118,9 +117,9 @@ frappe.ui.form.on("Salary Slip", {
frm.doc.payroll_frequency != ""); frm.doc.payroll_frequency != "");
}, },
get_emp_and_leave_details: function(frm) { get_emp_and_working_day_details: function(frm) {
return frappe.call({ return frappe.call({
method: 'get_emp_and_leave_details', method: 'get_emp_and_working_day_details',
doc: frm.doc, doc: frm.doc,
callback: function(r, rt) { callback: function(r, rt) {
frm.refresh(); frm.refresh();

View File

@ -11,20 +11,20 @@
"employee_name", "employee_name",
"department", "department",
"designation", "designation",
"branch",
"column_break1", "column_break1",
"company", "status",
"journal_entry", "journal_entry",
"payroll_entry", "payroll_entry",
"company",
"letter_head", "letter_head",
"branch",
"status",
"section_break_10", "section_break_10",
"salary_slip_based_on_timesheet", "salary_slip_based_on_timesheet",
"payroll_frequency",
"start_date", "start_date",
"end_date", "end_date",
"column_break_15", "column_break_15",
"salary_structure", "salary_structure",
"payroll_frequency",
"total_working_days", "total_working_days",
"leave_without_pay", "leave_without_pay",
"payment_days", "payment_days",
@ -309,6 +309,7 @@
{ {
"fieldname": "earning", "fieldname": "earning",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "Earning",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"width": "50%" "width": "50%"
}, },
@ -323,6 +324,7 @@
{ {
"fieldname": "deduction", "fieldname": "deduction",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "Deduction",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"width": "50%" "width": "50%"
}, },
@ -463,7 +465,7 @@
"idx": 9, "idx": 9,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-09 20:02:53.159827", "modified": "2020-04-14 20:02:53.159827",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import datetime, math import datetime, math
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from frappe import msgprint, _ from frappe import msgprint, _
@ -44,9 +44,9 @@ class SalarySlip(TransactionBase):
if not (len(self.get("earnings")) or len(self.get("deductions"))): if not (len(self.get("earnings")) or len(self.get("deductions"))):
# get details from salary structure # get details from salary structure
self.get_emp_and_leave_details() self.get_emp_and_working_day_details()
else: else:
self.get_leave_details(lwp = self.leave_without_pay) self.get_working_days_details(lwp = self.leave_without_pay)
self.calculate_net_pay() self.calculate_net_pay()
@ -117,7 +117,7 @@ class SalarySlip(TransactionBase):
self.start_date = date_details.start_date self.start_date = date_details.start_date
self.end_date = date_details.end_date self.end_date = date_details.end_date
def get_emp_and_leave_details(self): def get_emp_and_working_day_details(self):
'''First time, load all the components from salary structure''' '''First time, load all the components from salary structure'''
if self.employee: if self.employee:
self.set("earnings", []) self.set("earnings", [])
@ -129,7 +129,8 @@ class SalarySlip(TransactionBase):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"]) ["date_of_joining", "relieving_date"])
self.get_leave_details(joining_date, relieving_date) #getin leave details
self.get_working_days_details(joining_date, relieving_date)
struct = self.check_sal_struct(joining_date, relieving_date) struct = self.check_sal_struct(joining_date, relieving_date)
if struct: if struct:
@ -188,10 +189,9 @@ class SalarySlip(TransactionBase):
make_salary_slip(self._salary_structure_doc.name, self) make_salary_slip(self._salary_structure_doc.name, self)
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None, for_preview=0): def get_working_days_details(self, joining_date=None, relieving_date=None, lwp=None, for_preview=0):
if not joining_date: payroll_based_on = frappe.db.get_value("HR Settings", None, "payroll_based_on")
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, include_holidays_in_total_working_days = frappe.db.get_single_value("HR Settings", "include_holidays_in_total_working_days")
["date_of_joining", "relieving_date"])
working_days = date_diff(self.end_date, self.start_date) + 1 working_days = date_diff(self.end_date, self.start_date) + 1
if for_preview: if for_preview:
@ -200,24 +200,42 @@ class SalarySlip(TransactionBase):
return return
holidays = self.get_holidays_for_employee(self.start_date, self.end_date) holidays = self.get_holidays_for_employee(self.start_date, self.end_date)
actual_lwp = self.calculate_lwp(holidays, working_days)
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")): if not cint(include_holidays_in_total_working_days):
working_days -= len(holidays) working_days -= len(holidays)
if working_days < 0: if working_days < 0:
frappe.throw(_("There are more holidays than working days this month.")) frappe.throw(_("There are more holidays than working days this month."))
if not payroll_based_on:
frappe.throw(_("Please set Payroll based on in HR settings"))
if payroll_based_on == "Attendance":
actual_lwp = self.calculate_lwp_based_on_attendance(holidays)
else:
actual_lwp = self.calculate_lwp_based_on_leave_application(holidays, working_days)
if not lwp: if not lwp:
lwp = actual_lwp lwp = actual_lwp
elif lwp != actual_lwp: elif lwp != actual_lwp:
frappe.msgprint(_("Leave Without Pay does not match with approved Leave Application records")) frappe.msgprint(_("Leave Without Pay does not match with approved {} records")
.format(payroll_based_on))
self.total_working_days = working_days
self.leave_without_pay = lwp self.leave_without_pay = lwp
self.total_working_days = working_days
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp) payment_days = self.get_payment_days(joining_date,
self.payment_days = payment_days > 0 and payment_days or 0 relieving_date, include_holidays_in_total_working_days)
if flt(payment_days) > flt(lwp):
self.payment_days = flt(payment_days) - flt(lwp)
else:
self.payment_days = 0
def get_payment_days(self, joining_date, relieving_date, include_holidays_in_total_working_days):
if not joining_date:
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
def get_payment_days(self, joining_date, relieving_date):
start_date = getdate(self.start_date) start_date = getdate(self.start_date)
if joining_date: if joining_date:
if getdate(self.start_date) <= joining_date <= getdate(self.end_date): if getdate(self.start_date) <= joining_date <= getdate(self.end_date):
@ -235,9 +253,10 @@ class SalarySlip(TransactionBase):
payment_days = date_diff(end_date, start_date) + 1 payment_days = date_diff(end_date, start_date) + 1
if not cint(frappe.db.get_value("HR Settings", None, "include_holidays_in_total_working_days")): if not cint(include_holidays_in_total_working_days):
holidays = self.get_holidays_for_employee(start_date, end_date) holidays = self.get_holidays_for_employee(start_date, end_date)
payment_days -= len(holidays) payment_days -= len(holidays)
return payment_days return payment_days
def get_holidays_for_employee(self, start_date, end_date): def get_holidays_for_employee(self, start_date, end_date):
@ -256,27 +275,67 @@ class SalarySlip(TransactionBase):
return holidays return holidays
def calculate_lwp(self, holidays, working_days): def calculate_lwp_based_on_leave_application(self, holidays, working_days):
lwp = 0 lwp = 0
holidays = "','".join(holidays) holidays = "','".join(holidays)
daily_wages_fraction_for_half_day = \
flt(frappe.db.get_value("HR Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
for d in range(working_days): for d in range(working_days):
dt = add_days(cstr(getdate(self.start_date)), d) dt = add_days(cstr(getdate(self.start_date)), d)
leave = frappe.db.sql(""" leave = frappe.db.sql("""
SELECT t1.name, SELECT t1.name,
CASE WHEN t1.half_day_date = %(dt)s or t1.to_date = t1.from_date CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
THEN t1.half_day else 0 END THEN t1.half_day else 0 END
FROM `tabLeave Application` t1, `tabLeave Type` t2 FROM `tabLeave Application` t1, `tabLeave Type` t2
WHERE t2.name = t1.leave_type WHERE t2.name = t1.leave_type
AND t2.is_lwp = 1 AND t2.is_lwp = 1
AND t1.docstatus = 1 AND t1.docstatus = 1
AND t1.employee = %(employee)s AND t1.employee = %(employee)s
AND CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date and ifnull(t1.salary_slip, '') = '' AND ifnull(t1.salary_slip, '') = ''
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date and ifnull(t1.salary_slip, '') = '' AND CASE
WHEN t2.include_holiday != 1
THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
WHEN t2.include_holiday
THEN %(dt)s between from_date and to_date
END END
""".format(holidays), {"employee": self.employee, "dt": dt}) """.format(holidays), {"employee": self.employee, "dt": dt})
if leave: if leave:
lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) is_half_day_leave = cint(leave[0][1])
lwp += (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
return lwp
def calculate_lwp_based_on_attendance(self, holidays):
lwp = 0
daily_wages_fraction_for_half_day = \
flt(frappe.db.get_value("HR Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
lwp_leave_types = dict(frappe.get_all("Leave Type", {"is_lwp": 1}, ["name", "include_holiday"], as_list=1))
attendances = frappe.db.sql('''
SELECT attendance_date, status, leave_type
FROM `tabAttendance`
WHERE
status in ("Absent", "Half Day", "On leave")
AND employee = %s
AND docstatus = 1
AND attendance_date between %s and %s
''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
for d in attendances:
if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in lwp_leave_types:
continue
if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
if d.status == "Absent" or \
(d.leave_type and d.leave_type in lwp_leave_types and not lwp_leave_types[d.leave_type]):
continue
lwp += (1 - daily_wages_fraction_for_half_day) if d.status == "Half Day" else 1
return lwp return lwp
def add_earning_for_hourly_wages(self, doc, salary_component, amount): def add_earning_for_hourly_wages(self, doc, salary_component, amount):
@ -451,7 +510,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"):
@ -482,10 +542,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)
@ -507,7 +569,10 @@ 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 \
@ -517,13 +582,14 @@ class SalarySlip(TransactionBase):
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
@ -532,6 +598,24 @@ 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)
@ -550,7 +634,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
@ -610,6 +715,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,
@ -672,40 +784,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 tax_slab.allow_tax_exemption:
if self.deduct_tax_for_unsubmitted_tax_exemption_proof: if self.deduct_tax_for_unsubmitted_tax_exemption_proof:
exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission", exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, {"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["exemption_amount", "income_from_other_sources"]) ["exemption_amount"])
if exemption_proof: if exemption_proof:
total_exemption_amount, other_incomes = exemption_proof total_exemption_amount = exemption_proof
else: else:
declaration = frappe.db.get_value("Employee Tax Exemption Declaration", declaration = frappe.db.get_value("Employee Tax Exemption Declaration",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, {"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["total_exemption_amount", "income_from_other_sources"]) ["total_exemption_amount"])
if declaration: if declaration:
total_exemption_amount, other_incomes = 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:
@ -869,7 +1004,7 @@ class SalarySlip(TransactionBase):
if not self.salary_slip_based_on_timesheet: if not self.salary_slip_based_on_timesheet:
self.get_date_details() self.get_date_details()
self.pull_emp_details() self.pull_emp_details()
self.get_leave_details(for_preview=for_preview) self.get_working_days_details(for_preview=for_preview)
self.calculate_net_pay() self.calculate_net_pay()
def pull_emp_details(self): def pull_emp_details(self):
@ -878,8 +1013,8 @@ class SalarySlip(TransactionBase):
self.bank_name = emp.bank_name self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no self.bank_account_no = emp.bank_ac_no
def process_salary_based_on_leave(self, lwp=0): def process_salary_based_on_working_days(self):
self.get_leave_details(lwp=lwp) self.get_working_days_details(lwp=self.leave_without_pay)
self.calculate_net_pay() self.calculate_net_pay()
def unlink_ref_doc_from_salary_slip(ref_no): def unlink_ref_doc_from_salary_slip(ref_no):

View File

@ -21,18 +21,105 @@ class TestSalarySlip(unittest.TestCase):
make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_deduction_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, company_list=["_Test Company"])
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]:
frappe.db.sql("delete from `tab%s`" % dt) frappe.db.sql("delete from `tab%s`" % dt)
self.make_holiday_list() self.make_holiday_list()
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0) frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 0)
frappe.db.set_value('HR Settings', None, 'leave_status_notification_template', None)
frappe.db.set_value('HR Settings', None, 'leave_approval_notification_template', None)
def tearDown(self): def tearDown(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_payment_days_based_on_attendance(self):
from erpnext.hr.doctype.attendance.attendance import mark_attendance
no_of_days = self.get_no_of_days()
# Payroll based on attendance
frappe.db.set_value("HR Settings", None, "payroll_based_on", "Attendance")
frappe.db.set_value("HR Settings", None, "daily_wages_fraction_for_half_day", 0.75)
emp_id = make_employee("test_for_attendance@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql("""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""", (month_start_date, month_end_date))[0][0]
mark_attendance(emp_id, first_sunday, 'Absent', ignore_validate=True) # invalid lwp
mark_attendance(emp_id, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # valid lwp
mark_attendance(emp_id, add_days(first_sunday, 2), 'Half Day', leave_type='Leave Without Pay', ignore_validate=True) # valid 0.75 lwp
mark_attendance(emp_id, add_days(first_sunday, 3), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # valid lwp
mark_attendance(emp_id, add_days(first_sunday, 4), 'On Leave', leave_type='Casual Leave', ignore_validate=True) # invalid lwp
mark_attendance(emp_id, add_days(first_sunday, 7), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # invalid lwp
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
self.assertEqual(ss.leave_without_pay, 2.25)
days_in_month = no_of_days[0]
no_of_holidays = no_of_days[1]
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 2.25)
#Gross pay calculation based on attendances
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
self.assertEqual(ss.gross_pay, gross_pay)
frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave")
def test_payment_days_based_on_leave_application(self):
no_of_days = self.get_no_of_days()
# Payroll based on attendance
frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave")
emp_id = make_employee("test_for_attendance@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql("""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""", (month_start_date, month_end_date))[0][0]
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
self.assertEqual(ss.leave_without_pay, 3)
days_in_month = no_of_days[0]
no_of_holidays = no_of_days[1]
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 3)
#Gross pay calculation based on attendances
gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
self.assertEqual(ss.gross_pay, gross_pay)
frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave")
def test_salary_slip_with_holidays_included(self): def test_salary_slip_with_holidays_included(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1) frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
@ -47,10 +134,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, 68000.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 +151,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, 68000.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 +161,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 +212,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")
@ -203,8 +282,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",
@ -230,8 +312,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:
@ -255,8 +336,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")
@ -270,7 +350,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
@ -285,7 +365,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
@ -322,11 +402,11 @@ class TestSalarySlip(unittest.TestCase):
return [no_of_days_in_month[1], no_of_holidays_in_month] return [no_of_days_in_month[1], no_of_holidays_in_month]
def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): 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})})
@ -456,17 +536,15 @@ def make_deduction_salary_component(setup=False, test_tax=False, company_list=No
{ {
"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
@ -477,9 +555,7 @@ def make_deduction_salary_component(setup=False, test_tax=False, company_list=No
"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:
@ -535,29 +611,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 = []
@ -595,3 +689,17 @@ def create_additional_salary(employee, payroll_period, amount):
"type": "Earning" "type": "Earning"
}).submit() }).submit()
return salary_date return salary_date
def make_leave_application(employee, from_date, to_date, leave_type, company=None):
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
employee = employee,
leave_type = leave_type,
from_date = from_date,
to_date = to_date,
company = company or erpnext.get_default_company() or "_Test Company",
docstatus = 1,
status = "Approved",
leave_approver = 'test@example.com'
))
leave_application.submit()

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
@ -138,7 +149,7 @@ def get_existing_assignments(employees, salary_structure, from_date):
return salary_structures_assignments return salary_structures_assignments
@frappe.whitelist() @frappe.whitelist()
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0): def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0, ignore_permissions=False):
def postprocess(source, target): def postprocess(source, target):
if employee: if employee:
employee_details = frappe.db.get_value("Employee", employee, employee_details = frappe.db.get_value("Employee", employee,
@ -158,7 +169,7 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
"name": "salary_structure" "name": "salary_structure"
} }
} }
}, target_doc, postprocess, ignore_child_tables=True) }, target_doc, postprocess, ignore_child_tables=True, ignore_permissions=ignore_permissions)
if cint(as_print): if cint(as_print):
doc.name = 'Preview for {0}'.format(employee) doc.name = 'Preview for {0}'.format(employee)

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)
@ -112,6 +111,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
test_tax=False, company=None): test_tax=False, company=None):
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",
@ -124,7 +124,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:
@ -139,13 +140,18 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do
def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None): def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None):
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 = company or erpnext.get_default_company() salary_structure_assignment.company = company or 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 16:35:34.415099", "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

@ -1,85 +1,186 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# License: GNU General Public License v3. See license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.utils import flt from frappe.utils import flt
from erpnext.hr.doctype.leave_application.leave_application \ from frappe import _
import get_leave_balance_on, get_leaves_for_period from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on, get_leave_allocation_records
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") if filters.to_date <= filters.from_date:
frappe.throw(_('From date can not be greater than than To date'))
columns = get_columns(leave_types) columns = get_columns()
data = get_data(filters, leave_types) data = get_data(filters)
return columns, data return columns, data
def get_columns(leave_types): def get_columns():
columns = [ columns = [{
_("Employee") + ":Link.Employee:150", 'label': _('Leave Type'),
_("Employee Name") + "::200", 'fieldtype': 'Link',
_("Department") +"::150" 'fieldname': 'leave_type',
] 'width': 200,
'options': 'Leave Type'
for leave_type in leave_types: }, {
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160") 'label': _('Employee'),
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160") 'fieldtype': 'Link',
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160") 'fieldname': 'employee',
'width': 100,
'options': 'Employee'
}, {
'label': _('Employee Name'),
'fieldtype': 'Data',
'fieldname': 'employee_name',
'width': 100,
}, {
'label': _('Opening Balance'),
'fieldtype': 'float',
'fieldname': 'opening_balance',
'width': 130,
}, {
'label': _('Leaves Allocated'),
'fieldtype': 'float',
'fieldname': 'leaves_allocated',
'width': 130,
}, {
'label': _('Leaves Taken'),
'fieldtype': 'float',
'fieldname': 'leaves_taken',
'width': 130,
}, {
'label': _('Leaves Expired'),
'fieldtype': 'float',
'fieldname': 'leaves_expired',
'width': 130,
}, {
'label': _('Closing Balance'),
'fieldtype': 'float',
'fieldname': 'closing_balance',
'width': 130,
}]
return columns return columns
def get_conditions(filters): def get_data(filters):
conditions = { leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
"status": "Active",
"company": filters.company,
}
if filters.get("department"):
conditions.update({"department": filters.get("department")})
if filters.get("employee"):
conditions.update({"employee": filters.get("employee")})
return conditions
def get_data(filters, leave_types):
user = frappe.session.user
conditions = get_conditions(filters) conditions = get_conditions(filters)
if filters.to_date <= filters.from_date: user = frappe.session.user
frappe.throw(_("From date can not be greater than than To date"))
active_employees = frappe.get_all("Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
department_approver_map = get_department_leave_approver_map(filters.get('department')) department_approver_map = get_department_leave_approver_map(filters.get('department'))
data = [] active_employees = frappe.get_list('Employee',
for employee in active_employees: filters=conditions,
leave_approvers = department_approver_map.get(employee.department_name, []) fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)): data = []
row = [employee.name, employee.employee_name, employee.department]
for leave_type in leave_types: for leave_type in leave_types:
# leaves taken if len(active_employees) > 1:
data.append({
'leave_type': leave_type
})
else:
row = frappe._dict({
'leave_type': leave_type
})
for employee in active_employees:
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]) \
or ("HR Manager" in frappe.get_roles(user)):
if len(active_employees) > 1:
row = frappe._dict()
row.employee = employee.name,
row.employee_name = employee.employee_name
leaves_taken = get_leaves_for_period(employee.name, leave_type, leaves_taken = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1 filters.from_date, filters.to_date) * -1
# opening balance new_allocation, expired_leaves = get_allocated_and_expired_leaves(filters.from_date, filters.to_date, employee.name, leave_type)
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date) opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
# closing balance row.leaves_allocated = new_allocation
closing = max(opening - leaves_taken, 0) row.leaves_expired = expired_leaves - leaves_taken if expired_leaves - leaves_taken > 0 else 0
row.opening_balance = opening
row += [opening, leaves_taken, closing] row.leaves_taken = leaves_taken
row.closing_balance = closing
row.indent = 1
data.append(row) data.append(row)
new_leaves_allocated = 0
return data return data
def get_conditions(filters):
conditions={
'status': 'Active',
}
if filters.get('employee'):
conditions['name'] = filters.get('employee')
if filters.get('employee'):
conditions['name'] = filters.get('employee')
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
def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
from frappe.utils import getdate
new_allocation = 0
expired_leaves = 0
records= frappe.db.sql("""
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name,
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
AND docstatus=1 AND leaves>0
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)
for record in records:
if record.to_date <= getdate(to_date):
expired_leaves += record.leaves
if record.from_date >= getdate(from_date):
new_allocation += record.leaves
return new_allocation, expired_leaves

View File

@ -5,18 +5,11 @@
frappe.query_reports['Employee Leave Balance Summary'] = { frappe.query_reports['Employee Leave Balance Summary'] = {
filters: [ filters: [
{ {
fieldname:'from_date', fieldname:'date',
label: __('From Date'), label: __('Date'),
fieldtype: 'Date', fieldtype: 'Date',
reqd: 1, reqd: 1,
default: frappe.defaults.get_default('year_start_date') default: frappe.datetime.now_date()
},
{
fieldname:'to_date',
label: __('To Date'),
fieldtype: 'Date',
reqd: 1,
default: frappe.defaults.get_default('year_end_date')
}, },
{ {
fieldname:'company', fieldname:'company',

View File

@ -1,130 +1,75 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# For license information, please see license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
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 frappe.utils import flt
from erpnext.hr.doctype.leave_application.leave_application \
import get_leave_balance_on, get_leaves_for_period
from erpnext.hr.report.employee_leave_balance.employee_leave_balance \
import get_department_leave_approver_map
def execute(filters=None): def execute(filters=None):
if filters.to_date <= filters.from_date: leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
frappe.throw(_('From date can not be greater than than To date'))
columns = get_columns() columns = get_columns(leave_types)
data = get_data(filters) data = get_data(filters, leave_types)
return columns, data return columns, data
def get_columns(): def get_columns(leave_types):
columns = [{ columns = [
'label': _('Leave Type'), _("Employee") + ":Link.Employee:150",
'fieldtype': 'Link', _("Employee Name") + "::200",
'fieldname': 'leave_type', _("Department") +"::150"
'width': 300, ]
'options': 'Leave Type'
}, { for leave_type in leave_types:
'label': _('Employee'), columns.append(_(leave_type) + ":Float:160")
'fieldtype': 'Link',
'fieldname': 'employee',
'width': 100,
'options': 'Employee'
}, {
'label': _('Employee Name'),
'fieldtype': 'Data',
'fieldname': 'employee_name',
'width': 100,
}, {
'label': _('Opening Balance'),
'fieldtype': 'float',
'fieldname': 'opening_balance',
'width': 160,
}, {
'label': _('Leaves Taken'),
'fieldtype': 'float',
'fieldname': 'leaves_taken',
'width': 160,
}, {
'label': _('Closing Balance'),
'fieldtype': 'float',
'fieldname': 'closing_balance',
'width': 160,
}]
return columns return columns
def get_data(filters):
leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
conditions = get_conditions(filters)
user = frappe.session.user
department_approver_map = get_department_leave_approver_map(filters.get('department'))
active_employees = frappe.get_list('Employee',
filters=conditions,
fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
data = []
for leave_type in leave_types:
data.append({
'leave_type': leave_type
})
for employee in active_employees:
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]) \
or ("HR Manager" in frappe.get_roles(user)):
row = frappe._dict({
'employee': employee.name,
'employee_name': employee.employee_name
})
leaves_taken = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1
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)
return data
def get_conditions(filters): def get_conditions(filters):
conditions = { conditions = {
'status': 'Active', "status": "Active",
"company": filters.company,
} }
if filters.get('employee'): if filters.get("department"):
conditions['name'] = filters.get('employee') conditions.update({"department": filters.get("department")})
if filters.get("employee"):
if filters.get('employee'): conditions.update({"employee": filters.get("employee")})
conditions['name'] = filters.get('employee')
return conditions return conditions
def get_department_leave_approver_map(department=None): def get_data(filters, leave_types):
conditions='' user = frappe.session.user
if department: conditions = get_conditions(filters)
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child active_employees = frappe.get_all("Employee",
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
# retrieve approvers list from current department and from its subsequent child departments department_approver_map = get_department_leave_approver_map(filters.get('department'))
approver_list = frappe.get_all('Department Approver', filters={
'parentfield': 'leave_approvers',
'parent': ('in', department_list)
}, fields=['parent', 'approver'], as_list=1)
approvers = {} data = []
for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, [])
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
for k, v in approver_list: if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
approvers.setdefault(k, []).append(v) row = [employee.name, employee.employee_name, employee.department]
return approvers for leave_type in leave_types:
# opening balance
opening = get_leave_balance_on(employee.name, leave_type, filters.date)
row += [opening]
data.append(row)
return data

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

@ -1,15 +1,17 @@
{ {
"actions": [],
"allow_rename": 1,
"autoname": "field:loan_security_name", "autoname": "field:loan_security_name",
"creation": "2019-09-02 15:07:08.885593", "creation": "2019-09-02 15:07:08.885593",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"loan_security_type",
"loan_security_code",
"loan_security_name", "loan_security_name",
"unit_of_measure", "unit_of_measure",
"loan_security_code",
"column_break_3", "column_break_3",
"loan_security_type",
"haircut", "haircut",
"disabled" "disabled"
], ],
@ -17,7 +19,9 @@
{ {
"fieldname": "loan_security_name", "fieldname": "loan_security_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"label": "Loan Security Name", "label": "Loan Security Name",
"reqd": 1,
"unique": 1 "unique": 1
}, },
{ {
@ -33,8 +37,10 @@
{ {
"fieldname": "loan_security_type", "fieldname": "loan_security_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Loan Security Type", "label": "Loan Security Type",
"options": "Loan Security Type" "options": "Loan Security Type",
"reqd": 1
}, },
{ {
"fieldname": "loan_security_code", "fieldname": "loan_security_code",
@ -52,11 +58,15 @@
"fetch_from": "loan_security_type.unit_of_measure", "fetch_from": "loan_security_type.unit_of_measure",
"fieldname": "unit_of_measure", "fieldname": "unit_of_measure",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Unit Of Measure", "label": "Unit Of Measure",
"options": "UOM" "options": "UOM",
"read_only": 1,
"reqd": 1
} }
], ],
"modified": "2019-11-16 11:36:37.901656", "links": [],
"modified": "2020-04-28 14:07:54.506896",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security", "name": "Loan Security",
@ -87,7 +97,6 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"search_fields": "loan_security_code", "search_fields": "loan_security_code",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@ -9,9 +9,9 @@
"loan_security_type", "loan_security_type",
"unit_of_measure", "unit_of_measure",
"haircut", "haircut",
"disabled",
"column_break_5", "column_break_5",
"loan_to_value_ratio" "loan_to_value_ratio",
"disabled"
], ],
"fields": [ "fields": [
{ {
@ -23,7 +23,9 @@
{ {
"fieldname": "loan_security_type", "fieldname": "loan_security_type",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"label": "Loan Security Type", "label": "Loan Security Type",
"reqd": 1,
"unique": 1 "unique": 1
}, },
{ {
@ -34,8 +36,10 @@
{ {
"fieldname": "unit_of_measure", "fieldname": "unit_of_measure",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Unit Of Measure", "label": "Unit Of Measure",
"options": "UOM" "options": "UOM",
"reqd": 1
}, },
{ {
"fieldname": "column_break_5", "fieldname": "column_break_5",
@ -48,7 +52,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-02-28 12:43:20.364447", "modified": "2020-04-28 14:06:49.046177",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Type", "name": "Loan Security Type",

View File

@ -6,10 +6,17 @@ def get_data():
'heatmap': True, 'heatmap': True,
'heatmap_message': _('Member Activity'), 'heatmap_message': _('Member Activity'),
'fieldname': 'member', 'fieldname': 'member',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [ 'transactions': [
{ {
'label': _('Membership Details'), 'label': _('Membership Details'),
'items': ['Membership'] 'items': ['Membership']
},
{
'label': _('Fee'),
'items': ['Bank Account']
} }
] ]
} }

View File

@ -661,6 +661,7 @@ erpnext.patches.v12_0.set_job_offer_applicant_email
erpnext.patches.v12_0.create_irs_1099_field_united_states erpnext.patches.v12_0.create_irs_1099_field_united_states
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
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.add_permission_in_lower_deduction
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.rename_account_type_doctype erpnext.patches.v12_0.rename_account_type_doctype
erpnext.patches.v12_0.recalculate_requested_qty_in_bin erpnext.patches.v12_0.recalculate_requested_qty_in_bin
@ -668,5 +669,9 @@ erpnext.patches.v12_0.update_healthcare_refactored_changes
erpnext.patches.v12_0.set_total_batch_quantity erpnext.patches.v12_0.set_total_batch_quantity
erpnext.patches.v12_0.rename_mws_settings_fields erpnext.patches.v12_0.rename_mws_settings_fields
erpnext.patches.v12_0.set_updated_purpose_in_pick_list erpnext.patches.v12_0.set_updated_purpose_in_pick_list
erpnext.patches.v12_0.set_default_payroll_based_on
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.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
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry

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,34 @@
import frappe
def execute():
# fixes status of quotations which have status 'Expired' despite having valid sales order created
# filter out submitted expired quotations which has sales order created
cond = "qo.docstatus = 1 and qo.status = 'Expired'"
invalid_so_against_quo = """
SELECT
so.name FROM `tabSales Order` so, `tabSales Order Item` so_item
WHERE
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name
and qo.valid_till < so.transaction_date""" # check if SO was created after quotation expired
frappe.db.sql(
"""UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and exists({invalid_so_against_quo})"""
.format(cond=cond, invalid_so_against_quo=invalid_so_against_quo)
)
valid_so_against_quo = """
SELECT
so.name FROM `tabSales Order` so, `tabSales Order Item` so_item
WHERE
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name
and qo.valid_till >= so.transaction_date""" # check if SO was created before quotation expired
frappe.db.sql(
"""UPDATE `tabQuotation` qo SET qo.status = 'Closed' WHERE {cond} and exists({valid_so_against_quo})"""
.format(cond=cond, valid_so_against_quo=valid_so_against_quo)
)

View File

@ -0,0 +1,6 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("hr", "doctype", "hr_settings")
frappe.db.set_value("HR Settings", None, "payroll_based_on", "Leave")

View File

@ -0,0 +1,7 @@
import frappe
def execute():
job = frappe.db.exists('Scheduled Job Type', 'patient_appointment.send_appointment_reminder')
if job:
method = 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder'
frappe.db.set_value('Scheduled Job Type', job, 'method', method)

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;
@ -288,7 +302,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.setup_sms(); this.setup_sms();
this.setup_quality_inspection(); this.setup_quality_inspection();
let scan_barcode_field = this.frm.get_field('scan_barcode'); let scan_barcode_field = this.frm.get_field('scan_barcode');
if (scan_barcode_field) { if (scan_barcode_field && scan_barcode_field.get_value()) {
scan_barcode_field.set_value(""); scan_barcode_field.set_value("");
scan_barcode_field.set_new_description(""); scan_barcode_field.set_new_description("");

View File

@ -46,17 +46,19 @@ doctypes_with_dimensions.forEach((doctype) => {
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]) {
let default_dimension = default_dimensions[frm.doc.company][dimension['document_type']];
if(default_dimension) {
if (frappe.meta.has_field(doctype, dimension['fieldname'])) { if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
frm.set_value(dimension['fieldname'], frm.set_value(dimension['fieldname'], default_dimension);
default_dimensions[frm.doc.company][dimension['document_type']]);
} }
$.each(frm.doc.items || frm.doc.accounts || [], function(i, row) { $.each(frm.doc.items || frm.doc.accounts || [], function(i, row) {
frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], frappe.model.set_value(row.doctype, row.name, dimension['fieldname'], default_dimension);
default_dimensions[frm.doc.company][dimension['document_type']])
}); });
} }
} }
}
}); });
} }
}); });
@ -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

@ -61,7 +61,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)
@ -531,12 +531,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

@ -288,7 +288,7 @@ def calculate_annual_eligible_hra_exemption(doc):
}) })
def get_component_amt_from_salary_slip(employee, salary_structure, basic_component, hra_component): def get_component_amt_from_salary_slip(employee, salary_structure, basic_component, hra_component):
salary_slip = make_salary_slip(salary_structure, employee=employee, for_preview=1) salary_slip = make_salary_slip(salary_structure, employee=employee, for_preview=1, ignore_permissions=True)
basic_amt, hra_amt = 0, 0 basic_amt, hra_amt = 0, 0
for earning in salary_slip.earnings: for earning in salary_slip.earnings:
if earning.salary_component == basic_component: if earning.salary_component == basic_component:
@ -372,7 +372,6 @@ def calculate_hra_exemption_for_period(doc):
return exemptions return exemptions
def get_ewb_data(dt, dn): def get_ewb_data(dt, dn):
dn = dn.split(',')
ewaybills = [] ewaybills = []
for doc_name in dn: for doc_name in dn:
@ -453,16 +452,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

@ -17,7 +17,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Retail", "label": "Retail",
"modified": "2020-04-01 11:28:50.966145", "modified": "2020-04-26 22:42:39.346750",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Retail", "name": "Retail",

View File

@ -11,7 +11,8 @@ def get_data():
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Payment Entry': 'party', 'Payment Entry': 'party',
'Quotation': 'party_name', 'Quotation': 'party_name',
'Opportunity': 'party_name' 'Opportunity': 'party_name',
'Bank Account': 'party'
}, },
'dynamic_links': { 'dynamic_links': {
'party_name': ['Customer', 'quotation_to'] 'party_name': ['Customer', 'quotation_to']
@ -27,7 +28,7 @@ def get_data():
}, },
{ {
'label': _('Payments'), 'label': _('Payments'),
'items': ['Payment Entry'] 'items': ['Payment Entry', 'Bank Account']
}, },
{ {
'label': _('Support'), 'label': _('Support'),

View File

@ -193,12 +193,23 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
return doclist return doclist
def set_expired_status(): def set_expired_status():
frappe.db.sql(""" # filter out submitted non expired quotations whose validity has been ended
UPDATE cond = "qo.docstatus = 1 and qo.status != 'Expired' and qo.valid_till < %s"
`tabQuotation` SET `status` = 'Expired' # check if those QUO have SO against it
so_against_quo = """
SELECT
so.name FROM `tabSales Order` so, `tabSales Order Item` so_item
WHERE WHERE
`status` not in ('Ordered', 'Expired', 'Lost', 'Cancelled') AND `valid_till` < %s so_item.docstatus = 1 and so.docstatus = 1
""", (nowdate())) and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name"""
# if not exists any SO, set status as Expired
frappe.db.sql(
"""UPDATE `tabQuotation` qo SET qo.status = 'Expired' WHERE {cond} and not exists({so_against_quo})"""
.format(cond=cond, so_against_quo=so_against_quo),
(nowdate())
)
@frappe.whitelist() @frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None): def make_sales_invoice(source_name, target_doc=None):

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

@ -6,16 +6,23 @@ frappe.ui.form.on('Delivery Note', {
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus == 1 && !frm.is_dirty() && !frm.doc.ewaybill) { if(frm.doc.docstatus == 1 && !frm.is_dirty() && !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',
data: r.message,
docname: frm.doc.name
};
open_url_post(frappe.request.url, args);
} }
}
});
}, __("Create")); }, __("Create"));
} }
} }

Some files were not shown because too many files have changed in this diff Show More