Merge branch 'develop' into quo-status-fix

This commit is contained in:
rohitwaghchaure 2020-04-27 00:22:43 +05:30 committed by GitHub
commit 4d754eadc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 3417 additions and 1992 deletions

View File

@ -302,7 +302,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("contact_email", "");
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) {
frappe.msgprint(__("Please select Posting Date before selecting Party"))
frm.set_value("party", "");

View File

@ -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:
item_details.update({
'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)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
})

View File

@ -261,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
price_list: this.frm.doc.buying_price_list
}, function() {
me.apply_pricing_rule();
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("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() {
var me = this;
if(this.frm.doc.credit_to) {

View File

@ -13,6 +13,7 @@
"supplier_name",
"tax_id",
"due_date",
"tax_withholding_category",
"column_break1",
"company",
"posting_date",
@ -1294,13 +1295,21 @@
"fieldtype": "Check",
"label": "Is Internal Supplier",
"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",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-04-17 13:05:25.199832",
"modified": "2020-04-18 13:05:25.199832",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -1002,7 +1002,7 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds:
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:
return

View File

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

View File

@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.script_manager.trigger("is_pos");
me.frm.refresh_fields();
}
erpnext.queries.setup_warehouse_query(this.frm);
},
refresh: function(doc, dt, dn) {
@ -586,7 +587,9 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query("account_for_change_amount", function() {
return {
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() {
return {
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() {
return {
filters:{
"company": frm.doc.company
"company": frm.doc.company,
"is_group": 0
}
}
};

View File

@ -6,23 +6,42 @@ from __future__ import unicode_literals
import frappe
from frappe import _
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
class TaxWithholdingCategory(Document):
pass
def get_party_tax_withholding_details(ref_doc):
tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
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:
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)
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
.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)
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."))
def get_tax_row(tax_details, tds_amount):
return {
"category": "Total",
"add_deduct_tax": "Deduct",
@ -60,25 +80,36 @@ def get_tax_row(tax_details, 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
tds_amount = 0
tds_deducted = 0
def _get_tds(amount):
def _get_tds(amount, rate):
if amount <= 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("""
select voucher_no, credit
from `tabGL Entry`
where party=%s and fiscal_year=%s and credit > 0
""", (ref_doc.supplier, fiscal_year), as_dict=1)
where company = %s and
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]
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
@ -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
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:
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
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)'],
filters = {
'parent': ('in', vouchers), 'docstatus': 1,
'party': ref_doc.supplier,
'party': ('in', suppliers),
'reference_type': ('not in', ['Purchase Invoice'])
}, as_list=1)
supplier_credit_amount += (jv_supplier_credit_amt[0][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
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)):
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
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
if company:
condition += "and company =%s" % (company)
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("""
select distinct voucher_no
from `tabGL Entry`
where party=%s and %s and debit > 0
""", (supplier, condition)) or []
where party in %s and %s and debit > 0
""", (tuple(suppliers), condition)) or []
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None):
condition = ""
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
condition = "and 1=1"
if company:
condition = " and company=%s " % company
if len(suppliers) == 1:
suppliers.append(suppliers[0])
return flt(frappe.db.sql("""
select abs(sum(net_total))
from `tabPurchase Invoice`
where supplier=%s %s and is_return=1 and docstatus=1
and posting_date between %s and %s
""", (supplier, condition, year_start_date, year_end_date)))
where supplier in %s and is_return=1 and docstatus=1
and posting_date between %s and %s %s
""", (tuple(suppliers), year_start_date, year_end_date, condition)))
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
return current_amount * rate/100
else:
ltds_amount = (certificate_limit - deducted_amount)
tds_amount = current_amount - ltds_amount
return ltds_amount * rate/100 + tds_amount * tax_details.rate/100
def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit):
valid = False
if ((getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and
certificate_limit > deducted_amount):
valid = True
return valid

View File

@ -8,7 +8,6 @@ from frappe.utils import flt
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
import copy
def execute(filters=None):
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
filters.periodicity, filters.accumulated_values, filters.company)
@ -27,17 +26,19 @@ def execute(filters=None):
gross_income = get_revenue(income, period_list)
gross_expense = get_revenue(expense, period_list)
if(len(gross_income)==0 and len(gross_expense)== 0):
data.append({"account_name": "'" + _("Nothing is included in gross") + "'",
"account": "'" + _("Nothing is included in gross") + "'"})
data.append({
"account_name": "'" + _("Nothing is included in gross") + "'",
"account": "'" + _("Nothing is included in gross") + "'"
})
return columns, data
data.append({"account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'"})
data.append({
"account_name": "'" + _("Included in Gross Profit") + "'",
"account": "'" + _("Included in Gross Profit") + "'"
})
data.append({})
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):
profit_loss = {
"account_name": "'" + _(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:
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]:
has_value=True
@ -143,12 +145,18 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
for period in period_list:
key = period if consolidated else period.key
total_income = flt(gross_income[0].get(key, 0)) + flt(non_gross_income[0].get(key, 0))
total_expense = flt(gross_expense[0].get(key, 0)) + flt(non_gross_expense[0].get(key, 0))
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 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)
if profit_loss[key]:
has_value=True
if has_value:
return profit_loss
return profit_loss

View File

@ -44,9 +44,14 @@ def get_result(filters):
out = []
for supplier in filters.supplier:
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:
account = [d.account for d in tds.accounts if d.company == filters.company][0]
except IndexError:
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]))
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)
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])),
(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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,26 @@
{
"actions": [],
"creation": "2013-02-22 01:27:42",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"main_item_code",
"rm_item_code",
"required_qty",
"supplied_qty",
"rate",
"amount",
"column_break_6",
"bom_detail_no",
"reference_name",
"conversion_factor",
"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": [
{
@ -120,15 +126,34 @@
"in_list_view": 1,
"label": "Supplied Qty",
"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,
"idx": 1,
"istable": 1,
"modified": "2019-08-20 13:37:32.702068",
"links": [],
"modified": "2020-03-12 15:43:53.862897",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com",
"permissions": []
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

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

View File

@ -27,7 +27,7 @@ class EmailCampaign(Document):
for entry in campaign.get("campaign_schedules"):
send_after_days.append(entry.send_after_days)
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:
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))

View 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");
}
});

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

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

View File

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

View 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();
}
})
}

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

View 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()

View File

@ -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]];
}
}

View File

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

View File

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

View 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");
}
});

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

View 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()

View File

@ -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):
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"):
woocomm_item_id = item.get("product_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),
"qty": item.get("quantity"),
"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)

View File

@ -270,7 +270,8 @@ auto_cancel_exempted_doctypes= [
scheduler_events = {
"all": [
"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": [
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',

View File

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

View File

@ -7,33 +7,15 @@ import frappe
from frappe.utils import getdate, nowdate
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, get_datetime_str
from frappe.utils import update_progress_bar
from frappe.utils import cstr, get_datetime, format_date
class Attendance(Document):
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:
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(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()
def validate_attendance_date(self):
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):
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):
emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
self.employee)
if not emp:
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()
def get_events(start, end, filters=None):
events = []
@ -90,18 +105,20 @@ def add_attendance(events, start, end, conditions=None):
if e not in events:
events.append(e)
def mark_attendance(employee, attendance_date, status, shift=None):
employee_doc = frappe.get_doc('Employee', employee)
def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
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',
'employee': employee,
'attendance_date': attendance_date,
'status': status,
'company': employee_doc.company,
'shift': shift
}
attendance = frappe.get_doc(doc_dict).insert()
'company': company,
'shift': shift,
'leave_type': leave_type
})
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit()
return attendance.name

View File

@ -136,9 +136,18 @@ def make_bank_entry(dt, dn):
def make_return_entry(employee, company, employee_advance_name,
return_amount, advance_account, mode_of_payment=None):
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
mode_of_payment_type = ''
if mode_of_payment:
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
if mode_of_payment_type not in ["Cash", "Bank"]:
# if mode of payment is General then it unset the type
mode_of_payment_type = None
je = frappe.new_doc('Journal Entry')
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.remark = 'Return against Employee Advance: ' + employee_advance_name

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,31 +8,17 @@ from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
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
class DuplicateDeclarationError(frappe.ValidationError): pass
from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionDeclaration(Document):
def validate(self):
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_exemption_amount()
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):
self.total_declared_amount = 0.0
for d in self.declarations:

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext
import unittest
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):
def setUp(self):

View File

@ -21,8 +21,6 @@
"total_actual_amount",
"column_break_12",
"exemption_amount",
"other_incomes_section",
"income_from_other_sources",
"attachment_section",
"attachments",
"amended_from"
@ -111,16 +109,6 @@
"label": "Total Exemption Amount",
"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",
"fieldtype": "Section Break"
@ -142,7 +130,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-03-02 19:02:15.398486",
"modified": "2020-03-18 14:55:51.420016",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Tax Exemption Proof Submission",

View File

@ -7,7 +7,8 @@ import frappe
from frappe.model.document import Document
from frappe import _
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):
def validate(self):
@ -15,6 +16,7 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.set_total_actual_amount()
self.set_total_exemption_amount()
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):
self.total_actual_amount = flt(self.get("house_rent_payment_amount"))
@ -32,4 +34,4 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.exemption_amount += hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.monthly_house_rent = hra_exemption["monthly_house_rent"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]

View File

@ -13,10 +13,12 @@
"stop_birthday_reminders",
"expense_approver_mandatory_in_expense_claim",
"payroll_settings",
"payroll_based_on",
"max_working_hours_against_timesheet",
"include_holidays_in_total_working_days",
"disable_rounded_total",
"max_working_hours_against_timesheet",
"column_break_11",
"daily_wages_fraction_for_half_day",
"email_salary_slip_to_employee",
"encrypt_salary_slips_in_emails",
"password_policy",
@ -184,13 +186,27 @@
"fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application",
"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",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2020-01-06 18:46:30.189815",
"modified": "2020-04-13 21:20:59.382394",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",

View File

@ -15,6 +15,9 @@ class HRSettings(Document):
self.set_naming_series()
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):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,16 +30,16 @@ class LeaveAllocation(Document):
def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "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:
leave_allocated = 0
if leave_period:
leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
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:
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
.format(self.leave_type, self.employee))
.format(self.leave_type, self.employee))
def on_submit(self):
self.create_leave_ledger_entry()

View File

@ -374,7 +374,8 @@ class LeaveApplication(Document):
leaves=self.total_leave_days * -1,
from_date=self.from_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)
@ -384,7 +385,9 @@ class LeaveApplication(Document):
from_date=self.from_date,
to_date=expiry_date,
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)
@ -410,7 +413,7 @@ def get_allocation_expiry(employee, leave_type, to_date, from_date):
return expiry[0]['to_date'] if expiry else None
@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
if cint(half_day) == 1:
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
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
@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'])
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
@ -589,7 +592,7 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
''' Returns leave entries between from_date and to_date. '''
return frappe.db.sql("""
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
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
@ -607,9 +610,10 @@ def get_leave_entries(employee, leave_type, from_date, to_date):
}, as_dict=1)
@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'''
holiday_list = get_holiday_list_for_employee(employee)
if not holiday_list:
holiday_list = get_holiday_list_for_employee(employee)
holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
"deduct_full_tax_on_selected_payroll_date",
"depends_on_payment_days",
"is_tax_applicable",
"exempted_from_income_tax",
"is_flexible_benefit",
"variable_based_on_taxable_salary",
"section_break_2",
@ -62,6 +63,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_tax_applicable",
"fieldname": "is_tax_applicable",
"fieldtype": "Check",
@ -71,6 +73,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='earnings'",
"fetch_from": "salary_component.is_flexible_benefit",
"fieldname": "is_flexible_benefit",
"fieldtype": "Check",
@ -80,6 +83,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.parentfield=='deductions'",
"fetch_from": "salary_component.variable_based_on_taxable_salary",
"fieldname": "variable_based_on_taxable_salary",
"fieldtype": "Check",
@ -187,11 +191,20 @@
"fieldtype": "HTML",
"label": "Condition and Formula Help",
"options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Use field name from Salary Slip in conditions and formulas. <code>Payment Days = payment_days</code><code>Leave without pay = leave_without_pay</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base &lt; 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS &gt; 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>"
},
{
"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,
"links": [],
"modified": "2019-12-31 17:15:25.646689",
"modified": "2020-04-24 20:00:16.475295",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",

View File

@ -51,7 +51,7 @@ frappe.ui.form.on("Salary Slip", {
},
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){
@ -86,7 +86,7 @@ frappe.ui.form.on("Salary Slip", {
salary_slip_based_on_timesheet: function(frm) {
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) {
@ -95,15 +95,14 @@ frappe.ui.form.on("Salary Slip", {
},
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){
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({
method: 'process_salary_based_on_leave',
method: 'process_salary_based_on_working_days',
doc: frm.doc,
args: {"lwp": frm.doc.leave_without_pay},
callback: function(r, rt) {
frm.refresh();
}
@ -115,12 +114,12 @@ frappe.ui.form.on("Salary Slip", {
frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)===1);
frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'],
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({
method: 'get_emp_and_leave_details',
method: 'get_emp_and_working_day_details',
doc: frm.doc,
callback: function(r, rt) {
frm.refresh();

View File

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

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, erpnext
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 import msgprint, _
@ -44,9 +44,9 @@ class SalarySlip(TransactionBase):
if not (len(self.get("earnings")) or len(self.get("deductions"))):
# get details from salary structure
self.get_emp_and_leave_details()
self.get_emp_and_working_day_details()
else:
self.get_leave_details(lwp = self.leave_without_pay)
self.get_working_days_details(lwp = self.leave_without_pay)
self.calculate_net_pay()
@ -117,7 +117,7 @@ class SalarySlip(TransactionBase):
self.start_date = date_details.start_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'''
if self.employee:
self.set("earnings", [])
@ -129,7 +129,8 @@ class SalarySlip(TransactionBase):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["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)
if struct:
@ -188,10 +189,9 @@ class SalarySlip(TransactionBase):
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):
if not joining_date:
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
def get_working_days_details(self, joining_date=None, relieving_date=None, lwp=None, for_preview=0):
payroll_based_on = frappe.db.get_value("HR Settings", None, "payroll_based_on")
include_holidays_in_total_working_days = frappe.db.get_single_value("HR Settings", "include_holidays_in_total_working_days")
working_days = date_diff(self.end_date, self.start_date) + 1
if for_preview:
@ -200,24 +200,42 @@ class SalarySlip(TransactionBase):
return
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)
if working_days < 0:
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:
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.total_working_days = working_days
payment_days = flt(self.get_payment_days(joining_date, relieving_date)) - flt(lwp)
self.payment_days = payment_days > 0 and payment_days or 0
payment_days = self.get_payment_days(joining_date,
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)
if joining_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
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)
payment_days -= len(holidays)
return payment_days
def get_holidays_for_employee(self, start_date, end_date):
@ -256,27 +275,67 @@ class SalarySlip(TransactionBase):
return holidays
def calculate_lwp(self, holidays, working_days):
def calculate_lwp_based_on_leave_application(self, holidays, working_days):
lwp = 0
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):
dt = add_days(cstr(getdate(self.start_date)), d)
leave = frappe.db.sql("""
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
FROM `tabLeave Application` t1, `tabLeave Type` t2
WHERE t2.name = t1.leave_type
AND t2.is_lwp = 1
AND t1.docstatus = 1
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, '') = ''
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date and ifnull(t1.salary_slip, '') = ''
END
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
""".format(holidays), {"employee": self.employee, "dt": dt})
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
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,
'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,
'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:
if struct_row.get("is_additional_component"):
@ -482,10 +542,12 @@ class SalarySlip(TransactionBase):
return self.calculate_variable_tax(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)
remaining_sub_periods = get_period_factor(self.employee,
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
# 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_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component)
@ -507,23 +569,27 @@ class SalarySlip(TransactionBase):
unclaimed_taxable_benefits += current_taxable_earnings_for_payment_days.flexi_benefits
# 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 = previous_taxable_earnings + current_structured_taxable_earnings + future_structured_taxable_earnings \
+ current_additional_earnings + other_incomes + unclaimed_taxable_benefits - total_exemption_amount
# Total taxable earnings without additional earnings with full tax
total_taxable_earnings_without_full_tax_addl_components = total_taxable_earnings - current_additional_earnings_with_full_tax
# 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
# Total taxable earnings with additional earnings with full tax
full_tax_on_additional_earnings = 0.0
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
current_tax_amount = current_structured_tax_amount + full_tax_on_additional_earnings
@ -532,12 +598,30 @@ class SalarySlip(TransactionBase):
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):
taxable_earnings = frappe.db.sql("""
select sum(sd.amount)
from
`tabSalary Detail` sd join `tabSalary Slip` ss on sd.parent=ss.name
where
where
sd.parentfield='earnings'
and sd.is_tax_applicable=1
and is_flexible_benefit=0
@ -550,7 +634,28 @@ class SalarySlip(TransactionBase):
"from_date": start_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):
# find total_tax_paid, tax paid for benefit, additional_salary
@ -610,6 +715,13 @@ class SalarySlip(TransactionBase):
else:
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({
"taxable_earnings": taxable_earnings,
"additional_income": additional_income,
@ -672,40 +784,63 @@ class SalarySlip(TransactionBase):
return total_benefits_paid - total_benefits_claimed
def get_total_exemption_amount_and_other_incomes(self, payroll_period):
total_exemption_amount, other_incomes = 0, 0
if self.deduct_tax_for_unsubmitted_tax_exemption_proof:
exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["exemption_amount", "income_from_other_sources"])
if exemption_proof:
total_exemption_amount, other_incomes = exemption_proof
else:
declaration = frappe.db.get_value("Employee Tax Exemption Declaration",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["total_exemption_amount", "income_from_other_sources"])
if declaration:
total_exemption_amount, other_incomes = declaration
def get_total_exemption_amount(self, payroll_period, tax_slab):
total_exemption_amount = 0
if tax_slab.allow_tax_exemption:
if self.deduct_tax_for_unsubmitted_tax_exemption_proof:
exemption_proof = frappe.db.get_value("Employee Tax Exemption Proof Submission",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["exemption_amount"])
if exemption_proof:
total_exemption_amount = exemption_proof
else:
declaration = frappe.db.get_value("Employee Tax Exemption Declaration",
{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1},
["total_exemption_amount"])
if declaration:
total_exemption_amount = declaration
return total_exemption_amount, other_incomes
total_exemption_amount += flt(tax_slab.standard_tax_exemption_amount)
def calculate_tax_by_tax_slab(self, payroll_period, annual_taxable_earning):
payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
annual_taxable_earning -= flt(payroll_period_obj.standard_tax_exemption_amount)
return total_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.update({"annual_taxable_earning": annual_taxable_earning})
taxable_amount = 0
for slab in payroll_period_obj.taxable_salary_slabs:
tax_amount = 0
for slab in tax_slab.slabs:
if slab.condition and not self.eval_tax_slab_condition(slab.condition, data):
continue
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
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:
taxable_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01
return taxable_amount
tax_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01
# 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):
try:
@ -869,7 +1004,7 @@ class SalarySlip(TransactionBase):
if not self.salary_slip_based_on_timesheet:
self.get_date_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()
def pull_emp_details(self):
@ -878,8 +1013,8 @@ class SalarySlip(TransactionBase):
self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no
def process_salary_based_on_leave(self, lwp=0):
self.get_leave_details(lwp=lwp)
def process_salary_based_on_working_days(self):
self.get_working_days_details(lwp=self.leave_without_pay)
self.calculate_net_pay()
def unlink_ref_doc_from_salary_slip(ref_no):

View File

@ -21,18 +21,105 @@ class TestSalarySlip(unittest.TestCase):
make_earning_salary_component(setup=True, company_list=["_Test Company"])
make_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)
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("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):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
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):
no_of_days = self.get_no_of_days()
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.earnings[0].amount, 50000)
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.net_pay, 68000.0)
def test_salary_slip_with_holidays_excluded(self):
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].default_amount, 50000)
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.net_pay, 68000.0)
def test_payment_days(self):
no_of_days = self.get_no_of_days()
@ -80,8 +161,8 @@ class TestSalarySlip(unittest.TestCase):
# set joinng date in the same month
make_employee("test_employee@salary.com")
if getdate(nowdate()).day >= 15:
date_of_joining = 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:
date_of_joining = 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):
frappe.db.sql("delete from `tabEmail Queue`")
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.email_salary_slip_to_employee = 1
hr_settings.save()
frappe.db.set_value("HR Settings", None, "email_salary_slip_to_employee", 1)
make_employee("test_employee@salary.com")
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
frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""")
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")
delete_docs = [
"Salary Slip",
@ -230,8 +312,7 @@ class TestSalarySlip(unittest.TestCase):
payroll_period, deduct_random=False)
tax_paid = get_tax_paid_in_period(employee)
# total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200
annual_tax = 113568
annual_tax = 113589.0
try:
self.assertEqual(tax_paid, annual_tax)
except AssertionError:
@ -255,8 +336,7 @@ class TestSalarySlip(unittest.TestCase):
raise
# Submit proof for total 120000
data["proof-1"] = create_proof_submission(employee, payroll_period, 50000)
data["proof-2"] = create_proof_submission(employee, payroll_period, 70000)
data["proof"] = create_proof_submission(employee, payroll_period, 120000)
# Submit benefit claim for total 50000
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
try:
self.assertEqual(tax_paid, 88608)
self.assertEqual(tax_paid, 82389.0)
except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise
@ -285,7 +365,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200
tax_paid = get_tax_paid_in_period(employee)
try:
self.assertEqual(tax_paid, 121211)
self.assertEqual(tax_paid, annual_tax)
except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise
@ -322,11 +402,11 @@ class TestSalarySlip(unittest.TestCase):
return [no_of_days_in_month[1], no_of_holidays_in_month]
def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure
if not salary_structure:
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
employee = frappe.db.get_value("Employee", {"user_id": user})
salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
@ -456,17 +536,15 @@ def make_deduction_salary_component(setup=False, test_tax=False, company_list=No
{
"salary_component": 'Professional Tax',
"abbr":'PT',
"condition": 'base > 10000',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1
"amount": 200,
"exempted_from_income_tax": 1
},
{
"salary_component": 'TDS',
"abbr":'T',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1,
"depends_on_payment_days": 0,
"variable_based_on_taxable_salary": 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',
"abbr":'T',
"condition": 'employment_type=="Intern"',
"formula": 'base*.1',
"type": "Deduction",
"amount_based_on_formula": 1,
"round_to_the_nearest_integer": 1
})
if setup or test_tax:
@ -535,29 +611,47 @@ def create_benefit_claim(employee, payroll_period, amount, component):
}).submit()
return claim_date
def create_tax_slab(payroll_period):
data = [
def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False):
if frappe.db.exists("Income Tax Slab", "Tax Slab: " + payroll_period.name):
return
slabs = [
{
"from_amount": 250000,
"to_amount": 500000,
"percent_deduction": 5.2,
"percent_deduction": 5,
"condition": "annual_taxable_earning > 500000"
},
{
"from_amount": 500001,
"to_amount": 1000000,
"percent_deduction": 20.8
"percent_deduction": 20
},
{
"from_amount": 1000001,
"percent_deduction": 31.2
"percent_deduction": 30
}
]
payroll_period.taxable_salary_slabs = []
for item in data:
payroll_period.append("taxable_salary_slabs", item)
payroll_period.standard_tax_exemption_amount = 52500
payroll_period.save()
income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.name = "Tax Slab: " + payroll_period.name
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
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):
deducted_dates = []
@ -595,3 +689,17 @@ def create_additional_salary(employee, payroll_period, amount):
"type": "Earning"
}).submit()
return salary_date
def make_leave_application(employee, from_date, to_date, leave_type, company=None):
leave_application = frappe.get_doc(dict(
doctype = 'Leave Application',
employee = employee,
leave_type = leave_type,
from_date = from_date,
to_date = to_date,
company = company or erpnext.get_default_company() or "_Test Company",
docstatus = 1,
status = "Approved",
leave_approver = 'test@example.com'
))
leave_application.submit()

View File

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

View File

@ -16,6 +16,7 @@ class SalaryStructure(Document):
self.validate_amount()
self.strip_condition_and_formula_fields()
self.validate_max_benefits_with_flexi()
self.validate_component_based_on_tax_slab()
def set_missing_values(self):
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:
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):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative"))
@ -82,21 +89,23 @@ class SalaryStructure(Document):
@frappe.whitelist()
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)
if employees:
if len(employees) > 20:
frappe.enqueue(assign_salary_structure_for_employees, timeout=600,
employees=employees, salary_structure=self,from_date=from_date, base=base,variable=variable)
employees=employees, salary_structure=self,from_date=from_date,
base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
assign_salary_structure_for_employees(employees, self, from_date=from_date, base=base,variable=variable)
assign_salary_structure_for_employees(employees, self, from_date=from_date,
base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
frappe.msgprint(_("No Employee Found"))
def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None,variable=None):
def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None, variable=None, income_tax_slab=None):
salary_structures_assignments = []
existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date)
count=0
@ -105,7 +114,8 @@ def assign_salary_structure_for_employees(employees, salary_structure, from_date
continue
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)
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"))
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.employee = employee
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.base = base
assignment.variable = variable
assignment.income_tax_slab = income_tax_slab
assignment.save(ignore_permissions = True)
assignment.submit()
return assignment.name

View File

@ -9,8 +9,9 @@ from frappe.utils.make_random import get_random
from frappe.utils import nowdate, add_days, add_years, getdate, add_months
from 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,\
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_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_payroll_period
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")[2].amount, 25000)
self.assertEqual(sal_slip.get("gross_pay"), 78000)
self.assertEqual(sal_slip.get("deductions")[0].amount, 5000)
self.assertEqual(sal_slip.get("deductions")[1].amount, 5000)
self.assertEqual(sal_slip.get("total_deduction"), 10000)
self.assertEqual(sal_slip.get("net_pay"), 68000)
self.assertEqual(sal_slip.get("deductions")[0].amount, 200)
self.assertEqual(sal_slip.get("net_pay"), 78000 - sal_slip.get("total_deduction"))
def test_whitespaces_in_formula_conditions_fields(self):
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):
if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
if not frappe.db.exists('Salary Structure', salary_structure):
details = {
"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):
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:
salary_structure_doc.submit()
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):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
payroll_period = create_payroll_period()
create_tax_slab(payroll_period, allow_tax_exemption=True)
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee
salary_structure_assignment.base = 50000
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.company = company or erpnext.get_default_company()
salary_structure_assignment.save(ignore_permissions=True)
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
salary_structure_assignment.submit()
return salary_structure_assignment

View File

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

View File

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

View File

@ -1,85 +1,186 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
# Copyright (c) 2013, 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 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_summary.employee_leave_balance_summary \
import get_department_leave_approver_map
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on, get_leave_allocation_records
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)
data = get_data(filters, leave_types)
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns(leave_types):
columns = [
_("Employee") + ":Link.Employee:150",
_("Employee Name") + "::200",
_("Department") +"::150"
]
for leave_type in leave_types:
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
def get_columns():
columns = [{
'label': _('Leave Type'),
'fieldtype': 'Link',
'fieldname': 'leave_type',
'width': 200,
'options': 'Leave Type'
}, {
'label': _('Employee'),
'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': 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
def get_conditions(filters):
conditions = {
"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")})
def get_data(filters):
leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC")
return conditions
def get_data(filters, leave_types):
user = frappe.session.user
conditions = get_conditions(filters)
if filters.to_date <= filters.from_date:
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"])
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 employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, [])
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)):
row = [employee.name, employee.employee_name, employee.department]
for leave_type in leave_types:
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
for leave_type in leave_types:
# leaves taken
leaves_taken = get_leaves_for_period(employee.name, leave_type,
filters.from_date, filters.to_date) * -1
# opening balance
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)
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
# closing balance
closing = max(opening - leaves_taken, 0)
row.leaves_allocated = new_allocation
row.leaves_expired = expired_leaves - leaves_taken if expired_leaves - leaves_taken > 0 else 0
row.opening_balance = opening
row.leaves_taken = leaves_taken
row.closing_balance = closing
row.indent = 1
data.append(row)
new_leaves_allocated = 0
row += [opening, leaves_taken, closing]
data.append(row)
return data
return data
def get_conditions(filters):
conditions={
'status': 'Active',
}
if filters.get('employee'):
conditions['name'] = filters.get('employee')
if filters.get('employee'):
conditions['name'] = filters.get('employee')
return conditions
def get_department_leave_approver_map(department=None):
conditions=''
if department:
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
# get current department and all its child
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all('Department Approver', filters={
'parentfield': 'leave_approvers',
'parent': ('in', department_list)
}, fields=['parent', 'approver'], as_list=1)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers
def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type):
from frappe.utils import getdate
new_allocation = 0
expired_leaves = 0
records= frappe.db.sql("""
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name,
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
AND docstatus=1 AND leaves>0
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
""", {
"from_date": from_date,
"to_date": to_date,
"employee": employee,
"leave_type": leave_type
}, as_dict=1)
for record in records:
if record.to_date <= getdate(to_date):
expired_leaves += record.leaves
if record.from_date >= getdate(from_date):
new_allocation += record.leaves
return new_allocation, expired_leaves

View File

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

View File

@ -1,130 +1,75 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
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):
if filters.to_date <= filters.from_date:
frappe.throw(_('From date can not be greater than than To date'))
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
columns = get_columns()
data = get_data(filters)
columns = get_columns(leave_types)
data = get_data(filters, leave_types)
return columns, data
def get_columns():
columns = [{
'label': _('Leave Type'),
'fieldtype': 'Link',
'fieldname': 'leave_type',
'width': 300,
'options': 'Leave Type'
}, {
'label': _('Employee'),
'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,
}]
def get_columns(leave_types):
columns = [
_("Employee") + ":Link.Employee:150",
_("Employee Name") + "::200",
_("Department") +"::150"
]
for leave_type in leave_types:
columns.append(_(leave_type) + ":Float:160")
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):
conditions={
'status': 'Active',
conditions = {
"status": "Active",
"company": filters.company,
}
if filters.get('employee'):
conditions['name'] = filters.get('employee')
if filters.get('employee'):
conditions['name'] = filters.get('employee')
if filters.get("department"):
conditions.update({"department": filters.get("department")})
if filters.get("employee"):
conditions.update({"employee": 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}
def get_data(filters, leave_types):
user = frappe.session.user
conditions = get_conditions(filters)
# get current department and all its child
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
active_employees = frappe.get_all("Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"])
# 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)
department_approver_map = get_department_leave_approver_map(filters.get('department'))
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:
approvers.setdefault(k, []).append(v)
if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
row = [employee.name, employee.employee_name, employee.department]
return approvers
for leave_type in leave_types:
# opening balance
opening = get_leave_balance_on(employee.name, leave_type, filters.date)
row += [opening]
data.append(row)
return data

View File

@ -9,6 +9,8 @@ from frappe.model.document import Document
from frappe.desk.form import assign_to
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class DuplicateDeclarationError(frappe.ValidationError): pass
class EmployeeBoardingController(Document):
'''
Create the project and the task for the boarding process
@ -226,6 +228,17 @@ def get_employee_leave_policy(employee):
else:
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):
subcategories = []
for d in declarations:

View File

@ -84,7 +84,7 @@ class LoanDisbursement(AccountsController):
gle_map.append(
self.get_gl_dict({
"account": loan_details.loan_account,
"against": loan_details.applicant,
"against": loan_details.payment_account,
"debit": self.disbursed_amount,
"debit_in_account_currency": self.disbursed_amount,
"against_voucher_type": "Loan",
@ -100,7 +100,7 @@ class LoanDisbursement(AccountsController):
gle_map.append(
self.get_gl_dict({
"account": loan_details.payment_account,
"against": loan_details.applicant,
"against": loan_details.loan_account,
"credit": self.disbursed_amount,
"credit_in_account_currency": self.disbursed_amount,
"against_voucher_type": "Loan",

View File

@ -193,7 +193,7 @@ class BOM(WebsiteGenerator):
if self.rm_cost_as_per == 'Valuation Rate':
rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
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")) \
* (arg.get("conversion_factor") or 1)
elif self.rm_cost_as_per == "Price List":

View File

@ -346,6 +346,7 @@ class ProductionPlan(Document):
if not wo.fg_warehouse:
wo.fg_warehouse = warehouse.get('fg_warehouse')
try:
wo.flags.ignore_mandatory = True
wo.insert()
return wo.name
except OverProductionError:

View File

@ -313,7 +313,7 @@ frappe.ui.form.on("Work Order", {
"Work in Progress": "progress-bar-warning",
"Completed": "progress-bar-success"
};
let bars = [];
let message = '';
let title = '';
@ -404,7 +404,6 @@ frappe.ui.form.on("Work Order", {
},
before_submit: function(frm) {
frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true);
frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true);
frm.toggle_reqd("transfer_material_against",
frm.doc.operations && frm.doc.operations.length > 0);

View File

@ -231,6 +231,7 @@
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"label": "Work-in-Progress Warehouse",
"mandatory_depends_on": "eval:!doc.skip_transfer || doc.from_wip_warehouse",
"options": "Warehouse"
},
{
@ -238,7 +239,8 @@
"fieldname": "fg_warehouse",
"fieldtype": "Link",
"label": "Target Warehouse",
"options": "Warehouse"
"options": "Warehouse",
"reqd": 1
},
{
"fieldname": "column_break_12",
@ -481,7 +483,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2020-01-31 12:46:23.636033",
"modified": "2020-04-24 19:32:43.323054",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@ -661,6 +661,7 @@ erpnext.patches.v12_0.set_job_offer_applicant_email
erpnext.patches.v12_0.create_irs_1099_field_united_states
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
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.rename_account_type_doctype
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.rename_mws_settings_fields
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.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

View File

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

View File

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

View File

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

View File

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

View File

View File

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

View File

@ -288,7 +288,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.setup_sms();
this.setup_quality_inspection();
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_new_description("");
@ -1412,7 +1412,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.frm.doc.items.forEach(d => {
if (in_list(data.apply_rule_on_other_items, d[data.apply_rule_on])) {
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]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -61,7 +61,7 @@ def add_custom_roles_for_reports():
)).insert()
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)
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
add_permission(doctype, role, 0)
@ -531,12 +531,18 @@ def make_fixtures(company=None):
def set_salary_components(docs):
docs.extend([
{'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'},
{'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'},
{'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'},
{'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'},
{'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'},
{'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'}
{'doctype': 'Salary Component', 'salary_component': 'Professional Tax',
'description': 'Professional Tax', 'type': 'Deduction', 'exempted_from_income_tax': 1},
{'doctype': 'Salary Component', 'salary_component': 'Provident Fund',
'description': 'Provident fund', 'type': 'Deduction', 'is_tax_applicable': 1},
{'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance',
'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):

View File

@ -8,6 +8,10 @@ def get_data():
{
'label': _('Email Campaigns'),
'items': ['Email Campaign']
},
{
'label': _('Social Media Campaigns'),
'items': ['Social Media Post']
}
],
]
}

View File

@ -148,9 +148,14 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
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
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(__('Work Order'), () => this.make_work_order(), __('Create'));
}
@ -161,8 +166,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}
// material request
if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1
&& flt(doc.per_delivered, 6) < 100) {
if(!doc.order_type || (order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 6) < 100) {
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'));
}
@ -171,14 +175,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create'));
// maintenance
if(flt(doc.per_delivered, 2) < 100 &&
["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
if(flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
this.frm.add_custom_button(__('Maintenance Visit'), () => this.make_maintenance_visit(), __('Create'));
this.frm.add_custom_button(__('Maintenance Schedule'), () => this.make_maintenance_schedule(), __('Create'));
}
// 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'));
}

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