Merge branch 'develop' into quo-status-fix
This commit is contained in:
commit
4d754eadc8
@ -302,7 +302,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.set_value("contact_email", "");
|
frm.set_value("contact_email", "");
|
||||||
frm.set_value("contact_person", "");
|
frm.set_value("contact_person", "");
|
||||||
}
|
}
|
||||||
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party) {
|
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
|
||||||
if(!frm.doc.posting_date) {
|
if(!frm.doc.posting_date) {
|
||||||
frappe.msgprint(__("Please select Posting Date before selecting Party"))
|
frappe.msgprint(__("Please select Posting Date before selecting Party"))
|
||||||
frm.set_value("party", "");
|
frm.set_value("party", "");
|
||||||
|
|||||||
@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
|||||||
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
||||||
item_details.update({
|
item_details.update({
|
||||||
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
|
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
|
||||||
|
'price_or_product_discount': pricing_rule.price_or_product_discount,
|
||||||
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
|
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||||
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
|
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
me.frm.script_manager.trigger("is_pos");
|
me.frm.script_manager.trigger("is_pos");
|
||||||
me.frm.refresh_fields();
|
me.frm.refresh_fields();
|
||||||
}
|
}
|
||||||
|
erpnext.queries.setup_warehouse_query(this.frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc, dt, dn) {
|
refresh: function(doc, dt, dn) {
|
||||||
@ -586,7 +587,9 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
frm.set_query("account_for_change_amount", function() {
|
frm.set_query("account_for_change_amount", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
account_type: ['in', ["Cash", "Bank"]]
|
account_type: ['in', ["Cash", "Bank"]],
|
||||||
|
company: frm.doc.company,
|
||||||
|
is_group: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -667,7 +670,8 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
frm.fields_dict["loyalty_redemption_account"].get_query = function() {
|
frm.fields_dict["loyalty_redemption_account"].get_query = function() {
|
||||||
return {
|
return {
|
||||||
filters:{
|
filters:{
|
||||||
"company": frm.doc.company
|
"company": frm.doc.company,
|
||||||
|
"is_group": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -676,7 +680,8 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
|
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
|
||||||
return {
|
return {
|
||||||
filters:{
|
filters:{
|
||||||
"company": frm.doc.company
|
"company": frm.doc.company,
|
||||||
|
"is_group": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
||||||
@ -8,7 +8,6 @@ from frappe.utils import flt
|
|||||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||||
filters.periodicity, filters.accumulated_values, filters.company)
|
filters.periodicity, filters.accumulated_values, filters.company)
|
||||||
@ -27,17 +26,19 @@ def execute(filters=None):
|
|||||||
|
|
||||||
|
|
||||||
gross_income = get_revenue(income, period_list)
|
gross_income = get_revenue(income, period_list)
|
||||||
|
|
||||||
gross_expense = get_revenue(expense, period_list)
|
gross_expense = get_revenue(expense, period_list)
|
||||||
|
|
||||||
if(len(gross_income)==0 and len(gross_expense)== 0):
|
if(len(gross_income)==0 and len(gross_expense)== 0):
|
||||||
data.append({"account_name": "'" + _("Nothing is included in gross") + "'",
|
data.append({
|
||||||
"account": "'" + _("Nothing is included in gross") + "'"})
|
"account_name": "'" + _("Nothing is included in gross") + "'",
|
||||||
|
"account": "'" + _("Nothing is included in gross") + "'"
|
||||||
|
})
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
data.append({"account_name": "'" + _("Included in Gross Profit") + "'",
|
data.append({
|
||||||
"account": "'" + _("Included in Gross Profit") + "'"})
|
"account_name": "'" + _("Included in Gross Profit") + "'",
|
||||||
|
"account": "'" + _("Included in Gross Profit") + "'"
|
||||||
|
})
|
||||||
|
|
||||||
data.append({})
|
data.append({})
|
||||||
data.extend(gross_income or [])
|
data.extend(gross_income or [])
|
||||||
@ -111,7 +112,6 @@ def set_total(node, value, complete_list, totals):
|
|||||||
|
|
||||||
|
|
||||||
def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
|
def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
|
||||||
|
|
||||||
profit_loss = {
|
profit_loss = {
|
||||||
"account_name": "'" + _(profit_type) + "'",
|
"account_name": "'" + _(profit_type) + "'",
|
||||||
"account": "'" + _(profit_type) + "'",
|
"account": "'" + _(profit_type) + "'",
|
||||||
@ -123,7 +123,9 @@ def get_profit(gross_income, gross_expense, period_list, company, profit_type, c
|
|||||||
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
key = period if consolidated else period.key
|
key = period if consolidated else period.key
|
||||||
profit_loss[key] = flt(gross_income[0].get(key, 0)) - flt(gross_expense[0].get(key, 0))
|
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
|
||||||
|
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
|
||||||
|
profit_loss[key] = gross_income_for_period - gross_expense_for_period
|
||||||
|
|
||||||
if profit_loss[key]:
|
if profit_loss[key]:
|
||||||
has_value=True
|
has_value=True
|
||||||
@ -143,8 +145,14 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
|
|||||||
|
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
key = period if consolidated else period.key
|
key = period if consolidated else period.key
|
||||||
total_income = flt(gross_income[0].get(key, 0)) + flt(non_gross_income[0].get(key, 0))
|
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
|
||||||
total_expense = flt(gross_expense[0].get(key, 0)) + flt(non_gross_expense[0].get(key, 0))
|
non_gross_income_for_period = flt(non_gross_income[0].get(key, 0)) if non_gross_income else 0
|
||||||
|
|
||||||
|
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
|
||||||
|
non_gross_expense_for_period = flt(non_gross_expense[0].get(key, 0)) if non_gross_expense else 0
|
||||||
|
|
||||||
|
total_income = gross_income_for_period + non_gross_income_for_period
|
||||||
|
total_expense = gross_expense_for_period + non_gross_expense_for_period
|
||||||
profit_loss[key] = flt(total_income) - flt(total_expense)
|
profit_loss[key] = flt(total_income) - flt(total_expense)
|
||||||
|
|
||||||
if profit_loss[key]:
|
if profit_loss[key]:
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -365,9 +365,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 +382,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,
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
@ -27,7 +27,7 @@ class EmailCampaign(Document):
|
|||||||
for entry in campaign.get("campaign_schedules"):
|
for entry in campaign.get("campaign_schedules"):
|
||||||
send_after_days.append(entry.send_after_days)
|
send_after_days.append(entry.send_after_days)
|
||||||
try:
|
try:
|
||||||
end_date = add_days(getdate(self.start_date), max(send_after_days))
|
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
|
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
|
||||||
|
|
||||||
|
|||||||
0
erpnext/crm/doctype/linkedin_settings/__init__.py
Normal file
0
erpnext/crm/doctype/linkedin_settings/__init__.py
Normal file
71
erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
Normal file
71
erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('LinkedIn Settings', {
|
||||||
|
onload: function(frm){
|
||||||
|
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||||
|
frappe.confirm(
|
||||||
|
__('Session not valid, Do you want to login?'),
|
||||||
|
function(){
|
||||||
|
frm.trigger("login");
|
||||||
|
},
|
||||||
|
function(){
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refresh: function(frm){
|
||||||
|
if (frm.doc.session_status=="Expired"){
|
||||||
|
let msg = __("Session Not Active. Save doc to login.");
|
||||||
|
frm.dashboard.set_headline_alert(
|
||||||
|
`<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<span class="indicator whitespace-nowrap red"><span class="hidden-xs">${msg}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.session_status=="Active"){
|
||||||
|
let d = new Date(frm.doc.modified);
|
||||||
|
d.setDate(d.getDate()+60);
|
||||||
|
let dn = new Date();
|
||||||
|
let days = d.getTime() - dn.getTime();
|
||||||
|
days = Math.floor(days/(1000 * 3600 * 24));
|
||||||
|
let msg,color;
|
||||||
|
|
||||||
|
if (days>0){
|
||||||
|
msg = __("Your Session will be expire in ") + days + __(" days.");
|
||||||
|
color = "green";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg = __("Session is expired. Save doc to login.");
|
||||||
|
color = "red";
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.dashboard.set_headline_alert(
|
||||||
|
`<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
login: function(frm){
|
||||||
|
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||||
|
frappe.dom.freeze();
|
||||||
|
frappe.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: "get_authorization_url",
|
||||||
|
callback : function(r) {
|
||||||
|
window.location.href = r.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after_save: function(frm){
|
||||||
|
frm.trigger("login");
|
||||||
|
}
|
||||||
|
});
|
||||||
111
erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
Normal file
111
erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-01-30 13:36:39.492931",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"account_name",
|
||||||
|
"column_break_2",
|
||||||
|
"company_id",
|
||||||
|
"oauth_details",
|
||||||
|
"consumer_key",
|
||||||
|
"column_break_5",
|
||||||
|
"consumer_secret",
|
||||||
|
"user_details_section",
|
||||||
|
"access_token",
|
||||||
|
"person_urn",
|
||||||
|
"session_status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "account_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Account Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "oauth_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "OAuth Credentials"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumer_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Consumer Key",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumer_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Consumer Secret",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "access_token",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Access Token",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "person_urn",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Person URN",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "user_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "User Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "session_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Session Status",
|
||||||
|
"options": "Expired\nActive",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Company ID",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-04-16 23:22:51.966397",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "LinkedIn Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
166
erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
Normal file
166
erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# -*- 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, requests, json
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils.file_manager import get_file, get_file_path
|
||||||
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
|
class LinkedInSettings(Document):
|
||||||
|
def get_authorization_url(self):
|
||||||
|
params = urlencode({
|
||||||
|
"response_type":"code",
|
||||||
|
"client_id": self.consumer_key,
|
||||||
|
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
|
||||||
|
"scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social"
|
||||||
|
})
|
||||||
|
|
||||||
|
url = "https://www.linkedin.com/oauth/v2/authorization?{}".format(params)
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_access_token(self, code):
|
||||||
|
url = "https://www.linkedin.com/oauth/v2/accessToken"
|
||||||
|
body = {
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": code,
|
||||||
|
"client_id": self.consumer_key,
|
||||||
|
"client_secret": self.get_password(fieldname="consumer_secret"),
|
||||||
|
"redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback",
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.http_post(url=url, data=body, headers=headers)
|
||||||
|
response = frappe.parse_json(response.content.decode())
|
||||||
|
self.db_set("access_token", response["access_token"])
|
||||||
|
|
||||||
|
def get_member_profile(self):
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer {}".format(self.access_token)
|
||||||
|
}
|
||||||
|
url = "https://api.linkedin.com/v2/me"
|
||||||
|
response = requests.get(url=url, headers=headers)
|
||||||
|
response = frappe.parse_json(response.content.decode())
|
||||||
|
|
||||||
|
frappe.db.set_value(self.doctype, self.name, {
|
||||||
|
"person_urn": response["id"],
|
||||||
|
"account_name": response["vanityName"],
|
||||||
|
"session_status": "Active"
|
||||||
|
})
|
||||||
|
frappe.local.response["type"] = "redirect"
|
||||||
|
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
|
||||||
|
|
||||||
|
def post(self, text, media=None):
|
||||||
|
if not media:
|
||||||
|
return self.post_text(text)
|
||||||
|
else:
|
||||||
|
media_id = self.upload_image(media)
|
||||||
|
|
||||||
|
if media_id:
|
||||||
|
return self.post_text(text, media_id=media_id)
|
||||||
|
else:
|
||||||
|
frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
|
||||||
|
|
||||||
|
|
||||||
|
def upload_image(self, media):
|
||||||
|
media = get_file_path(media)
|
||||||
|
register_url = "https://api.linkedin.com/v2/assets?action=registerUpload"
|
||||||
|
body = {
|
||||||
|
"registerUploadRequest": {
|
||||||
|
"recipes": ["urn:li:digitalmediaRecipe:feedshare-image"],
|
||||||
|
"owner": "urn:li:organization:{0}".format(self.company_id),
|
||||||
|
"serviceRelationships": [{
|
||||||
|
"relationshipType": "OWNER",
|
||||||
|
"identifier": "urn:li:userGeneratedContent"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Bearer {}".format(self.access_token)
|
||||||
|
}
|
||||||
|
response = self.http_post(url=register_url, body=body, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
response = response.json()
|
||||||
|
asset = response["value"]["asset"]
|
||||||
|
upload_url = response["value"]["uploadMechanism"]["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]["uploadUrl"]
|
||||||
|
headers['Content-Type']='image/jpeg'
|
||||||
|
response = self.http_post(upload_url, headers=headers, data=open(media,"rb"))
|
||||||
|
if response.status_code < 200 and response.status_code > 299:
|
||||||
|
frappe.throw(_("Error While Uploading Image"), title="{0} {1}".format(response.status_code, response.reason))
|
||||||
|
return None
|
||||||
|
return asset
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def post_text(self, text, media_id=None):
|
||||||
|
url = "https://api.linkedin.com/v2/shares"
|
||||||
|
headers = {
|
||||||
|
"X-Restli-Protocol-Version": "2.0.0",
|
||||||
|
"Authorization": "Bearer {}".format(self.access_token),
|
||||||
|
"Content-Type": "application/json; charset=UTF-8"
|
||||||
|
}
|
||||||
|
body = {
|
||||||
|
"distribution": {
|
||||||
|
"linkedInDistributionTarget": {}
|
||||||
|
},
|
||||||
|
"owner":"urn:li:organization:{0}".format(self.company_id),
|
||||||
|
"subject": "Test Share Subject",
|
||||||
|
"text": {
|
||||||
|
"text": text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if media_id:
|
||||||
|
body["content"]= {
|
||||||
|
"contentEntities": [{
|
||||||
|
"entity": media_id
|
||||||
|
}],
|
||||||
|
"shareMediaCategory": "IMAGE"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.http_post(url=url, headers=headers, body=body)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def http_post(self, url, headers=None, body=None, data=None):
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
url = url,
|
||||||
|
json = body,
|
||||||
|
data = data,
|
||||||
|
headers = headers
|
||||||
|
)
|
||||||
|
if response.status_code not in [201,200]:
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
content = json.loads(response.content)
|
||||||
|
|
||||||
|
if response.status_code == 401:
|
||||||
|
self.db_set("session_status", "Expired")
|
||||||
|
frappe.db.commit()
|
||||||
|
frappe.throw(content["message"], title="LinkedIn Error - Unauthorized")
|
||||||
|
elif response.status_code == 403:
|
||||||
|
frappe.msgprint(_("You Didn't have permission to access this API"))
|
||||||
|
frappe.throw(content["message"], title="LinkedIn Error - Access Denied")
|
||||||
|
else:
|
||||||
|
frappe.throw(response.reason, title=response.status_code)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def callback(code=None, error=None, error_description=None):
|
||||||
|
if not error:
|
||||||
|
linkedin_settings = frappe.get_doc("LinkedIn Settings")
|
||||||
|
linkedin_settings.get_access_token(code)
|
||||||
|
linkedin_settings.get_member_profile()
|
||||||
|
frappe.db.commit()
|
||||||
|
else:
|
||||||
|
frappe.local.response["type"] = "redirect"
|
||||||
|
frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
|
||||||
@ -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 TestLinkedInSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
0
erpnext/crm/doctype/social_media_post/__init__.py
Normal file
0
erpnext/crm/doctype/social_media_post/__init__.py
Normal file
67
erpnext/crm/doctype/social_media_post/social_media_post.js
Normal file
67
erpnext/crm/doctype/social_media_post/social_media_post.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
frappe.ui.form.on('Social Media Post', {
|
||||||
|
validate: function(frm){
|
||||||
|
if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){
|
||||||
|
frappe.throw(__("Select atleast one Social Media from Share on."))
|
||||||
|
}
|
||||||
|
if (frm.doc.scheduled_time) {
|
||||||
|
let scheduled_time = new Date(frm.doc.scheduled_time);
|
||||||
|
let date_time = new Date();
|
||||||
|
if (scheduled_time.getTime() < date_time.getTime()){
|
||||||
|
frappe.throw(__("Invalid Scheduled Time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (frm.doc.text?.length > 280){
|
||||||
|
frappe.throw(__("Length Must be less than 280."))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refresh: function(frm){
|
||||||
|
if (frm.doc.docstatus === 1){
|
||||||
|
if (frm.doc.post_status != "Posted"){
|
||||||
|
add_post_btn(frm);
|
||||||
|
}
|
||||||
|
else if (frm.doc.post_status == "Posted"){
|
||||||
|
frm.set_df_property('sheduled_time', 'read_only', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let html='';
|
||||||
|
if (frm.doc.twitter){
|
||||||
|
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||||
|
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||||
|
html += `<div class="col-xs-6">
|
||||||
|
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span>
|
||||||
|
</div>` ;
|
||||||
|
}
|
||||||
|
if (frm.doc.linkedin){
|
||||||
|
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||||
|
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||||
|
html += `<div class="col-xs-6">
|
||||||
|
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span>
|
||||||
|
</div>` ;
|
||||||
|
}
|
||||||
|
html = `<div class="row">${html}</div>`;
|
||||||
|
frm.dashboard.set_headline_alert(html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var add_post_btn = function(frm){
|
||||||
|
frm.add_custom_button(('Post Now'), function(){
|
||||||
|
post(frm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var post = function(frm){
|
||||||
|
frappe.dom.freeze();
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.crm.doctype.social_media_post.social_media_post.publish",
|
||||||
|
args: {
|
||||||
|
doctype: frm.doc.doctype,
|
||||||
|
name: frm.doc.name
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
frm.reload_doc();
|
||||||
|
frappe.dom.unfreeze();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
166
erpnext/crm/doctype/social_media_post/social_media_post.json
Normal file
166
erpnext/crm/doctype/social_media_post/social_media_post.json
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format: CRM-SMP-{YYYY}-{MM}-{DD}-{###}",
|
||||||
|
"creation": "2020-01-30 11:53:13.872864",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"campaign_name",
|
||||||
|
"scheduled_time",
|
||||||
|
"post_status",
|
||||||
|
"column_break_6",
|
||||||
|
"twitter",
|
||||||
|
"linkedin",
|
||||||
|
"twitter_post_id",
|
||||||
|
"linkedin_post_id",
|
||||||
|
"content",
|
||||||
|
"text",
|
||||||
|
"column_break_14",
|
||||||
|
"tweet_preview",
|
||||||
|
"linkedin_section",
|
||||||
|
"linkedin_post",
|
||||||
|
"column_break_15",
|
||||||
|
"attachments_section",
|
||||||
|
"image",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "text",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Tweet",
|
||||||
|
"mandatory_depends_on": "eval:doc.twitter ==1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "twitter",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Twitter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "linkedin",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "LinkedIn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Social Media Post",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.twitter ==1",
|
||||||
|
"fieldname": "content",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Twitter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "post_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Post Status",
|
||||||
|
"options": "\nScheduled\nPosted\nError",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "twitter_post_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Twitter Post Id",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "linkedin_post_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "LinkedIn Post Id",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "campaign_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Campaign",
|
||||||
|
"options": "Campaign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"label": "Share On"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tweet_preview",
|
||||||
|
"fieldtype": "HTML"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "eval:doc.linkedin==1",
|
||||||
|
"fieldname": "linkedin_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "LinkedIn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "attachments_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Attachments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "linkedin_post",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Post",
|
||||||
|
"mandatory_depends_on": "eval:doc.linkedin ==1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_15",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "scheduled_time",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Scheduled Time",
|
||||||
|
"read_only_depends_on": "eval:doc.post_status == \"Posted\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-04-21 15:10:04.953713",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Social Media Post",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
56
erpnext/crm/doctype/social_media_post/social_media_post.py
Normal file
56
erpnext/crm/doctype/social_media_post/social_media_post.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# -*- 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
|
||||||
|
from frappe import _
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class SocialMediaPost(Document):
|
||||||
|
def validate(self):
|
||||||
|
if self.scheduled_time:
|
||||||
|
current_time = frappe.utils.now_datetime()
|
||||||
|
scheduled_time = frappe.utils.get_datetime(self.scheduled_time)
|
||||||
|
if scheduled_time < current_time:
|
||||||
|
frappe.throw(_("Invalid Scheduled Time"))
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
if self.scheduled_time:
|
||||||
|
self.post_status = "Scheduled"
|
||||||
|
super(SocialMediaPost, self).submit()
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
try:
|
||||||
|
if self.twitter and not self.twitter_post_id:
|
||||||
|
twitter = frappe.get_doc("Twitter Settings")
|
||||||
|
twitter_post = twitter.post(self.text, self.image)
|
||||||
|
self.db_set("twitter_post_id", twitter_post.id)
|
||||||
|
if self.linkedin and not self.linkedin_post_id:
|
||||||
|
linkedin = frappe.get_doc("LinkedIn Settings")
|
||||||
|
linkedin_post = linkedin.post(self.linkedin_post, self.image)
|
||||||
|
self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1])
|
||||||
|
self.db_set("post_status", "Posted")
|
||||||
|
|
||||||
|
except:
|
||||||
|
self.db_set("post_status", "Error")
|
||||||
|
title = _("Error while POSTING {0}").format(self.name)
|
||||||
|
traceback = frappe.get_traceback()
|
||||||
|
frappe.log_error(message=traceback , title=title)
|
||||||
|
|
||||||
|
def process_scheduled_social_media_posts():
|
||||||
|
posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"])
|
||||||
|
start = frappe.utils.now_datetime()
|
||||||
|
end = start + datetime.timedelta(minutes=10)
|
||||||
|
for post in posts:
|
||||||
|
if post.scheduled_time:
|
||||||
|
post_time = frappe.utils.get_datetime(post.scheduled_time)
|
||||||
|
if post_time > start and post_time <= end:
|
||||||
|
publish('Social Media Post', post.name)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def publish(doctype, name):
|
||||||
|
sm_post = frappe.get_doc(doctype, name)
|
||||||
|
sm_post.post()
|
||||||
|
frappe.db.commit()
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
frappe.listview_settings['Social Media Post'] = {
|
||||||
|
add_fields: ["status","post_status"],
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
return [__(doc.post_status), {
|
||||||
|
"Scheduled": "orange",
|
||||||
|
"Posted": "green",
|
||||||
|
"Error": "red"
|
||||||
|
}[doc.post_status]];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 TestSocialMediaPost(unittest.TestCase):
|
||||||
|
pass
|
||||||
0
erpnext/crm/doctype/twitter_settings/__init__.py
Normal file
0
erpnext/crm/doctype/twitter_settings/__init__.py
Normal 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 TestTwitterSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
52
erpnext/crm/doctype/twitter_settings/twitter_settings.js
Normal file
52
erpnext/crm/doctype/twitter_settings/twitter_settings.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Twitter Settings', {
|
||||||
|
onload: function(frm){
|
||||||
|
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||||
|
frappe.confirm(
|
||||||
|
__('Session not valid, Do you want to login?'),
|
||||||
|
function(){
|
||||||
|
frm.trigger("login");
|
||||||
|
},
|
||||||
|
function(){
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refresh: function(frm){
|
||||||
|
let msg,color;
|
||||||
|
if (frm.doc.session_status == "Active"){
|
||||||
|
msg = __("Session Active");
|
||||||
|
color = 'green';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg = __("Session Not Active. Save doc to login.");
|
||||||
|
color = 'red';
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.dashboard.set_headline_alert(
|
||||||
|
`<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">${msg}</span></span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
login: function(frm){
|
||||||
|
if (frm.doc.consumer_key && frm.doc.consumer_secret){
|
||||||
|
frappe.dom.freeze();
|
||||||
|
frappe.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: "get_authorize_url",
|
||||||
|
callback : function(r) {
|
||||||
|
window.location.href = r.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after_save: function(frm){
|
||||||
|
frm.trigger("login");
|
||||||
|
}
|
||||||
|
});
|
||||||
101
erpnext/crm/doctype/twitter_settings/twitter_settings.json
Normal file
101
erpnext/crm/doctype/twitter_settings/twitter_settings.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-01-30 10:29:08.562108",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"account_name",
|
||||||
|
"profile_pic",
|
||||||
|
"oauth_details",
|
||||||
|
"consumer_key",
|
||||||
|
"column_break_5",
|
||||||
|
"consumer_secret",
|
||||||
|
"oauth_token",
|
||||||
|
"oauth_secret",
|
||||||
|
"session_status"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "account_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Account Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "oauth_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "OAuth Credentials"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumer_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "API Key",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumer_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "API Secret Key",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "oauth_token",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "OAuth Token",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "oauth_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "OAuth Token Secret",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_5",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "profile_pic",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"hidden": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "session_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Session Status",
|
||||||
|
"options": "Expired\nActive",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image_field": "profile_pic",
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-04-21 22:06:43.726798",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Twitter Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
98
erpnext/crm/doctype/twitter_settings/twitter_settings.py
Normal file
98
erpnext/crm/doctype/twitter_settings/twitter_settings.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# -*- 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, os, tweepy, json
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils.file_manager import get_file_path
|
||||||
|
from frappe.utils import get_url_to_form, get_link_to_form
|
||||||
|
from tweepy.error import TweepError
|
||||||
|
|
||||||
|
class TwitterSettings(Document):
|
||||||
|
def get_authorize_url(self):
|
||||||
|
callback_url = "{0}/?cmd=erpnext.crm.doctype.twitter_settings.twitter_settings.callback".format(frappe.utils.get_url())
|
||||||
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
redirect_url = auth.get_authorization_url()
|
||||||
|
return redirect_url
|
||||||
|
except:
|
||||||
|
frappe.msgprint(_("Error! Failed to get request token."))
|
||||||
|
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
|
||||||
|
|
||||||
|
|
||||||
|
def get_access_token(self, oauth_token, oauth_verifier):
|
||||||
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
||||||
|
auth.request_token = {
|
||||||
|
'oauth_token' : oauth_token,
|
||||||
|
'oauth_token_secret' : oauth_verifier
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth.get_access_token(oauth_verifier)
|
||||||
|
api = self.get_api()
|
||||||
|
user = api.me()
|
||||||
|
profile_pic = (user._json["profile_image_url"]).replace("_normal","")
|
||||||
|
|
||||||
|
frappe.db.set_value(self.doctype, self.name, {
|
||||||
|
"oauth_token" : auth.access_token,
|
||||||
|
"oauth_secret" : auth.access_token_secret,
|
||||||
|
"account_name" : user._json["screen_name"],
|
||||||
|
"profile_pic" : profile_pic,
|
||||||
|
"session_status" : "Active"
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.local.response["type"] = "redirect"
|
||||||
|
frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")
|
||||||
|
except TweepError as e:
|
||||||
|
frappe.msgprint(_("Error! Failed to get access token."))
|
||||||
|
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
|
||||||
|
|
||||||
|
def get_api(self):
|
||||||
|
# authentication of consumer key and secret
|
||||||
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
||||||
|
# authentication of access token and secret
|
||||||
|
auth.set_access_token(self.oauth_token, self.get_password(fieldname="oauth_secret"))
|
||||||
|
|
||||||
|
return tweepy.API(auth)
|
||||||
|
|
||||||
|
def post(self, text, media=None):
|
||||||
|
if not media:
|
||||||
|
return self.send_tweet(text)
|
||||||
|
|
||||||
|
if media:
|
||||||
|
media_id = self.upload_image(media)
|
||||||
|
return self.send_tweet(text, media_id)
|
||||||
|
|
||||||
|
def upload_image(self, media):
|
||||||
|
media = get_file_path(media)
|
||||||
|
api = self.get_api()
|
||||||
|
media = api.media_upload(media)
|
||||||
|
|
||||||
|
return media.media_id
|
||||||
|
|
||||||
|
def send_tweet(self, text, media_id=None):
|
||||||
|
api = self.get_api()
|
||||||
|
try:
|
||||||
|
if media_id:
|
||||||
|
response = api.update_status(status = text, media_ids = [media_id])
|
||||||
|
else:
|
||||||
|
response = api.update_status(status = text)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
except TweepError as e:
|
||||||
|
content = json.loads(e.response.content)
|
||||||
|
content = content["errors"][0]
|
||||||
|
if e.response.status_code == 401:
|
||||||
|
self.db_set("session_status", "Expired")
|
||||||
|
frappe.db.commit()
|
||||||
|
frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def callback(oauth_token, oauth_verifier):
|
||||||
|
twitter_settings = frappe.get_single("Twitter Settings")
|
||||||
|
twitter_settings.get_access_token(oauth_token,oauth_verifier)
|
||||||
|
frappe.db.commit()
|
||||||
@ -144,6 +144,10 @@ def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
|||||||
def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_lang):
|
def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_lang):
|
||||||
company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
|
company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
|
||||||
|
|
||||||
|
default_warehouse = _("Stores - {0}", sys_lang).format(company_abbr)
|
||||||
|
if not frappe.db.exists("Warehouse", default_warehouse):
|
||||||
|
frappe.throw(_("Please set Warehouse in Woocommerce Settings"))
|
||||||
|
|
||||||
for item in order.get("line_items"):
|
for item in order.get("line_items"):
|
||||||
woocomm_item_id = item.get("product_id")
|
woocomm_item_id = item.get("product_id")
|
||||||
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
||||||
@ -158,7 +162,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
|||||||
"uom": woocommerce_settings.uom or _("Nos", sys_lang),
|
"uom": woocommerce_settings.uom or _("Nos", sys_lang),
|
||||||
"qty": item.get("quantity"),
|
"qty": item.get("quantity"),
|
||||||
"rate": item.get("price"),
|
"rate": item.get("price"),
|
||||||
"warehouse": woocommerce_settings.warehouse or _("Stores - {0}", sys_lang).format(company_abbr)
|
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
||||||
})
|
})
|
||||||
|
|
||||||
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
||||||
|
|||||||
@ -270,7 +270,8 @@ auto_cancel_exempted_doctypes= [
|
|||||||
scheduler_events = {
|
scheduler_events = {
|
||||||
"all": [
|
"all": [
|
||||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||||
"erpnext.healthcare_healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder"
|
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder",
|
||||||
|
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts"
|
||||||
],
|
],
|
||||||
"hourly": [
|
"hourly": [
|
||||||
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
|
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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, format_date
|
||||||
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, format_date(self.attendance_date)))
|
||||||
|
else:
|
||||||
|
self.status = 'On Leave'
|
||||||
|
frappe.msgprint(_("Employee {0} is on Leave on {1}")
|
||||||
|
.format(self.employee, format_date(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, format_date(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
|
||||||
|
|
||||||
|
|||||||
@ -136,9 +136,18 @@ def make_bank_entry(dt, dn):
|
|||||||
def make_return_entry(employee, company, employee_advance_name,
|
def make_return_entry(employee, company, employee_advance_name,
|
||||||
return_amount, advance_account, mode_of_payment=None):
|
return_amount, advance_account, mode_of_payment=None):
|
||||||
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||||
|
|
||||||
|
mode_of_payment_type = ''
|
||||||
|
if mode_of_payment:
|
||||||
|
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
|
||||||
|
if mode_of_payment_type not in ["Cash", "Bank"]:
|
||||||
|
# if mode of payment is General then it unset the type
|
||||||
|
mode_of_payment_type = None
|
||||||
|
|
||||||
je = frappe.new_doc('Journal Entry')
|
je = frappe.new_doc('Journal Entry')
|
||||||
je.posting_date = nowdate()
|
je.posting_date = nowdate()
|
||||||
je.voucher_type = 'Bank Entry'
|
# if mode of payment is Bank then voucher type is Bank Entry
|
||||||
|
je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
|
||||||
je.company = company
|
je.company = company
|
||||||
je.remark = 'Return against Employee Advance: ' + employee_advance_name
|
je.remark = 'Return against Employee Advance: ' + employee_advance_name
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
|
||||||
}
|
}
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
0
erpnext/hr/doctype/income_tax_slab/__init__.py
Normal file
0
erpnext/hr/doctype/income_tax_slab/__init__.py
Normal file
6
erpnext/hr/doctype/income_tax_slab/income_tax_slab.js
Normal file
6
erpnext/hr/doctype/income_tax_slab/income_tax_slab.js
Normal 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', {
|
||||||
|
|
||||||
|
});
|
||||||
160
erpnext/hr/doctype/income_tax_slab/income_tax_slab.json
Normal file
160
erpnext/hr/doctype/income_tax_slab/income_tax_slab.json
Normal 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
|
||||||
|
}
|
||||||
10
erpnext/hr/doctype/income_tax_slab/income_tax_slab.py
Normal file
10
erpnext/hr/doctype/income_tax_slab/income_tax_slab.py
Normal 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
|
||||||
10
erpnext/hr/doctype/income_tax_slab/test_income_tax_slab.py
Normal file
10
erpnext/hr/doctype/income_tax_slab/test_income_tax_slab.py
Normal 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
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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))
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -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()
|
||||||
|
|||||||
@ -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 < 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 > 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 < 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 > 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",
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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, format_date
|
||||||
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 format_date(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):
|
||||||
|
|||||||
@ -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()
|
||||||
@ -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')}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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){
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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
|
||||||
@ -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:
|
||||||
|
|||||||
@ -84,7 +84,7 @@ class LoanDisbursement(AccountsController):
|
|||||||
gle_map.append(
|
gle_map.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": loan_details.loan_account,
|
"account": loan_details.loan_account,
|
||||||
"against": loan_details.applicant,
|
"against": loan_details.payment_account,
|
||||||
"debit": self.disbursed_amount,
|
"debit": self.disbursed_amount,
|
||||||
"debit_in_account_currency": self.disbursed_amount,
|
"debit_in_account_currency": self.disbursed_amount,
|
||||||
"against_voucher_type": "Loan",
|
"against_voucher_type": "Loan",
|
||||||
@ -100,7 +100,7 @@ class LoanDisbursement(AccountsController):
|
|||||||
gle_map.append(
|
gle_map.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": loan_details.payment_account,
|
"account": loan_details.payment_account,
|
||||||
"against": loan_details.applicant,
|
"against": loan_details.loan_account,
|
||||||
"credit": self.disbursed_amount,
|
"credit": self.disbursed_amount,
|
||||||
"credit_in_account_currency": self.disbursed_amount,
|
"credit_in_account_currency": self.disbursed_amount,
|
||||||
"against_voucher_type": "Loan",
|
"against_voucher_type": "Loan",
|
||||||
|
|||||||
@ -193,7 +193,7 @@ class BOM(WebsiteGenerator):
|
|||||||
if self.rm_cost_as_per == 'Valuation Rate':
|
if self.rm_cost_as_per == 'Valuation Rate':
|
||||||
rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
|
rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
|
||||||
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
||||||
rate = (arg.get('last_purchase_rate') \
|
rate = flt(arg.get('last_purchase_rate') \
|
||||||
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
|
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
|
||||||
* (arg.get("conversion_factor") or 1)
|
* (arg.get("conversion_factor") or 1)
|
||||||
elif self.rm_cost_as_per == "Price List":
|
elif self.rm_cost_as_per == "Price List":
|
||||||
|
|||||||
@ -346,6 +346,7 @@ class ProductionPlan(Document):
|
|||||||
if not wo.fg_warehouse:
|
if not wo.fg_warehouse:
|
||||||
wo.fg_warehouse = warehouse.get('fg_warehouse')
|
wo.fg_warehouse = warehouse.get('fg_warehouse')
|
||||||
try:
|
try:
|
||||||
|
wo.flags.ignore_mandatory = True
|
||||||
wo.insert()
|
wo.insert()
|
||||||
return wo.name
|
return wo.name
|
||||||
except OverProductionError:
|
except OverProductionError:
|
||||||
|
|||||||
@ -404,7 +404,6 @@ frappe.ui.form.on("Work Order", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
before_submit: function(frm) {
|
before_submit: function(frm) {
|
||||||
frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);
|
|
||||||
frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true);
|
frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true);
|
||||||
frm.toggle_reqd("transfer_material_against",
|
frm.toggle_reqd("transfer_material_against",
|
||||||
frm.doc.operations && frm.doc.operations.length > 0);
|
frm.doc.operations && frm.doc.operations.length > 0);
|
||||||
|
|||||||
@ -231,6 +231,7 @@
|
|||||||
"fieldname": "wip_warehouse",
|
"fieldname": "wip_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Work-in-Progress Warehouse",
|
"label": "Work-in-Progress Warehouse",
|
||||||
|
"mandatory_depends_on": "eval:!doc.skip_transfer || doc.from_wip_warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -238,7 +239,8 @@
|
|||||||
"fieldname": "fg_warehouse",
|
"fieldname": "fg_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Target Warehouse",
|
"label": "Target Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
@ -481,7 +483,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-01-31 12:46:23.636033",
|
"modified": "2020-04-24 19:32:43.323054",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
|
|||||||
@ -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,8 @@ 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.fix_quotation_expired_status
|
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.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
|
||||||
|
erpnext.patches.v12_0.fix_quotation_expired_status
|
||||||
|
|||||||
@ -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')""")
|
||||||
|
|||||||
13
erpnext/patches/v12_0/add_permission_in_lower_deduction.py
Normal file
13
erpnext/patches/v12_0/add_permission_in_lower_deduction.py
Normal 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)
|
||||||
6
erpnext/patches/v12_0/set_default_payroll_based_on.py
Normal file
6
erpnext/patches/v12_0/set_default_payroll_based_on.py
Normal 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")
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import add_days, getdate, today
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.db.exists('DocType', 'Email Campaign'):
|
||||||
|
email_campaign = frappe.get_all('Email Campaign')
|
||||||
|
for campaign in email_campaign:
|
||||||
|
doc = frappe.get_doc("Email Campaign",campaign["name"])
|
||||||
|
send_after_days = []
|
||||||
|
|
||||||
|
camp = frappe.get_doc("Campaign", doc.campaign_name)
|
||||||
|
for entry in camp.get("campaign_schedules"):
|
||||||
|
send_after_days.append(entry.send_after_days)
|
||||||
|
if send_after_days:
|
||||||
|
end_date = add_days(getdate(doc.start_date), max(send_after_days))
|
||||||
|
doc.db_set("end_date", end_date)
|
||||||
|
today_date = getdate(today())
|
||||||
|
if doc.start_date > today_date:
|
||||||
|
doc.db_set("status", "Scheduled")
|
||||||
|
elif end_date >= today_date:
|
||||||
|
doc.db_set("status", "In Progress")
|
||||||
|
elif end_date < today_date:
|
||||||
|
doc.db_set("status", "Completed")
|
||||||
0
erpnext/patches/v13_0/__init__.py
Normal file
0
erpnext/patches/v13_0/__init__.py
Normal 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"):
|
||||||
|
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
|
||||||
@ -288,7 +288,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("");
|
||||||
|
|
||||||
@ -1412,7 +1412,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
me.frm.doc.items.forEach(d => {
|
me.frm.doc.items.forEach(d => {
|
||||||
if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) {
|
if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) {
|
||||||
for(var k in data) {
|
for(var k in data) {
|
||||||
if (in_list(fields, k) && data[k]) {
|
if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'price' || k === 'pricing_rules')) {
|
||||||
frappe.model.set_value(d.doctype, d.name, k, data[k]);
|
frappe.model.set_value(d.doctype, d.name, k, data[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
}
|
||||||
@ -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)))
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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):
|
||||||
|
|||||||
@ -8,6 +8,10 @@ def get_data():
|
|||||||
{
|
{
|
||||||
'label': _('Email Campaigns'),
|
'label': _('Email Campaigns'),
|
||||||
'items': ['Email Campaign']
|
'items': ['Email Campaign']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Social Media Campaigns'),
|
||||||
|
'items': ['Social Media Post']
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,8 +149,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
|
|
||||||
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
|
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
|
||||||
|
|
||||||
|
const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
|
||||||
|
const order_is_maintenance = ["Maintenance"].indexOf(doc.order_type) !== -1;
|
||||||
|
// order type has been customised then show all the action buttons
|
||||||
|
const order_is_a_custom_sale = ["Sales", "Shopping Cart", "Maintenance"].indexOf(doc.order_type) === -1;
|
||||||
|
|
||||||
// delivery note
|
// delivery note
|
||||||
if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
|
if(flt(doc.per_delivered, 6) < 100 && (order_is_a_sale || order_is_a_custom_sale) && allow_delivery) {
|
||||||
this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create'));
|
this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create'));
|
||||||
this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create'));
|
this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create'));
|
||||||
}
|
}
|
||||||
@ -161,8 +166,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// material request
|
// material request
|
||||||
if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1
|
if(!doc.order_type || (order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 6) < 100) {
|
||||||
&& flt(doc.per_delivered, 6) < 100) {
|
|
||||||
this.frm.add_custom_button(__('Material Request'), () => this.make_material_request(), __('Create'));
|
this.frm.add_custom_button(__('Material Request'), () => this.make_material_request(), __('Create'));
|
||||||
this.frm.add_custom_button(__('Request for Raw Materials'), () => this.make_raw_material_request(), __('Create'));
|
this.frm.add_custom_button(__('Request for Raw Materials'), () => this.make_raw_material_request(), __('Create'));
|
||||||
}
|
}
|
||||||
@ -171,14 +175,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create'));
|
this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create'));
|
||||||
|
|
||||||
// maintenance
|
// maintenance
|
||||||
if(flt(doc.per_delivered, 2) < 100 &&
|
if(flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
|
||||||
["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
|
|
||||||
this.frm.add_custom_button(__('Maintenance Visit'), () => this.make_maintenance_visit(), __('Create'));
|
this.frm.add_custom_button(__('Maintenance Visit'), () => this.make_maintenance_visit(), __('Create'));
|
||||||
this.frm.add_custom_button(__('Maintenance Schedule'), () => this.make_maintenance_schedule(), __('Create'));
|
this.frm.add_custom_button(__('Maintenance Schedule'), () => this.make_maintenance_schedule(), __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// project
|
// project
|
||||||
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
|
if(flt(doc.per_delivered, 2) < 100 && (order_is_a_sale || order_is_a_custom_sale) && allow_delivery) {
|
||||||
this.frm.add_custom_button(__('Project'), () => this.make_project(), __('Create'));
|
this.frm.add_custom_button(__('Project'), () => this.make_project(), __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user