Merge pull request #6101 from bcornwellmott/ppt_test2
Upgraded PPT for more variety of MR creation options. Added tests
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index c433ddb..7c5133a 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
import frappe
-__version__ = '7.0.28'
+__version__ = '7.0.31'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json
index 4946304..d9464a5 100644
--- a/erpnext/accounts/doctype/budget/budget.json
+++ b/erpnext/accounts/doctype/budget/budget.json
@@ -8,6 +8,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
+ "editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
@@ -65,24 +66,24 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "monthly_distribution",
+ "fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Monthly Distribution",
+ "label": "Company",
"length": 0,
"no_copy": 0,
- "options": "Monthly Distribution",
+ "options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -170,24 +171,25 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "company",
+ "depends_on": "eval:in_list([\"Stop\", \"Warn\"], doc.action_if_accumulated_monthly_budget_exceeded)",
+ "fieldname": "monthly_distribution",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Company",
+ "label": "Monthly Distribution",
"length": 0,
"no_copy": 0,
- "options": "Company",
+ "options": "Monthly Distribution",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
@@ -271,13 +273,14 @@
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
+ "image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-05-16 15:00:40.233685",
+ "modified": "2016-08-18 14:46:02.653081",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 819a635..529401c 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, getdate, add_months, get_last_day
+from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money
from frappe.model.naming import make_autoname
from frappe.model.document import Document
@@ -73,25 +73,30 @@
args.posting_date, args.fiscal_year, budget.budget_amount)
args["month_end_date"] = get_last_day(args.posting_date)
-
+
compare_expense_with_budget(args, budget.cost_center,
budget_amount, _("Accumulated Monthly"), monthly_action)
- elif yearly_action in ["Stop", "Warn"]:
- compare_expense_with_budget(args, budget.cost_center,
- flt(budget.budget_amount), _("Annual"), yearly_action)
+ if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
+ and yearly_action != monthly_action:
+ compare_expense_with_budget(args, budget.cost_center,
+ flt(budget.budget_amount), _("Annual"), yearly_action)
def compare_expense_with_budget(args, cost_center, budget_amount, action_for, action):
actual_expense = get_actual_expense(args, cost_center)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
+ currency = frappe.db.get_value('Company', frappe.db.get_value('Cost Center',
+ cost_center, 'company'), 'default_currency')
- msg = _("{0} Budget for Account {1} against Cost Center {2} is {3}. It will exceed by {4}").format(_(action_for), args.account, cost_center, budget_amount, diff)
+ msg = _("{0} Budget for Account {1} against Cost Center {2} is {3}. It will exceed by {4}").format(_(action_for),
+ frappe.bold(args.account), frappe.bold(cost_center),
+ frappe.bold(fmt_money(budget_amount, currency=currency)), frappe.bold(fmt_money(diff, currency=currency)))
if action=="Stop":
frappe.throw(msg, BudgetError)
else:
- frappe.msgprint(msg)
+ frappe.msgprint(msg, indicator='orange')
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {}
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index dea87f3..83ecc9f 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -32,8 +32,13 @@
cur_frm.toggle_display('sb1', doc.is_group==0)
cur_frm.set_intro(intro_txt);
- cur_frm.add_custom_button(__('Chart of Cost Centers'),
- function() { frappe.set_route("Tree", "Cost Center"); }, __("View"))
+ if(!cur_frm.doc.__islocal) {
+ cur_frm.add_custom_button(__('Chart of Cost Centers'),
+ function() { frappe.set_route("Tree", "Cost Center"); });
+
+ cur_frm.add_custom_button(__('Budget'),
+ function() { frappe.set_route("List", "Budget", {'cost_center': cur_frm.doc.name}); });
+ }
}
cur_frm.cscript.parent_cost_center = function(doc, cdt, cdn) {
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index cb637d2..01a87c4 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -122,6 +122,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "campaign",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Campaign",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Campaign",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"hidden": 0,
@@ -694,6 +720,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "account_for_change_amount",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Account for Change Amount",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "taxes_and_charges",
"fieldtype": "Link",
"hidden": 0,
@@ -833,7 +885,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-08-06 17:05:59.990031",
+ "modified": "2016-08-17 15:12:56.713748",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index c655626..c51231e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -232,7 +232,8 @@
cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(doc, cdt, cdn) {
return {
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_purchase_item': 1}
}
}
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
index 433cda7..a1d8bf8 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
@@ -18,3 +18,13 @@
}
refresh_field('add_deduct_tax', d.name, 'taxes');
});
+
+frappe.ui.form.on("Purchase Taxes and Charges", "category", function(doc, cdt, cdn) {
+ var d = locals[cdt][cdn];
+
+ if (d.category != 'Total' && d.add_deduct_tax == 'Deduct') {
+ msgprint(__("Cannot deduct when category is for 'Valuation' or 'Vaulation and Total'"));
+ d.add_deduct_tax = '';
+ }
+ refresh_field('add_deduct_tax', d.name, 'taxes');
+});
diff --git a/erpnext/accounts/doctype/salary_component_account/__init__.py b/erpnext/accounts/doctype/salary_component_account/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/salary_component_account/__init__.py
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
new file mode 100644
index 0000000..6bbde22
--- /dev/null
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
@@ -0,0 +1,90 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2016-07-27 17:24:24.956896",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "description": "Default Bank / Cash account will be automatically updated in POS Invoice when this mode is selected.",
+ "fieldname": "default_account",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Default Account",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2016-07-27 17:24:24.956896",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Salary Component Account",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.py b/erpnext/accounts/doctype/salary_component_account/salary_component_account.py
new file mode 100644
index 0000000..983d015
--- /dev/null
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, 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 SalaryComponentAccount(Document):
+ pass
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 33032bf..599e9b1 100644
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -13,9 +13,9 @@
@frappe.whitelist()
def get_pos_data():
doc = frappe.new_doc('Sales Invoice')
- doc.update_stock = 1;
doc.is_pos = 1;
pos_profile = get_pos_profile(doc.company) or {}
+ doc.update_stock = pos_profile.get('update_stock')
if pos_profile.get('name'):
pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name'))
@@ -35,7 +35,6 @@
'customers': get_customers(pos_profile, doc),
'pricing_rules': get_pricing_rules(doc),
'print_template': print_template,
- 'write_off_account': pos_profile.get('write_off_account'),
'meta': {
'invoice': frappe.get_meta('Sales Invoice'),
'items': frappe.get_meta('Sales Invoice Item'),
@@ -45,7 +44,12 @@
def update_pos_profile_data(doc, pos_profile):
company_data = frappe.db.get_value('Company', doc.company, '*', as_dict=1)
+ doc.campaign = pos_profile.get('campaign')
+ doc.write_off_account = pos_profile.get('write_off_account') or \
+ company_data.write_off_account
+ doc.change_amount_account = pos_profile.get('change_amount_account') or \
+ company_data.default_cash_account
doc.taxes_and_charges = pos_profile.get('taxes_and_charges')
if doc.taxes_and_charges:
update_tax_table(doc)
@@ -54,7 +58,8 @@
doc.conversion_rate = 1.0
if doc.currency != company_data.default_currency:
doc.conversion_rate = get_exchange_rate(doc.currency, company_data.default_currency)
- doc.selling_price_list = pos_profile.get('selling_price_list') or frappe.db.get_value('Selling Settings', None, 'selling_price_list')
+ doc.selling_price_list = pos_profile.get('selling_price_list') or \
+ frappe.db.get_value('Selling Settings', None, 'selling_price_list')
doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head
doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0
@@ -100,7 +105,7 @@
def get_items(doc, pos_profile):
item_list = []
- for item in frappe.get_all("Item", fields=["*"], filters={'disabled': 0, 'has_variants': 0}):
+ for item in frappe.get_all("Item", fields=["*"], filters={'disabled': 0, 'has_variants': 0, 'is_sales_item': 1}):
item_doc = frappe.get_doc('Item', item.name)
if item_doc.taxes:
item.taxes = json.dumps(dict(([d.tax_type, d.tax_rate] for d in
@@ -157,10 +162,11 @@
def get_pricing_rules(doc):
pricing_rules = ""
if doc.ignore_pricing_rule == 0:
- pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2 and disable = 0
- and selling = 1 and ifnull(company, '') in (%(company)s, '') and
- ifnull(for_price_list, '') in (%(price_list)s, '') and %(date)s between
- ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') order by priority desc, name desc""",
+ pricing_rules = frappe.db.sql(""" Select * from `tabPricing Rule` where docstatus < 2
+ and ifnull(for_price_list, '') in (%(price_list)s, '') and selling = 1
+ and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s
+ between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
+ order by priority desc, name desc""",
{'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
return pricing_rules
@@ -173,9 +179,9 @@
for docs in doc_list:
for name, doc in docs.items():
- if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):
- validate_customer(doc)
- validate_item(doc)
+ if not frappe.db.exists('Sales Invoice',
+ {'offline_pos_name': name, 'docstatus': ("<", "2")}):
+ validate_records(doc)
si_doc = frappe.new_doc('Sales Invoice')
si_doc.offline_pos_name = name
si_doc.update(doc)
@@ -186,6 +192,10 @@
return name_list
+def validate_records(doc):
+ validate_customer(doc)
+ validate_item(doc)
+
def validate_customer(doc):
if not frappe.db.exists('Customer', doc.get('customer')):
customer_doc = frappe.new_doc('Customer')
@@ -197,8 +207,6 @@
frappe.db.commit()
doc['customer'] = customer_doc.name
- return doc
-
def validate_item(doc):
for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b73673f..3245e78 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -267,6 +267,16 @@
amount: function(){
this.write_off_outstanding_amount_automatically()
+ },
+
+ change_amount: function(){
+ if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
+ this.calculate_write_off_amount()
+ }else {
+ this.frm.set_value("change_amount", 0.0)
+ }
+
+ this.frm.refresh_fields();
}
});
@@ -458,7 +468,7 @@
]
}
}
- },
+ }
})
frappe.ui.form.on('Sales Invoice Timesheet', {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 06bc635..a2e930c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -2204,32 +2204,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "base_change_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Base Change Amount (Company Currency)",
- "length": 0,
- "no_copy": 1,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "column_break_86",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2282,6 +2256,80 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "section_break_88",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "base_change_amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Base Change Amount (Company Currency)",
+ "length": 0,
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "column_break_90",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "change_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -2297,7 +2345,33 @@
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
- "read_only": 1,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "account_for_change_amount",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Account for Change Amount",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Account",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@@ -3677,7 +3751,7 @@
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2016-08-03 11:50:49.680278",
+ "modified": "2016-08-17 15:12:39.357372",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f055140..871a151 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -61,6 +61,7 @@
self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.add_remarks()
self.validate_write_off_account()
+ self.validate_account_for_change_amount()
self.validate_fixed_asset()
self.set_income_account_for_fixed_assets()
@@ -233,12 +234,22 @@
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
pos = get_pos_profile(self.company)
+ if not self.get('payments'):
+ pos_profile = frappe.get_doc('POS Profile', pos.name) if pos else None
+ update_multi_mode_option(self, pos_profile)
+
+ if not self.account_for_change_amount:
+ self.account_for_change_amount = frappe.db.get_value('Company', self.company, 'default_cash_account')
+
if pos:
if not for_validate and not self.customer:
self.customer = pos.customer
self.mode_of_payment = pos.mode_of_payment
# self.set_customer_defaults()
+ if pos.get('account_for_change_amount'):
+ self.account_for_change_amount = pos.get('account_for_change_amount')
+
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account',
'write_off_account', 'write_off_cost_center'):
@@ -265,10 +276,6 @@
if self.taxes_and_charges and not len(self.get("taxes")):
self.set_taxes()
- if not self.get('payments'):
- pos_profile = frappe.get_doc('POS Profile', pos.name)
- update_multi_mode_option(self, pos_profile)
-
return pos
def get_company_abbr(self):
@@ -379,6 +386,9 @@
if flt(self.write_off_amount) and not self.write_off_account:
msgprint(_("Please enter Write Off Account"), raise_exception=1)
+ def validate_account_for_change_amount(self):
+ if flt(self.change_amount) and not self.account_for_change_amount:
+ msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
def validate_c_form(self):
""" Blank C-form no if C-form applicable marked as 'No'"""
@@ -502,7 +512,7 @@
gl_entries = merge_similar_entries(gl_entries)
self.make_pos_gl_entries(gl_entries)
- self.make_gle_for_change(gl_entries)
+ self.make_gle_for_change_amount(gl_entries)
self.make_write_off_gl_entry(gl_entries)
@@ -606,16 +616,15 @@
}, payment_mode_account_currency)
)
- def make_gle_for_change(self, gl_entries):
+ def make_gle_for_change_amount(self, gl_entries):
if cint(self.is_pos) and self.change_amount:
- cash_account = self.get_cash_account()
- if cash_account:
+ if self.account_for_change_amount:
gl_entries.append(
self.get_gl_dict({
"account": self.debit_to,
"party_type": "Customer",
"party": self.customer,
- "against": cash_account,
+ "against": self.account_for_change_amount,
"debit": flt(self.base_change_amount),
"debit_in_account_currency": flt(self.base_change_amount) \
if self.party_account_currency==self.company_currency else flt(self.change_amount),
@@ -626,22 +635,13 @@
gl_entries.append(
self.get_gl_dict({
- "account": cash_account,
+ "account": self.account_for_change_amount,
"against": self.customer,
"credit": self.base_change_amount
})
)
-
-
- def get_cash_account(self):
- cash_account = [d.account for d in self.payments if d.type=="Cash"]
- if cash_account:
- cash_account = cash_account[0]
- else:
- cash_account = frappe.db.get_value("Account",
- filters={"company": self.company, "account_type": "Cash", "is_group": 0})
-
- return cash_account
+ else:
+ frappe.throw(_("Select change amount account"), title="Mandatory Field")
def make_write_off_gl_entry(self, gl_entries):
# write off entries, applicable if only pos
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index dd28059..1bb7b1c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -455,6 +455,25 @@
self.pos_gl_entry(si, pos, 300)
+ def test_pos_change_amount(self):
+ set_perpetual_inventory()
+ self.make_pos_profile()
+
+ self._insert_purchase_receipt()
+ pos = copy.deepcopy(test_records[1])
+ pos["is_pos"] = 1
+ pos["update_stock"] = 1
+ pos["payments"] = [{'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 300},
+ {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 340}]
+
+ si = frappe.copy_doc(pos)
+ si.change_amount = 5.0
+ si.insert()
+ si.submit()
+
+ self.assertEquals(si.grand_total, 630.0)
+ self.assertEquals(si.write_off_amount, -5)
+
def test_make_pos_invoice(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index 0d3e019..06606a9 100644
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -183,6 +183,14 @@
if(this.frm.doc.customer){
this.party_field.$input.val(this.frm.doc.customer);
}
+
+ if(!this.frm.doc.write_off_account){
+ this.frm.doc.write_off_account = doc.write_off_account
+ }
+
+ if(!this.frm.doc.account_for_change_amount){
+ this.frm.doc.account_for_change_amount = doc.account_for_change_amount
+ }
},
get_invoice_doc: function(si_docs){
@@ -209,7 +217,6 @@
window.meta = r.message.meta;
window.print_template = r.message.print_template;
me.default_customer = r.message.default_customer || null;
- me.write_off_account = r.message.write_off_account;
localStorage.setItem('doc', JSON.stringify(r.message.doc));
if(callback){
callback();
@@ -485,11 +492,8 @@
this.remove_item = []
$.each(this.frm.doc["items"] || [], function(i, d) {
- if (d.item_code == item_code && d.serial_no
- && field == 'qty' && cint(value) != value) {
- d.qty = 0.0;
- me.refresh();
- frappe.throw(__("Serial no item cannot be a fraction"))
+ if(d.serial_no){
+ me.validate_serial_no_qty(d, item_code, field, value)
}
if (d.item_code == item_code) {
@@ -723,49 +727,6 @@
}, 1000)
},
- write_off_amount: function(){
- var me = this;
- var value = 0.0;
-
- if(this.frm.doc.outstanding_amount > 0){
- dialog = new frappe.ui.Dialog({
- title: 'Write Off Amount',
- fields: [
- {fieldtype: "Check", fieldname: "write_off_amount", label: __("Write off Outstanding Amount")},
- {fieldtype: "Link", options:"Account", default:this.write_off_account, fieldname: "write_off_account",
- label: __("Write off Account"), get_query: function() {
- return {
- filters: {'is_group': 0, 'report_type': 'Profit and Loss'}
- }
- }}
- ]
- });
-
- dialog.show();
-
- dialog.fields_dict.write_off_amount.$input.change(function(){
- write_off_amount = dialog.get_values().write_off_amount;
- me.frm.doc.write_off_outstanding_amount_automatically = write_off_amount;
- me.frm.doc.base_write_off_amount = (write_off_amount==1) ? flt(me.frm.doc.grand_total - me.frm.doc.paid_amount, precision("outstanding_amount")) : 0;
- me.frm.doc.write_off_account = (write_off_amount==1) ? dialog.get_values().write_off_account : '';
- me.frm.doc.write_off_amount = flt(me.frm.doc.base_write_off_amount * me.frm.doc.conversion_rate, precision("write_off_amount"))
- me.calculate_outstanding_amount();
- me.set_primary_action();
- })
-
- dialog.fields_dict.write_off_account.$input.change(function(){
- me.frm.doc.write_off_account = dialog.get_values().write_off_account;
- })
-
- dialog.set_primary_action(__("Submit"), function(){
- dialog.hide()
- me.submit_invoice()
- })
- }else{
- this.submit_invoice()
- }
- },
-
submit_invoice: function(){
var me = this;
frappe.confirm(__("Do you really want to submit the invoice?"), function () {
@@ -951,6 +912,23 @@
}
},
+ validate_serial_no_qty: function(args, item_code, field, value){
+ var me = this;
+ if (args.item_code == item_code && args.serial_no
+ && field == 'qty' && cint(value) != value) {
+ args.qty = 0.0;
+ this.refresh();
+ frappe.throw(__("Serial no item cannot be a fraction"))
+ }
+
+ if(args.serial_no && args.serial_no.split('\n').length != cint(value)){
+ args.qty = 0.0;
+ args.serial_no = ''
+ this.refresh();
+ frappe.throw(__("Total nos of serial no is not equal to quantity."))
+ }
+ },
+
mandatory_batch_no: function(){
var me = this;
if(this.items[0].has_batch_no && !this.item_batch_no[this.items[0].item_code]){
@@ -978,11 +956,13 @@
get_pricing_rule: function(item){
var me = this;
return $.grep(this.pricing_rules, function(data){
- if(data.item_code == item.item_code || in_list(['All Item Groups', item.item_group], data.item_group)) {
- if(in_list(['Customer', 'Customer Group', 'Territory'], data.applicable_for)){
- return me.validate_condition(data)
- }else{
- return true
+ if(item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty)) ){
+ if(data.item_code == item.item_code || in_list(['All Item Groups', item.item_group], data.item_group)) {
+ if(in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)){
+ return me.validate_condition(data)
+ }else{
+ return true
+ }
}
}
})
@@ -1001,6 +981,7 @@
'Customer': [data.customer, [this.frm.doc.customer]],
'Customer Group': [data.customer_group, [this.frm.doc.customer_group, 'All Customer Groups']],
'Territory': [data.territory, [this.frm.doc.territory, 'All Territories']],
+ 'Campaign': [data.campaign, [this.frm.doc.campaign]],
}
},
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 843937f..ed429d6 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -66,7 +66,8 @@
columns.append({
"fieldname": "currency",
"label": _("Currency"),
- "fieldtype": "Data",
+ "fieldtype": "Link",
+ "options": "Currency",
"width": 100
})
if args.get("party_type") == "Customer":
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 36e9734..8c627b6 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -34,7 +34,8 @@
columns.append({
"fieldname": "currency",
"label": _("Currency"),
- "fieldtype": "Data",
+ "fieldtype": "Link",
+ "options": "Currency",
"width": 80
})
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index aa0232b..a052821 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -6,6 +6,7 @@
from frappe import _, scrub
from frappe.utils import flt
+
def execute(filters=None):
if not filters: filters = frappe._dict()
company_currency = frappe.db.get_value("Company", filters.company, "default_currency")
@@ -188,8 +189,8 @@
# stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc
if item_code in self.non_stock_items:
- # average purchasing rate for non-stock items
- item_rate = self.get_average_buying_rate(item_code)
+ #Issue 6089-Get last purchasing rate for non-stock item
+ item_rate = self.get_last_purchase_rate(item_code)
return flt(row.qty) * item_rate
else:
@@ -225,6 +226,22 @@
return self.average_buying_rate[item_code]
+ def get_last_purchase_rate(self, item_code):
+ if self.filters.to_date:
+ last_purchase_rate = frappe.db.sql("""
+ select (a.base_rate / a.conversion_factor)
+ from `tabPurchase Invoice Item` a
+ where a.item_code = %s and a.docstatus=1
+ and modified <= %s
+ order by a.modified desc limit 1""", (item_code,self.filters.to_date))
+ else:
+ last_purchase_rate = frappe.db.sql("""
+ select (a.base_rate / a.conversion_factor)
+ from `tabPurchase Invoice Item` a
+ where a.item_code = %s and a.docstatus=1
+ order by a.modified desc limit 1""", item_code)
+ return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
+
def load_invoice_items(self):
conditions = ""
if self.filters.company:
diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js
index 2e76516..6e27546 100644
--- a/erpnext/buying/doctype/purchase_common/purchase_common.js
+++ b/erpnext/buying/doctype/purchase_common/purchase_common.js
@@ -79,7 +79,8 @@
}
} else {
return{
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_purchase_item': 1}
}
}
});
diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
index 45bc738..0f44baa 100644
--- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
+++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js
@@ -11,5 +11,72 @@
"default": ""
}
- ]
+ ],
+ onload: function(report) {
+ //Create a button for setting the default supplier
+ report.page.add_inner_button(__("Select Default Supplier"), function() {
+
+ var reporter = frappe.query_reports["Quoted Item Comparison"];
+
+ //Always make a new one so that the latest values get updated
+ reporter.make_default_supplier_dialog(report);
+ report.dialog.show();
+ setTimeout(function() { report.dialog.input.focus(); }, 1000);
+
+ }, 'Tools');
+
+ },
+ "make_default_supplier_dialog": function (report) {
+ //Get the name of the item to change
+ var filters = report.get_values();
+ var item_code = filters.item;
+
+ //Get a list of the suppliers (with a blank as well) for the user to select
+ var select_options = "";
+ for (let supplier of report.data)
+ {
+ select_options += supplier.supplier_name+ '\n'
+ }
+
+ //Create a dialog window for the user to pick their supplier
+ var d = new frappe.ui.Dialog({
+ title: __('Select Default Supplier'),
+ fields: [
+ {fieldname: 'supplier', fieldtype:'Select', label:'Supplier', reqd:1,options:select_options},
+ {fieldname: 'ok_button', fieldtype:'Button', label:'Set Default Supplier'},
+ ]
+ });
+
+ //On the user clicking the ok button
+ d.fields_dict.ok_button.input.onclick = function() {
+ var btn = d.fields_dict.ok_button.input;
+ var v = report.dialog.get_values();
+ if(v) {
+ $(btn).set_working();
+
+ //Set the default_supplier field of the appropriate Item to the selected supplier
+ frappe.call({
+ method: "frappe.client.set_value",
+ args: {
+ doctype: "Item",
+ name: item_code,
+ fieldname: "default_supplier",
+ value: v.supplier,
+ },
+ callback: function (r){
+ $(btn).done_working();
+ msgprint("Successfully Set Supplier");
+ report.dialog.hide();
+
+ }
+ });
+ }
+ }
+ report.dialog = d;
+
+
+ }
+
}
+
+
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 28b2f7c..125f4fe 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -440,6 +440,7 @@
paid_amount = self.doc.paid_amount \
if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
+ self.calculate_write_off_amount()
self.calculate_change_amount()
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) +
@@ -468,6 +469,12 @@
self.doc.base_change_amount = flt(self.doc.change_amount * self.doc.conversion_rate,
self.doc.precision("base_change_amount"))
+ def calculate_write_off_amount(self):
+ if flt(self.doc.change_amount) > 0:
+ self.doc.write_off_amount = self.doc.grand_total - self.doc.paid_amount + self.doc.change_amount
+ self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
+ self.doc.precision("base_write_off_amount"))
+
def calculate_margin(self, item):
total_margin = 0.0
if item.price_list_rate:
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 21a7429..c9f1ffb 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -51,7 +51,8 @@
this.frm.set_query("item_code", "items", function() {
return {
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_sales_item': 1}
};
});
diff --git a/erpnext/demo/setup_data.py b/erpnext/demo/setup_data.py
index 1be2cac..97dc894 100644
--- a/erpnext/demo/setup_data.py
+++ b/erpnext/demo/setup_data.py
@@ -290,9 +290,13 @@
def setup_salary_structure():
f = frappe.get_doc('Fiscal Year', frappe.defaults.get_global_default('fiscal_year'))
+ ss = frappe.new_doc('Salary Structure')
+ ss.name = "Sample Salary Structure - " + str(f.year_start_date)
for e in frappe.get_all('Employee', fields=['name', 'date_of_joining']):
- ss = frappe.new_doc('Salary Structure')
- ss.employee = e.name
+ ss.append('employees', {
+ 'employee': e.name,
+ 'base': random.random() * 10000
+ })
if not e.date_of_joining:
continue
@@ -300,16 +304,24 @@
ss.from_date = e.date_of_joining if (e.date_of_joining
and e.date_of_joining > f.year_start_date) else f.year_start_date
ss.to_date = f.year_end_date
- ss.append('earnings', {
- 'salary_component': 'Basic',
- 'amount': random.random() * 10000
- })
- ss.append('deductions', {
- 'salary_component': 'Income Tax',
- 'amount': random.random() * 1000
- })
- ss.insert()
+ ss.append('earnings', {
+ 'salary_component': 'Basic',
+ "abbr":'B',
+ 'condition': 'base > 5000',
+ 'formula': 'base*.2',
+ 'amount_based_on_formula': 1,
+ "idx": 1
+ })
+ ss.append('deductions', {
+ 'salary_component': 'Income Tax',
+ "abbr":'IT',
+ 'condition': 'base > 5000',
+ 'amount': random.random() * 1000,
+ "idx": 1
+ })
+
+ ss.insert()
def setup_salary_structure_for_timesheet():
for e in frappe.get_all('Salary Structure', fields=['name'], filters={'is_active': 'Yes'}, limit=2):
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
index 3d5ac83..73f330d 100644
--- a/erpnext/demo/user/hr.py
+++ b/erpnext/demo/user/hr.py
@@ -92,13 +92,20 @@
expense.sanctioned_amount = sanctioned_amount
def get_timesheet_based_salary_slip_employee():
- return frappe.get_all('Salary Structure', fields = ["distinct employee as name"],
- filters = {'salary_slip_based_on_timesheet': 1})
+ sal_struct = frappe.db.sql("""
+ select name from `tabSalary Structure`
+ where salary_slip_based_on_timesheet = 1
+ and docstatus != 2""")
+ if sal_struct:
+ employees = frappe.db.sql("""
+ select employee from `tabSalary Structure Employee`
+ where parent IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True)
+ return employees
def make_timesheet_records():
employees = get_timesheet_based_salary_slip_employee()
- for employee in employees:
- ts = make_timesheet(employee.name, simulate = True, billable = 1, activity_type=get_random("Activity Type"))
+ for e in employees:
+ ts = make_timesheet(e.employee, simulate = True, billable = 1, activity_type=get_random("Activity Type"))
rand = random.random()
if rand >= 0.3:
diff --git a/erpnext/docs/assets/img/human-resources/condition-amount.png b/erpnext/docs/assets/img/human-resources/condition-amount.png
new file mode 100644
index 0000000..af95a11
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/condition-amount.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/condition-formula.png b/erpnext/docs/assets/img/human-resources/condition-formula.png
new file mode 100644
index 0000000..918d21c
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/condition-formula.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/salary-structure.png b/erpnext/docs/assets/img/human-resources/salary-structure.png
index af850ee..a135092 100644
--- a/erpnext/docs/assets/img/human-resources/salary-structure.png
+++ b/erpnext/docs/assets/img/human-resources/salary-structure.png
Binary files differ
diff --git a/erpnext/docs/assets/img/human-resources/salary-timesheet.png b/erpnext/docs/assets/img/human-resources/salary-timesheet.png
new file mode 100644
index 0000000..c18a1b7
--- /dev/null
+++ b/erpnext/docs/assets/img/human-resources/salary-timesheet.png
Binary files differ
diff --git a/erpnext/docs/user/manual/en/CRM/index.md b/erpnext/docs/user/manual/en/CRM/index.md
index e394333..2cc7e98 100644
--- a/erpnext/docs/user/manual/en/CRM/index.md
+++ b/erpnext/docs/user/manual/en/CRM/index.md
@@ -1,4 +1,3 @@
-#CRM
# CRM
ERPNext helps you track business **Opportunities** from **Leads** and
diff --git a/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md b/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
index 05e01e1..62933df 100644
--- a/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
+++ b/erpnext/docs/user/manual/en/human-resources/salary-and-payroll.md
@@ -42,15 +42,52 @@
> Human Resources > Setup > Salary Structure > New Salary Structure
-#### Figure 1:Salary Structure
+#### Figure 1.1:Salary Structure
<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/salary-structure.png">
### In the Salary Structure,
- * Select the Employee
+ * Select the Employees and enter Base (which is base salary or CTC) and Variable (if applicable)
* Set the starting date from which this is valid (Note: There can only be one Salary Structure that can be “Active” for an Employee during any period)
- * In the “Earnings” and “Deductions” table all your defined Earning Type and Deductions Type will be auto-populated. Set the values of the Earnings and Deductions and save the Salary Structure.
+
+#### Figure 1.2:Salary Structure for Salary Slip based on Timesheet
+
+<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/salary-timesheet.png">
+
+### Salary Slip Based on Timesheet
+
+Salary Slip based on Timesheet is applicable if you have timesheet based payroll system
+
+ * Check "Salary Slip Based on Timesheet"
+ * Select the salary component and enter Hour Rate (Note: This salary component gets added to earnings in Salary Slip)
+
+### Earnings and Deductions in Salary Structure
+
+In the “Earnings” and “Deductions” tables, you can calculate the values of Salary Components based on,
+
+ * Condition and Formula
+
+#### Figure 1.3:Condition and Formula
+
+<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/condition-formula.png">
+
+ * Condition and Amount
+
+#### Figure 1.4:Condition and Amount
+
+<img class="screenshot" alt="Salary Structure" src="{{docs_base_url}}/assets/img/human-resources/condition-amount.png">
+
+ * Only Formula
+ * Only Amount
+
+Save the Salary Structure.
+
+In conditions and formulas,
+
+ * Use field "base" for using base salary of the Employee
+ * Use Salary Component abbreviations. For example: BS for Basic Salary
+ * Use field name for employee details. For example: Employment Type for employment_type
### Leave Without Pay (LWP)
@@ -64,6 +101,7 @@
If you don’t want ERPNext to manage LWP, just don’t click on LWP in any of the
Earning Types and Deduction Types.
+
* * *
### Creating Salary Slips
@@ -71,8 +109,9 @@
Once the Salary Structure is created, you can make a salary slip from the same
form or you can process your payroll for the month using Process Payroll.
-To create a salary slip from Salary Structure, click on the button Make Salary
-Slip.
+To create a new Salary Slip go to:
+
+> Human Resources > Setup > Salary Slip > New Salary Slip
#### Figure 2: Salary Slip
diff --git a/erpnext/docs/user/manual/en/setting-up/email/email-alerts.md b/erpnext/docs/user/manual/en/setting-up/email/email-alerts.md
index 4caffbc..3fbb4e4 100644
--- a/erpnext/docs/user/manual/en/setting-up/email/email-alerts.md
+++ b/erpnext/docs/user/manual/en/setting-up/email/email-alerts.md
@@ -30,7 +30,7 @@
### Setting a Subject
-You can retrieve the data for a particular field by using `doc.[field_name]`. To use it in your subject / message, you have to surround it with `{{ }}`. These are called [Jinja](http://jinja.pocoo.org/) tags. So, for example to get the name of a document, you use `{{ doc.name }}`. The below example sends an email on saving a Task with the Subject, "TASK##### has been created"
+You can retrieve the data for a particular field by using `doc.[field_name]`. To use it in your subject / message, you have to surround it with `{% raw %}{{ }}{% endraw %}`. These are called [Jinja](http://jinja.pocoo.org/) tags. So, for example to get the name of a document, you use `{% raw %}{{ doc.name }}{% endraw %}`. The below example sends an email on saving a Task with the Subject, "TASK##### has been created"
<img class="screenshot" alt="Setting Subject" src="{{docs_base_url}}/assets/img/setup/email/email-alert-subject.png">
@@ -44,7 +44,7 @@
### Setting a Message
-You can use both Jinja Tags (`{{ doc.[field_name] }}`) and HTML tags in the message textbox.
+You can use both Jinja Tags (`{% raw %}{{ doc.[field_name] }}{% endraw %}`) and HTML tags in the message textbox.
{% raw %}<h3>Order Overdue</h3>
diff --git a/erpnext/docs/user/manual/index.md b/erpnext/docs/user/manual/index.md
index a2db1ba..d6cf4f1 100644
--- a/erpnext/docs/user/manual/index.md
+++ b/erpnext/docs/user/manual/index.md
@@ -5,6 +5,6 @@
Select your language
-1. [English](en)
-1. [Deutsch](de)
-1. [Español](es)
\ No newline at end of file
+1. [English]({{docs_base_url}}/user/manual/en)
+1. [Deutsch]({{docs_base_url}}/user/manual/de)
+1. [Español]({{docs_base_url}}/user/manual/es)
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js
index bb9edf9..f69f4e5 100755
--- a/erpnext/hr/doctype/employee/employee.js
+++ b/erpnext/hr/doctype/employee/employee.js
@@ -40,13 +40,6 @@
"Ms": "Female"
}[this.frm.doc.salutation]);
}
- },
-
- make_salary_structure: function(btn) {
- frappe.model.open_mapped_doc({
- method: "erpnext.hr.doctype.employee.employee.make_salary_structure",
- frm: cur_frm
- });
}
});
cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm});
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 18b166b..c6b3633 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -178,19 +178,6 @@
return ret
-@frappe.whitelist()
-def make_salary_structure(source_name, target=None):
- target = get_mapped_doc("Employee", source_name, {
- "Employee": {
- "doctype": "Salary Structure",
- "field_map": {
- "name": "employee",
- }
- }
- })
- target.make_earn_ded_table()
- return target
-
def validate_employee_role(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("user_roles")]:
diff --git a/erpnext/hr/doctype/employee/employee_list.js b/erpnext/hr/doctype/employee/employee_list.js
index 697900c..c786004 100644
--- a/erpnext/hr/doctype/employee/employee_list.js
+++ b/erpnext/hr/doctype/employee/employee_list.js
@@ -1,5 +1,5 @@
frappe.listview_settings['Employee'] = {
- add_fields: ["status", "branch", "department", "designation"],
+ add_fields: ["status", "branch", "department", "designation","image"],
filters: [["status","=", "Active"]],
get_indicator: function(doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index ba2c38b..d7892c7 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -8,6 +8,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
+ "editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
@@ -187,6 +188,31 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "max_working_hours_against_timesheet",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Max working hours against Timesheet",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -200,7 +226,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-06-27 16:20:59.737869",
+ "modified": "2016-08-10 12:32:39.780599",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py
index c80c660..c7162b8 100644
--- a/erpnext/hr/doctype/process_payroll/process_payroll.py
+++ b/erpnext/hr/doctype/process_payroll/process_payroll.py
@@ -10,6 +10,7 @@
class ProcessPayroll(Document):
+
def get_emp_list(self):
"""
Returns list of active employees based on selected criteria
@@ -18,15 +19,23 @@
cond = self.get_filter_condition()
cond += self.get_joining_releiving_condition()
+ sal_struct = frappe.db.sql("""
+ select name from `tabSalary Structure`
+ where docstatus != 2 and
+ ifnull(salary_slip_based_on_timesheet,0) = 0""")
+
+ if sal_struct:
+ cond += "and t2.parent IN %(sal_struct)s "
+
emp_list = frappe.db.sql("""
select t1.name
- from `tabEmployee` t1, `tabSalary Structure` t2
- where t1.docstatus!=2 and t2.docstatus != 2 and
- ifnull(t2.salary_slip_based_on_timesheet,0) = 0 and t1.name = t2.employee
- %s """% cond)
+ from `tabEmployee` t1, `tabSalary Structure Employee` t2
+ where t1.docstatus!=2 and t1.name = t2.employee
+ %s """% cond, {"sal_struct": sal_struct})
return emp_list
+
def get_filter_condition(self):
self.check_mandatory()
diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json
index 3cf83c1..5595fcc 100644
--- a/erpnext/hr/doctype/salary_component/salary_component.json
+++ b/erpnext/hr/doctype/salary_component/salary_component.json
@@ -9,6 +9,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
+ "editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
@@ -39,6 +40,33 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "salary_component_abbr",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 1,
+ "in_list_view": 1,
+ "label": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "print_width": "120px",
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0,
+ "width": "120px"
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
@@ -59,6 +87,32 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Accounts",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Salary Component Account",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
}
],
"hide_heading": 0,
@@ -72,7 +126,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-01 12:42:46.103131",
+ "modified": "2016-07-27 17:40:18.335540",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Component",
diff --git a/erpnext/hr/doctype/salary_component/salary_component.py b/erpnext/hr/doctype/salary_component/salary_component.py
index 5a37172..ca69568 100644
--- a/erpnext/hr/doctype/salary_component/salary_component.py
+++ b/erpnext/hr/doctype/salary_component/salary_component.py
@@ -5,6 +5,24 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe import _
class SalaryComponent(Document):
- pass
+ def validate(self):
+ self.validate_abbr()
+
+
+ def validate_abbr(self):
+ if not self.salary_component_abbr:
+ self.salary_component_abbr = ''.join([c[0] for c in self.salary_component.split()]).upper()
+
+ self.salary_component_abbr = self.salary_component_abbr.strip()
+
+ if self.get('__islocal') and len(self.salary_component_abbr) > 5:
+ frappe.throw(_("Abbreviation cannot have more than 5 characters"))
+
+ if not self.salary_component_abbr.strip():
+ frappe.throw(_("Abbreviation is mandatory"))
+
+ if frappe.db.sql("select salary_component_abbr from `tabSalary Component` where name!=%s and salary_component_abbr=%s", (self.name, self.salary_component_abbr)):
+ frappe.throw(_("Abbreviation already used for another salary component"))
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_detail/salary_detail.json b/erpnext/hr/doctype/salary_detail/salary_detail.json
index eadfccf..d809787 100644
--- a/erpnext/hr/doctype/salary_detail/salary_detail.json
+++ b/erpnext/hr/doctype/salary_detail/salary_detail.json
@@ -21,7 +21,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
- "label": "Salary Component",
+ "label": "Component",
"length": 0,
"no_copy": 0,
"options": "Salary Component",
@@ -40,13 +40,171 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "abbr",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Abbr",
+ "length": 0,
+ "no_copy": 0,
+ "options": "salary_component.salary_component_abbr",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "condition",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Condition",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "default": "1",
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "amount_based_on_formula",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Amount based on formula",
+ "length": 0,
+ "no_copy": 0,
+ "options": "",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "default": "",
+ "depends_on": "eval:doc.amount_based_on_formula!=0 && doc.parenttype=='Salary Structure'",
+ "description": "",
+ "fieldname": "formula",
+ "fieldtype": "Code",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Formula",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "eval:doc.amount_based_on_formula!==1 || doc.parenttype==='Salary Slip'",
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
- "in_list_view": 1,
+ "in_list_view": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
@@ -66,30 +224,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
+ "depends_on": "",
"fieldname": "depends_on_lwp",
"fieldtype": "Check",
"hidden": 0,
@@ -102,6 +237,33 @@
"no_copy": 0,
"permlevel": 0,
"precision": "",
+ "print_hide": 1,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "default_amount",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Default Amount",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -115,17 +277,43 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "default_amount",
- "fieldtype": "Currency",
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "section_break_11",
+ "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
- "label": "Default Amount",
"length": 0,
"no_copy": 0,
- "options": "Company:company:default_currency",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "depends_on": "eval:doc.parenttype=='Salary Structure'",
+ "fieldname": "condition_and_formula_help",
+ "fieldtype": "HTML",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Condition and Formula Help",
+ "length": 0,
+ "no_copy": 0,
+ "options": "<h3>Condition and Formula Help</h3>\n\n<p>Notes:</p>\n\n<ol>\n<li>Use field <code>base</code> for using base salary of the Employee</li>\n<li>Use Salary Component abbreviations in conditions and formulas. <code>BS = Basic Salary</code></li>\n<li>Use field name for employee details in conditions and formulas. <code>Employment Type = employment_type</code><code>Branch = branch</code></li>\n<li>Direct Amount can also be entered based on Condtion. See example 3</li></ol>\n\n<h4>Examples</h4>\n<ol>\n<li>Calculating Basic Salary based on <code>base</code>\n<pre><code>Condition: base < 10000</code></pre>\n<pre><code>Formula: base * .2</code></pre></li>\n<li>Calculating HRA based on Basic Salary<code>BS</code> \n<pre><code>Condition: BS > 2000</code></pre>\n<pre><code>Formula: BS * .1</code></pre></li>\n<li>Calculating TDS based on Employment Type<code>employment_type</code> \n<pre><code>Condition: employment_type==\"Intern\"</code></pre>\n<pre><code>Amount: 1000</code></pre></li>\n</ol>",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -148,7 +336,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-11 03:28:06.925361",
+ "modified": "2016-08-22 00:25:44.331685",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js
index 6cfba4b..3b6eef8 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.js
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.js
@@ -24,7 +24,7 @@
refresh: function(frm) {
frm.trigger("toggle_fields")
- },
+ },
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json
index 75146d9..a6f5266 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.json
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.json
@@ -9,6 +9,7 @@
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
+ "editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
@@ -1172,7 +1173,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-07 12:49:01.596547",
+ "modified": "2016-08-10 15:57:59.944600",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip",
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 3bc7f0e..c82dcf5 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -25,23 +25,84 @@
self.set_month_dates()
if not (len(self.get("earnings")) or len(self.get("deductions"))):
+ # get details from salary structure
self.get_emp_and_leave_details()
else:
self.get_leave_details(lwp = self.leave_without_pay)
- if self.salary_slip_based_on_timesheet or not self.net_pay:
- self.calculate_net_pay()
+ # if self.salary_slip_based_on_timesheet or not self.net_pay:
+ # self.calculate_net_pay()
company_currency = get_company_currency(self.company)
self.total_in_words = money_in_words(self.rounded_total, company_currency)
-
- set_employee_name(self)
+
+ if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"):
+ max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet")
+ if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
+ frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
+ format(max_working_hours), alert=True)
def validate_dates(self):
if date_diff(self.end_date, self.start_date) < 0:
frappe.throw(_("To date cannot be before From date"))
+
+ def calculate_component_amounts(self):
+ if not getattr(self, '_salary_structure_doc', None):
+ self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
+
+ data = self.get_data_for_eval()
+ for key in ('earnings', 'deductions'):
+ for d in self._salary_structure_doc.get(key):
+ amount = self.eval_condition_and_formula(d, data)
+ if amount:
+ self.append(key, {
+ 'amount': amount,
+ 'default_amount': amount,
+ 'depends_on_lwp' : d.depends_on_lwp,
+ 'salary_component' : d.salary_component
+ })
+
+ def eval_condition_and_formula(self, d, data):
+ try:
+ if d.condition:
+ if not eval(d.condition, None, data):
+ return None
+
+ amount = d.amount
+ if d.amount_based_on_formula:
+ if d.formula:
+ amount = eval(d.formula, None, data)
+
+ data[d.abbr] = amount
+ return amount
+ except NameError as err:
+ frappe.throw(_("Name error: {0}".format(err)))
+ except SyntaxError as err:
+ frappe.throw(_("Syntax error in formula or condition: {0}".format(err)))
+ except:
+ frappe.throw(_("Error in formula or condition"))
+ raise
+
+ def get_data_for_eval(self):
+ '''Returns data for evaluating formula'''
+ data = frappe._dict()
+
+ for d in self._salary_structure_doc.employees:
+ if d.employee == self.employee:
+ data.base, data.variable = d.base, d.variable
+
+ data.update(frappe.get_doc("Employee", self.employee).as_dict())
+
+ # set values for components
+ salary_components = frappe.get_all("Salary Component", fields=["salary_component_abbr"])
+ for salary_component in salary_components:
+ data[salary_component.salary_component_abbr] = 0
+
+ return data
+
def get_emp_and_leave_details(self):
+ '''First time, load all the components from salary structure'''
if self.employee:
self.set("earnings", [])
self.set("deductions", [])
@@ -55,10 +116,10 @@
struct = self.check_sal_struct(joining_date, relieving_date)
if struct:
- ss_doc = frappe.get_doc('Salary Structure', struct)
- self.salary_slip_based_on_timesheet = ss_doc.salary_slip_based_on_timesheet or 0
+ self._salary_structure_doc = frappe.get_doc('Salary Structure', struct)
+ self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
self.set_time_sheet()
- self.pull_sal_struct(ss_doc)
+ self.pull_sal_struct()
def set_time_sheet(self):
if self.salary_slip_based_on_timesheet:
@@ -79,28 +140,44 @@
self.end_date = m['month_end_date']
def check_sal_struct(self, joining_date, relieving_date):
- struct = frappe.db.sql("""select name from `tabSalary Structure`
- where employee=%s and is_active = 'Yes'
- and (from_date <= %s or from_date <= %s)
- and (to_date is null or to_date >= %s or to_date >= %s) order by from_date desc limit 1""",
- (self.employee, self.start_date, joining_date, self.end_date, relieving_date))
+ st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
+ where employee=%s order by modified desc limit 1""",self.employee)
+
+ if st_name:
+ struct = frappe.db.sql("""select name from `tabSalary Structure`
+ where name=%s and is_active = 'Yes'
+ and (from_date <= %s or from_date <= %s)
+ and (to_date is null or to_date >= %s or to_date >= %s) order by from_date desc limit 1""",
+ (st_name, self.start_date, joining_date, self.end_date, relieving_date))
- if not struct:
+ if not struct:
+ self.salary_structure = None
+ frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
+ .format(self.employee), title=_('Salary Structure Missing'))
+
+ return struct and struct[0][0] or ''
+ else:
self.salary_structure = None
frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
- .format(self.employee), title=_('Salary Structure Missing'))
+ .format(self.employee), title=_('Salary Structure Missing'))
- return struct and struct[0][0] or ''
-
- def pull_sal_struct(self, ss_doc):
+ def pull_sal_struct(self):
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
- make_salary_slip(ss_doc.name, self)
+ make_salary_slip(self._salary_structure_doc.name, self)
if self.salary_slip_based_on_timesheet:
- self.salary_structure = ss_doc.name
- self.hour_rate = ss_doc.hour_rate
+ self.salary_structure = self._salary_structure_doc.name
+ self.hour_rate = self._salary_structure_doc.hour_rate
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
- self.add_earning_for_hourly_wages(ss_doc.salary_component)
+ self.add_earning_for_hourly_wages(self._salary_structure_doc.salary_component)
+
+
+
+ def process_salary_structure(self):
+ '''Calculate salary after salary structure details have been updated'''
+ self.pull_emp_details()
+ self.get_leave_details()
+ self.calculate_net_pay()
def add_earning_for_hourly_wages(self, salary_component):
default_type = False
@@ -121,6 +198,7 @@
self.bank_name = emp.bank_name
self.bank_account_no = emp.bank_ac_no
+
def get_leave_details(self, joining_date=None, relieving_date=None, lwp=None):
if not self.fiscal_year:
# if default fiscal year is not set, get from nowdate
@@ -222,35 +300,28 @@
if frappe.db.get_value('Timesheet', data.time_sheet, 'status') == 'Payrolled':
frappe.throw(_("Salary Slip of employee {0} already created for time sheet {1}").format(self.employee, data.time_sheet))
- def calculate_earning_total(self):
- self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
- for d in self.get("earnings"):
- if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet:
- d.amount = rounded((flt(d.default_amount) * flt(self.payment_days)
- / cint(self.total_days_in_month)), self.precision("amount", "earnings"))
- elif not self.payment_days and not self.salary_slip_based_on_timesheet:
- d.amount = 0
- elif not d.amount:
- d.amount = d.default_amount
- self.gross_pay += flt(d.amount)
-
- def calculate_ded_total(self):
- self.total_deduction = 0
- for d in self.get('deductions'):
+ def sum_components(self, component_type, total_field):
+ for d in self.get(component_type):
if cint(d.depends_on_lwp) == 1 and not self.salary_slip_based_on_timesheet:
d.amount = rounded((flt(d.amount) * flt(self.payment_days)
- / cint(self.total_days_in_month)), self.precision("amount", "deductions"))
+ / cint(self.total_days_in_month)), self.precision("amount", component_type))
elif not self.payment_days and not self.salary_slip_based_on_timesheet:
d.amount = 0
elif not d.amount:
d.amount = d.default_amount
- self.total_deduction += flt(d.amount)
+ self.set(total_field, self.get(total_field) + flt(d.amount))
def calculate_net_pay(self):
+ self.calculate_component_amounts()
+
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
- self.calculate_earning_total()
- self.calculate_ded_total()
+ self.gross_pay = flt(self.arrear_amount) + flt(self.leave_encashment_amount)
+ self.total_deduction = 0
+
+ self.sum_components('earnings', 'gross_pay')
+ self.sum_components('deductions', 'total_deduction')
+
self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index d90d4b2..503996d 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -4,19 +4,22 @@
import unittest
import frappe
-from frappe.utils import today, now_datetime, getdate, cstr
-from erpnext.hr.doctype.employee.employee import make_salary_structure
+import erpnext
+from frappe.utils import today, now_datetime, getdate, cstr, add_years, nowdate
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
class TestSalarySlip(unittest.TestCase):
def setUp(self):
+ self.make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
+
for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("delete from `tab%s`" % dt)
make_allocation_record(leave_type="_Test Leave Type LWP")
-
- frappe.db.set_value("Company", "_Test Company", "default_holiday_list", "_Test Holiday List")
+
+ self.make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
from erpnext.hr.doctype.leave_application.test_leave_application import _test_records as leave_applications
la = frappe.copy_doc(leave_applications[2])
@@ -30,71 +33,78 @@
def test_salary_slip_with_holidays_included(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
self.assertEquals(ss.total_days_in_month, 31)
- self.assertEquals(ss.payment_days, 30)
- self.assertEquals(ss.earnings[0].amount, 14516.13)
- self.assertEquals(ss.earnings[1].amount, 500)
- self.assertEquals(ss.deductions[0].amount, 100)
- self.assertEquals(ss.deductions[1].amount, 48.39)
- self.assertEquals(ss.gross_pay, 15016.13)
- self.assertEquals(ss.net_pay, 14867.74)
+ self.assertEquals(ss.payment_days, 31)
+ self.assertEquals(ss.earnings[0].amount, 0)
+ self.assertEquals(ss.earnings[1].amount, 0)
+ self.assertEquals(ss.deductions[0].amount, 0)
+ self.assertEquals(ss.deductions[1].amount, 0)
+ self.assertEquals(ss.gross_pay, 0)
+ self.assertEquals(ss.net_pay, 0)
def test_salary_slip_with_holidays_excluded(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 28)
- self.assertEquals(ss.earnings[0].amount, 14516.13)
- self.assertEquals(ss.earnings[1].amount, 500)
- self.assertEquals(ss.deductions[0].amount, 100)
- self.assertEquals(ss.deductions[1].amount, 48.39)
- self.assertEquals(ss.gross_pay, 15016.13)
- self.assertEquals(ss.net_pay, 14867.74)
-
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
+ self.assertEquals(ss.earnings[0].amount, 0)
+ self.assertEquals(ss.earnings[0].default_amount, 5000)
+ self.assertEquals(ss.earnings[1].amount, 0)
+ self.assertEquals(ss.deductions[0].amount, 0)
+ self.assertEquals(ss.deductions[1].amount, 0)
+ self.assertEquals(ss.gross_pay, 0)
+ self.assertEquals(ss.net_pay, 0)
+
def test_payment_days(self):
# Holidays not included in working days
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
# set joinng date in the same month
- frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2013-01-11")
+ self.make_employee("test_employee@salary.com")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2013-01-11")
+
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
- ss = frappe.copy_doc(test_records[0])
- ss.insert()
-
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 19)
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
# set relieving date in the same month
- frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", "2013-01-28")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", "12-12-2016")
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
+
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
ss.save()
- self.assertEquals(ss.total_days_in_month, 29)
- self.assertEquals(ss.payment_days, 16)
-
+
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
# Holidays included in working days
- frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
+ frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 1)
+ self.assertEquals(ss.total_days_in_month, 27)
+ self.assertEquals(ss.payment_days, 27)
ss.save()
- self.assertEquals(ss.total_days_in_month, 31)
- self.assertEquals(ss.payment_days, 17)
-
- frappe.db.set_value("Employee", "_T-Employee-0001", "date_of_joining", "2001-01-11")
- frappe.db.set_value("Employee", "_T-Employee-0001", "relieving_date", None)
+ #
+ # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", "2001-01-11")
+ # frappe.db.set_value("Employee", frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
def test_employee_salary_slip_read_permission(self):
- self.make_employee("test_employee@example.com")
- self.make_employee("test_employee_2@example.com")
+ self.make_employee("test_employee@salary.com")
salary_slip_test_employee = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee@example.com"))
-
- salary_slip_test_employee_2 = frappe.get_doc("Salary Slip",
- self.make_employee_salary_slip("test_employee_2@example.com"))
-
- frappe.set_user("test_employee@example.com")
+ self.make_employee_salary_slip("test_employee@salary.com"))
+ frappe.set_user("test_employee@salary.com")
self.assertTrue(salary_slip_test_employee.has_permission("read"))
def test_email_salary_slip(self):
@@ -104,8 +114,10 @@
hr_settings.email_salary_slip_to_employee = 1
hr_settings.save()
- self.make_employee("test_employee@example.com")
- self.make_employee_salary_slip("test_employee@example.com")
+ self.make_employee("test_employee@salary.com")
+ ss = frappe.get_doc("Salary Slip",
+ self.make_employee_salary_slip("test_employee@salary.com"))
+ ss.submit()
email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
self.assertTrue(email_queue)
@@ -123,32 +135,52 @@
if not frappe.db.get_value("Employee", {"user_id": user}):
frappe.get_doc({
"doctype": "Employee",
- "naming_series": "_T-Employee-",
+ "naming_series": "EMP-",
"employee_name": user,
+ "company": erpnext.get_default_company(),
"user_id": user,
- "company": "_Test Company",
"date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01",
- "department": "_Test Department 1",
+ "department": frappe.get_all("Department", fields="name")[0].name,
"gender": "Female",
"company_email": user,
- "status": "Active"
+ "status": "Active",
+ "employment_type": "Intern"
}).insert()
-
+
+ def make_holiday_list(self):
+ if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "Salary Slip Test Holiday List",
+ "from_date": nowdate(),
+ "to_date": add_years(nowdate(), 1),
+ "weekly_off": "Sunday"
+ }).insert()
+ holiday_list.get_weekly_off_dates()
+ holiday_list.save()
+
+ def make_salary_component(self, salary_components):
+ for salary_component in salary_components:
+ if not frappe.db.exists('Salary Component', salary_component):
+ sal_comp = frappe.get_doc({
+ "doctype": "Salary Component",
+ "salary_component": salary_component
+ })
+ sal_comp.insert()
+
def make_employee_salary_slip(self, user):
employee = frappe.db.get_value("Employee", {"user_id": user})
- salary_structure = frappe.db.get_value("Salary Structure", {"employee": employee})
- if not salary_structure:
- salary_structure = make_salary_structure(employee)
- salary_structure.from_date = today()
- salary_structure.insert()
- salary_structure = salary_structure.name
-
- salary_slip = frappe.db.get_value("Salary Slip", {"employee": employee})
+ salary_structure = make_salary_structure("Salary Structure Test for Salary Slip")
+ salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
+
if not salary_slip:
- salary_slip = make_salary_slip(salary_structure)
+ salary_slip = make_salary_slip(salary_structure, employee = employee)
+ salary_slip.employee_name = frappe.get_value("Employee", {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
+ salary_slip.month = "12"
+ salary_slip.fiscal_year = "_Test Fiscal Year 2016"
salary_slip.insert()
- salary_slip.submit()
+ # salary_slip.submit()
salary_slip = salary_slip.name
return salary_slip
@@ -160,6 +192,82 @@
activity_type.wage_rate = 25
activity_type.save()
-test_dependencies = ["Leave Application", "Holiday List"]
-test_records = frappe.get_test_records('Salary Slip')
+def make_salary_structure(sal_struct):
+ if not frappe.db.exists('Salary Structure', sal_struct):
+ frappe.get_doc({
+ "doctype": "Salary Structure",
+ "name": sal_struct,
+ "company": erpnext.get_default_company(),
+ "from_date": nowdate(),
+ "employees": get_employee_details(),
+ "earnings": get_earnings_component(),
+ "deductions": get_deductions_component()
+ }).insert()
+ return sal_struct
+
+
+def get_employee_details():
+ return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
+ "base": 25000,
+ "variable": 5000
+ }
+ ]
+
+def get_earnings_component():
+ return [
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base < 10000',
+ "formula": 'base*.1',
+ "idx": 2
+ },
+ {
+ "salary_component": 'HRA',
+ "abbr":'H',
+ "amount": 3000,
+ "idx": 3
+ },
+ {
+ "salary_component": 'Allowance',
+ "abbr":'A',
+ "condition": 'H < 10000',
+ "formula": 'BS*.5',
+ "idx": 4
+ },
+ ]
+
+def get_deductions_component():
+ return [
+ {
+ "salary_component": 'Professional Tax',
+ "abbr":'PT',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "formula": 'base*.5',
+ "idx": 2
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "condition": 'employment_type=="Intern"',
+ "formula": 'base*.1',
+ "idx": 3
+ }
+ ]
+
+test_dependencies = ["Leave Application", "Holiday List"]
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index fab7ac4..a108e38 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -9,15 +9,7 @@
e_tbl = doc.earnings || [];
d_tbl = doc.deductions || [];
if (e_tbl.length == 0 && d_tbl.length == 0)
- return $c_obj(doc,'make_earn_ded_table','', function(r, rt) { refresh_many(['earnings', 'deductions']);});
-}
-
-cur_frm.cscript.refresh = function(doc, dt, dn){
- if((!doc.__islocal) && (doc.is_active == 'Yes') && cint(doc.salary_slip_based_on_timesheet == 0)){
- cur_frm.add_custom_button(__('Salary Slip'),
- cur_frm.cscript['Make Salary Slip'], __("Make"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
- }
+ return function(r, rt) { refresh_many(['earnings', 'deductions']);};
}
frappe.ui.form.on('Salary Structure', {
@@ -25,29 +17,54 @@
frm.trigger("toggle_fields")
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
- },
+
+ frm.add_custom_button(__("Preview Salary Slip"),
+ function() { frm.trigger('preview_salary_slip'); }, "icon-sitemap", "btn-default");
+
+ },
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
},
-
+
+ preview_salary_slip: function(frm) {
+ var d = new frappe.ui.Dialog({
+ title: __("Preview Salary Slip"),
+ fields: [
+ {"fieldname":"employee", "fieldtype":"Select", "label":__("Employee"),
+ options: $.map(frm.doc.employees, function(d) { return d.employee }), reqd: 1, label:"Employee"},
+ {fieldname:"fetch", "label":__("Show Salary Slip"), "fieldtype":"Button"}
+ ]
+ });
+ d.get_input("fetch").on("click", function() {
+ var values = d.get_values();
+ if(!values) return;
+ frm.doc.salary_slip_based_on_timesheet?print_format="Salary Slip based on Timesheet":print_format="Salary Slip Standard";
+
+ frappe.call({
+ method: "erpnext.hr.doctype.salary_structure.salary_structure.make_salary_slip",
+ args: {
+ source_name: frm.doc.name,
+ employee: values.employee,
+ as_print: 1,
+ print_format: print_format
+ },
+ callback: function(r) {
+ var new_window = window.open();
+ new_window.document.write(r.message);
+ // frappe.msgprint(r.message);
+ }
+ });
+ });
+ d.show();
+ },
+
toggle_fields: function(frm) {
frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
frm.toggle_reqd(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
}
})
-cur_frm.cscript['Make Salary Slip'] = function() {
- frappe.model.open_mapped_doc({
- method: "erpnext.hr.doctype.salary_structure.salary_structure.make_salary_slip",
- frm: cur_frm
- });
-}
-
-cur_frm.cscript.employee = function(doc, dt, dn){
- if (doc.employee)
- return get_server_fields('get_employee_details','','',doc,dt,dn);
-}
cur_frm.cscript.amount = function(doc, cdt, cdn){
calculate_totals(doc, cdt, cdn);
@@ -79,10 +96,6 @@
if(doc.employee && doc.is_active == "Yes") frappe.model.clear_doc("Employee", doc.employee);
}
-cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
- return{ query: "erpnext.controllers.queries.employee_query" }
-}
-
frappe.ui.form.on('Salary Detail', {
amount: function(frm) {
@@ -96,4 +109,12 @@
deductions_remove: function(frm) {
calculate_totals(frm.doc);
}
-})
\ No newline at end of file
+})
+
+frappe.ui.form.on('Salary Structure Employee', {
+ onload: function(frm) {
+ frm.set_query("employee","employees", function(doc,cdt,cdn) {
+ return{ query: "erpnext.controllers.queries.employee_query" }
+ })
+ }
+});
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.json b/erpnext/hr/doctype/salary_structure/salary_structure.json
index 4cdc67b..7ee4204 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.json
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.json
@@ -2,6 +2,7 @@
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 0,
+ "autoname": "Prompt",
"beta": 0,
"creation": "2013-03-07 18:50:29",
"custom": 0,
@@ -38,140 +39,6 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "employee",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Employee",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "employee",
- "oldfieldtype": "Link",
- "options": "Employee",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "employee_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Employee Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "employee_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "branch",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Branch",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "branch",
- "oldfieldtype": "Select",
- "options": "Branch",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "designation",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Designation",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "designation",
- "oldfieldtype": "Select",
- "options": "Designation",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "department",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Department",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "department",
- "oldfieldtype": "Select",
- "options": "Department",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -354,6 +221,57 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "fieldname": "employee_break",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "description": "Select employees for current Salary Structure",
+ "fieldname": "employees",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Employees",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Salary Structure Employee",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"fieldname": "time_sheet_earning_detail",
"fieldtype": "Section Break",
"hidden": 0,
@@ -498,6 +416,7 @@
"oldfieldname": "earning_deduction",
"oldfieldtype": "Section Break",
"permlevel": 0,
+ "precision": "2",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -671,7 +590,7 @@
"collapsible": 0,
"fieldname": "total_earning",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -698,7 +617,7 @@
"collapsible": 0,
"fieldname": "total_deduction",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -723,33 +642,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "fieldname": "column_break3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
- "width": "50%"
- },
- {
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"fieldname": "net_pay",
"fieldtype": "Currency",
- "hidden": 0,
+ "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
@@ -780,7 +675,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-13 23:56:01.550518",
+ "modified": "2016-08-10 12:18:31.521436",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Structure",
@@ -832,7 +727,7 @@
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "timeline_field": "employee",
- "title_field": "employee_name",
+ "timeline_field": "",
+ "title_field": "",
"track_seen": 0
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index c2f95af..d4bc6e3 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cstr, flt, getdate
+from frappe.utils import cstr, flt, getdate, cint
from frappe.model.naming import make_autoname
from frappe import _
from frappe.model.mapper import get_mapped_doc
@@ -12,29 +12,12 @@
from erpnext.hr.utils import set_employee_name
class SalaryStructure(Document):
- def autoname(self):
- self.name = make_autoname(self.employee + '/.SST' + '/.#####')
-
+
def validate(self):
- self.check_overlap()
self.validate_amount()
- self.validate_employee()
self.validate_joining_date()
- set_employee_name(self)
-
- def get_employee_details(self):
- ret = {}
- det = frappe.db.sql("""select employee_name, branch, designation, department
- from `tabEmployee` where name = %s""", self.employee)
- if det:
- ret = {
- 'employee_name': cstr(det[0][0]),
- 'branch': cstr(det[0][1]),
- 'designation': cstr(det[0][2]),
- 'department': cstr(det[0][3]),
- 'backup_employee': cstr(self.employee)
- }
- return ret
+ for e in self.get('employees'):
+ set_employee_name(e)
def get_ss_values(self,employee):
basic_info = frappe.db.sql("""select bank_name, bank_ac_no
@@ -43,71 +26,23 @@
'bank_ac_no': basic_info and basic_info[0][1] or ''}
return ret
- def make_table(self, doct_name, tab_fname, tab_name):
- list1 = frappe.db.sql("select name from `tab%s` where docstatus != 2" % doct_name)
- for li in list1:
- child = self.append(tab_fname, {})
- if(tab_fname == 'earnings'):
- child.salary_component = cstr(li[0])
- child.amount = 0
- elif(tab_fname == 'deductions'):
- child.salary_component = cstr(li[0])
- child.amount = 0
-
- def make_earn_ded_table(self):
- self.make_table('Salary Component','earnings','Salary Detail')
- self.make_table('Salary Component','deductions', 'Salary Detail')
-
- def check_overlap(self):
- existing = frappe.db.sql("""select name from `tabSalary Structure`
- where employee = %(employee)s and
- (
- (%(from_date)s > from_date and %(from_date)s < to_date) or
- (%(to_date)s > from_date and %(to_date)s < to_date) or
- (%(from_date)s <= from_date and %(to_date)s >= to_date))
- and name!=%(name)s
- and docstatus < 2""",
- {
- "employee": self.employee,
- "from_date": self.from_date,
- "to_date": self.to_date,
- "name": self.name or "No Name"
- }, as_dict=True)
-
- if existing:
- frappe.throw(_("Salary structure {0} already exist, more than one salary structure for same period is not allowed").format(existing[0].name))
-
def validate_amount(self):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
frappe.throw(_("Net pay cannot be negative"))
- def validate_employee(self):
- old_employee = frappe.db.get_value("Salary Structure", self.name, "employee")
- if old_employee and self.employee != old_employee:
- frappe.throw(_("Employee can not be changed"))
-
def validate_joining_date(self):
- joining_date = getdate(frappe.db.get_value("Employee", self.employee, "date_of_joining"))
- if getdate(self.from_date) < joining_date:
- frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
+ for e in self.get('employees'):
+ joining_date = getdate(frappe.db.get_value("Employee", e.employee, "date_of_joining"))
+ if getdate(self.from_date) < joining_date:
+ frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
+
@frappe.whitelist()
-def make_salary_slip(source_name, target_doc=None):
+def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None):
def postprocess(source, target):
- # copy earnings and deductions table
- for key in ('earnings', 'deductions'):
- for d in source.get(key):
- target.append(key, {
- 'amount': d.amount,
- 'default_amount': d.amount,
- 'depends_on_lwp' : d.depends_on_lwp,
- 'salary_component' : d.salary_component
- })
-
- target.run_method("pull_emp_details")
- target.run_method("get_leave_details")
- target.run_method("calculate_net_pay")
-
+ if employee:
+ target.employee = employee
+ target.run_method('process_salary_structure')
doc = get_mapped_doc("Salary Structure", source_name, {
"Salary Structure": {
@@ -119,4 +54,8 @@
}
}, target_doc, postprocess, ignore_child_tables=True)
- return doc
+ if cint(as_print):
+ doc.name = 'Preview for {0}'.format(employee)
+ return frappe.get_print(doc.doctype, doc.name, doc = doc, print_format = print_format)
+ else:
+ return doc
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure/test_records.json b/erpnext/hr/doctype/salary_structure/test_records.json
deleted file mode 100644
index 08841d1..0000000
--- a/erpnext/hr/doctype/salary_structure/test_records.json
+++ /dev/null
@@ -1,24 +0,0 @@
-[
- {
- "doctype": "Salary Structure",
- "name": "_Test Salary Structure 1",
- "employee": "_T-Employee-0001",
- "from_date": "2014-02-01",
- "earnings": [
- {
- "salary_component": "_Test Basic Salary"
- },
- {
- "salary_component": "_Test Allowance"
- }
- ],
- "deductions": [
- {
- "salary_component": "_Test Professional Tax"
- },
- {
- "salary_component": "_Test TDS"
- }
- ]
- }
-]
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index 63d0eed..ef9231a 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -4,8 +4,183 @@
import frappe
import unittest
-
-test_records = frappe.get_test_records('Salary Structure')
+import erpnext
+from frappe.utils import nowdate, add_days, add_years
+from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
+# test_records = frappe.get_test_records('Salary Structure')
class TestSalaryStructure(unittest.TestCase):
- pass
+ def test_setup(self):
+ if not frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2016"):
+ fy = frappe.get_doc({
+ "doctype": "Fiscal Year",
+ "year": "_Test Fiscal Year 2016",
+ "year_end_date": "2016-12-31",
+ "year_start_date": "2016-01-01"
+ })
+ fy.insert()
+
+ self.make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List")
+ self.make_salary_component(["Basic Salary", "Allowance", "HRA", "Professional Tax", "TDS"])
+ employee1 = self.make_employee("test_employee@salary.com")
+ employee2 = self.make_employee("test_employee_2@salary.com")
+
+ def make_holiday_list(self):
+ if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "Salary Structure Test Holiday List",
+ "from_date": nowdate(),
+ "to_date": add_years(nowdate(), 1),
+ "weekly_off": "Sunday"
+ }).insert()
+ holiday_list.get_weekly_off_dates()
+ holiday_list.save()
+
+ def make_employee(self, user):
+ if not frappe.db.get_value("User", user):
+ frappe.get_doc({
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "new_password": "password",
+ "user_roles": [{"doctype": "UserRole", "role": "Employee"}]
+ }).insert()
+
+
+ if not frappe.db.get_value("Employee", {"user_id": user}):
+ emp = frappe.get_doc({
+ "doctype": "Employee",
+ "naming_series": "EMP-",
+ "employee_name": user,
+ "company": erpnext.get_default_company(),
+ "user_id": user,
+ "date_of_birth": "1990-05-08",
+ "date_of_joining": "2013-01-01",
+ "relieving_date": "",
+ "department": frappe.get_all("Department", fields="name")[0].name,
+ "gender": "Female",
+ "company_email": user,
+ "status": "Active",
+ "employment_type": "Intern"
+ }).insert()
+ return emp.name
+ else:
+ return frappe.get_value("Employee", {"employee_name":user}, "name")
+
+ def make_salary_component(self, salary_components):
+ for salary_component in salary_components:
+ if not frappe.db.exists('Salary Component', salary_component):
+ sal_comp = frappe.get_doc({
+ "doctype": "Salary Component",
+ "salary_component": salary_component
+ })
+ sal_comp.insert()
+
+
+
+ def test_amount_totals(self):
+ sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"})
+ if not sal_slip:
+ sal_slip = make_salary_slip_from_salary_structure(employee=frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}))
+ self.assertEquals(sal_slip.get("salary_structure"), 'Salary Structure Sample')
+ self.assertEquals(sal_slip.get("earnings")[0].amount, 0)
+ self.assertEquals(sal_slip.get("deductions")[0].amount, 0)
+ self.assertEquals(sal_slip.get("deductions")[1].amount, 0)
+ self.assertEquals(sal_slip.get("total_deduction"), 0)
+ self.assertEquals(sal_slip.get("net_pay"), 0)
+
+
+def make_salary_slip_from_salary_structure(employee):
+ sal_struct = make_salary_structure('Salary Structure Sample')
+ sal_slip = make_salary_slip(sal_struct, employee = employee)
+ sal_slip.employee_name = frappe.get_value("Employee", {"name":employee}, "employee_name")
+ sal_slip.month = "11"
+ sal_slip.fiscal_year = "_Test Fiscal Year 2016"
+ sal_slip.insert()
+ sal_slip.submit()
+ return sal_slip
+
+def make_salary_structure(sal_struct):
+ if not frappe.db.exists('Salary Structure', sal_struct):
+ frappe.get_doc({
+ "doctype": "Salary Structure",
+ "name": sal_struct,
+ "company": erpnext.get_default_company(),
+ "from_date": nowdate(),
+ "employees": get_employee_details(),
+ "earnings": get_earnings_component(),
+ "deductions": get_deductions_component()
+ }).insert()
+ return sal_struct
+
+
+def get_employee_details():
+ return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
+ "base": 25000,
+ "variable": 5000,
+ "idx": 1
+ },
+ {"employee": frappe.get_value("Employee", {"employee_name":"test_employee_2@salary.com"}, "name"),
+ "base": 2100,
+ "variable": 100,
+ "idx": 2
+ }
+ ]
+
+def get_earnings_component():
+ return [
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'Basic Salary',
+ "abbr":'BS',
+ "condition": 'base < 10000',
+ "formula": 'base*.1',
+ "idx": 2
+ },
+ {
+ "salary_component": 'HRA',
+ "abbr":'H',
+ "amount": 3000,
+ "idx": 3
+ },
+ {
+ "salary_component": 'Allowance',
+ "abbr":'A',
+ "condition": 'H < 10000',
+ "formula": 'BS*.5',
+ "idx": 4
+ },
+ ]
+
+def get_deductions_component():
+ return [
+ {
+ "salary_component": 'Professional Tax',
+ "abbr":'PT',
+ "condition": 'base > 10000',
+ "formula": 'base*.2',
+ "idx": 1
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "formula": 'base*.5',
+ "idx": 2
+ },
+ {
+ "salary_component": 'TDS',
+ "abbr":'T',
+ "condition": 'employment_type=="Intern"',
+ "formula": 'base*.1',
+ "idx": 3
+ }
+ ]
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure_employee/__init__.py b/erpnext/hr/doctype/salary_structure_employee/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure_employee/__init__.py
diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json
new file mode 100644
index 0000000..aae33e3
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.json
@@ -0,0 +1,139 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "employee",
+ "beta": 0,
+ "creation": "2016-07-26 11:53:43.621605",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Employee",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Employee",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Employee Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "base",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Base",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "fieldname": "variable",
+ "fieldtype": "Currency",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Variable",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "max_attachments": 0,
+ "modified": "2016-08-11 12:18:14.526977",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Salary Structure Employee",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py
new file mode 100644
index 0000000..dfcac3f
--- /dev/null
+++ b/erpnext/hr/doctype/salary_structure_employee/salary_structure_employee.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, 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 SalaryStructureEmployee(Document):
+ pass
diff --git a/erpnext/hr/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json b/erpnext/hr/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json
index c5154d7..20c7d23 100644
--- a/erpnext/hr/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json
+++ b/erpnext/hr/print_format/salary_slip_based_on_timesheet/salary_slip_based_on_timesheet.json
@@ -6,9 +6,9 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\">{{doc.name}}</h3><div><hr></div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_working_hours\"}, {\"print_hide\": 0, \"fieldname\": \"hour_rate\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"time_sheet\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"working_hours\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"timesheets\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"default_amount\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"default_amount\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\">{{doc.name}}</h3><div><hr></div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_working_hours\"}, {\"print_hide\": 0, \"fieldname\": \"hour_rate\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"time_sheet\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"working_hours\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"timesheets\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
"idx": 0,
- "modified": "2016-07-07 13:09:43.734752",
+ "modified": "2016-08-21 21:02:59.896033",
"modified_by": "Administrator",
"name": "Salary Slip based on Timesheet",
"owner": "Administrator",
diff --git a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
index f8bd328..1789c75 100644
--- a/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
+++ b/erpnext/hr/print_format/salary_slip_standard/salary_slip_standard.json
@@ -6,9 +6,9 @@
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
- "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\"><span style=\\\"line-height: 1.42857;\\\">{{doc.name}}</span></h3>\\n<div>\\n <hr style=\\\"text-align: center;\\\">\\n</div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_days_in_month\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"default_amount\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"default_amount\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"HTML\", \"options\": \" <h3 style=\\\"text-align: right;\\\"><span style=\\\"line-height: 1.42857;\\\">{{doc.name}}</span></h3>\\n<div>\\n <hr style=\\\"text-align: center;\\\">\\n</div> \"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"employee\"}, {\"print_hide\": 0, \"fieldname\": \"company\"}, {\"print_hide\": 0, \"fieldname\": \"employee_name\"}, {\"print_hide\": 0, \"fieldname\": \"department\"}, {\"print_hide\": 0, \"fieldname\": \"designation\"}, {\"print_hide\": 0, \"fieldname\": \"branch\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"start_date\"}, {\"print_hide\": 0, \"fieldname\": \"end_date\"}, {\"print_hide\": 0, \"fieldname\": \"total_days_in_month\"}, {\"print_hide\": 0, \"fieldname\": \"leave_without_pay\"}, {\"print_hide\": 0, \"fieldname\": \"payment_days\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"earnings\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"salary_component\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"depends_on_lwp\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"deductions\"}, {\"fieldtype\": \"Section Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"arrear_amount\"}, {\"print_hide\": 0, \"fieldname\": \"leave_encashment_amount\"}, {\"print_hide\": 0, \"fieldname\": \"gross_pay\"}, {\"print_hide\": 0, \"fieldname\": \"total_deduction\"}, {\"print_hide\": 0, \"fieldname\": \"net_pay\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\"}, {\"print_hide\": 0, \"fieldname\": \"total_in_words\"}]",
"idx": 0,
- "modified": "2016-07-07 13:04:55.804431",
+ "modified": "2016-08-22 00:21:42.600548",
"modified_by": "Administrator",
"name": "Salary Slip Standard",
"owner": "Administrator",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ef9f4da..e938b7d 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -23,13 +23,15 @@
execute:frappe.reload_doc('buying', 'doctype', 'supplier') # 2014-01-29
execute:frappe.reload_doc('accounts', 'doctype', 'asset_category')
execute:frappe.reload_doc('accounts', 'doctype', 'pricing_rule')
+
+erpnext.patches.v5_2.change_item_selects_to_checks
execute:frappe.reload_doctype('Item')
+
erpnext.patches.v4_0.map_charge_to_taxes_and_charges
execute:frappe.reload_doc('support', 'doctype', 'newsletter') # 2014-01-31
execute:frappe.reload_doc('hr', 'doctype', 'employee') # 2014-02-03
execute:frappe.db.sql("update tabPage set module='Core' where name='Setup'")
-erpnext.patches.v5_2.change_item_selects_to_checks
erpnext.patches.v4_0.fields_to_be_renamed
erpnext.patches.v4_0.rename_sitemap_to_route
erpnext.patches.v7_0.re_route #2016-06-27
@@ -310,4 +312,5 @@
erpnext.patches.v7_0.rename_examination_to_assessment
erpnext.patches.v7_0.set_portal_settings
erpnext.patches.v7_0.repost_future_gle_for_purchase_invoice
-erpnext.patches.v7_0.fix_duplicate_icons
\ No newline at end of file
+erpnext.patches.v7_0.fix_duplicate_icons
+erpnext.patches.v7_0.move_employee_parent_to_child_in_salary_structure
\ No newline at end of file
diff --git a/erpnext/patches/v5_2/change_item_selects_to_checks.py b/erpnext/patches/v5_2/change_item_selects_to_checks.py
index dde0b56..1ee8f6c 100644
--- a/erpnext/patches/v5_2/change_item_selects_to_checks.py
+++ b/erpnext/patches/v5_2/change_item_selects_to_checks.py
@@ -4,8 +4,7 @@
def execute():
fields = ("is_stock_item", "is_asset_item", "has_batch_no", "has_serial_no",
- "inspection_required", "is_sub_contracted_item")
-
+ "is_sales_item", "is_purchase_item", "inspection_required", "is_sub_contracted_item")
# convert to 1 or 0
update_str = ", ".join(["`{0}`=if(`{0}`='Yes',1,0)".format(f) for f in fields])
diff --git a/erpnext/patches/v7_0/calculate_total_costing_amount.py b/erpnext/patches/v7_0/calculate_total_costing_amount.py
index 4da839f..11fdff9 100644
--- a/erpnext/patches/v7_0/calculate_total_costing_amount.py
+++ b/erpnext/patches/v7_0/calculate_total_costing_amount.py
@@ -13,4 +13,5 @@
ts.flags.ignore_validate = True
ts.flags.ignore_mandatory = True
ts.flags.ignore_validate_update_after_submit = True
+ ts.flags.ignore_links = True
ts.save()
diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
index 072ba13..b802656 100644
--- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
+++ b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py
@@ -1,23 +1,30 @@
import frappe
from erpnext.manufacturing.doctype.production_order.production_order \
import make_timesheet, add_timesheet_detail
-from erpnext.projects.doctype.timesheet.timesheet import OverlapError
def execute():
frappe.reload_doc('projects', 'doctype', 'timesheet')
for data in frappe.get_all('Time Log', fields=["*"], filters = [["docstatus", "<", "2"]]):
- try:
- time_sheet = make_timesheet(data.production_order)
- args = get_timelog_data(data)
- add_timesheet_detail(time_sheet, args)
- time_sheet.docstatus = data.docstatus
- time_sheet.note = data.note
- time_sheet.company = frappe.db.get_single_value('Global Defaults', 'default_company')
- time_sheet.save(ignore_permissions=True)
- except OverlapError:
- time_sheet.flags.ignore_validate = True
- time_sheet.save(ignore_permissions=True)
+ if data.task:
+ company = frappe.db.get_value("Task", data.task, "company")
+ elif data.production_order:
+ company = frappe.db.get_value("Prodction Order", data.production_order, "company")
+ else:
+ company = frappe.db.get_single_value('Global Defaults', 'default_company')
+
+ time_sheet = make_timesheet(data.production_order)
+ args = get_timelog_data(data)
+ add_timesheet_detail(time_sheet, args)
+ time_sheet.docstatus = data.docstatus
+ time_sheet.note = data.note
+ time_sheet.company = company
+
+ time_sheet.set_status()
+ time_sheet.update_cost()
+ time_sheet.calculate_total_amounts()
+ time_sheet.flags.ignore_validate = True
+ time_sheet.save(ignore_permissions=True)
def get_timelog_data(data):
return {
diff --git a/erpnext/patches/v7_0/make_guardian.py b/erpnext/patches/v7_0/make_guardian.py
index f654b79..0839c4f 100644
--- a/erpnext/patches/v7_0/make_guardian.py
+++ b/erpnext/patches/v7_0/make_guardian.py
@@ -2,19 +2,25 @@
import frappe
def execute():
- if frappe.db.exists("DocType", "Student") and "father_name" in frappe.db.get_table_columns("Student"):
- frappe.reload_doc("schools", "doctype", "student")
- frappe.reload_doc("schools", "doctype", "guardian")
- frappe.reload_doc("schools", "doctype", "guardian_interest")
- frappe.reload_doc("hr", "doctype", "interest")
+ if frappe.db.exists("DocType", "Student"):
+ student_table_cols = frappe.db.get_table_columns("Student")
+ if "father_name" in student_table_cols:
+ frappe.reload_doc("schools", "doctype", "student")
+ frappe.reload_doc("schools", "doctype", "guardian")
+ frappe.reload_doc("schools", "doctype", "guardian_interest")
+ frappe.reload_doc("hr", "doctype", "interest")
+
+ fields = ["name", "father_name", "mother_name"]
+
+ if "father_email_id" in student_table_cols:
+ fields += ["father_email_id", "mother_email_id"]
- students = frappe.get_all("Student", fields=["name", "father_name", "father_email_id",
- "mother_name", "mother_email_id"])
- for stud in students:
- if stud.father_name:
- make_guardian(stud.father_name, stud.name, stud.father_email_id)
- if stud.mother_name:
- make_guardian(stud.mother_name, stud.name, stud.mother_email_id)
+ students = frappe.get_all("Student", fields)
+ for stud in students:
+ if stud.father_name:
+ make_guardian(stud.father_name, stud.name, stud.father_email_id)
+ if stud.mother_name:
+ make_guardian(stud.mother_name, stud.name, stud.mother_email_id)
def make_guardian(name, student, email=None):
frappe.get_doc({
diff --git a/erpnext/patches/v7_0/migrate_schools_to_erpnext.py b/erpnext/patches/v7_0/migrate_schools_to_erpnext.py
index 80e9eac..f64f400 100644
--- a/erpnext/patches/v7_0/migrate_schools_to_erpnext.py
+++ b/erpnext/patches/v7_0/migrate_schools_to_erpnext.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
import frappe, os
+from frappe.installer import remove_from_installed_apps
def execute():
reload_doctypes_for_schools_icons()
@@ -10,18 +11,15 @@
if 'schools' in frappe.get_installed_apps():
frappe.db.sql("""delete from `tabDesktop Icon`""")
- if not frappe.db.exists('Module Def', 'Schools'):
- frappe.get_doc({
- 'doctype': 'Module Def',
- 'module_name': 'Schools',
- 'app_name': 'erpnext'
- }).insert()
- frappe.db.sql("""update `tabDocType` set module='Schools' where module='Academics'""")
- from frappe.installer import remove_from_installed_apps
+
+ if not frappe.db.exists('Module Def', 'Schools') and frappe.db.exists('Module Def', 'Academics'):
+ frappe.rename_doc("Module Def", "Academics", "Schools")
+
remove_from_installed_apps("schools")
def reload_doctypes_for_schools_icons():
base_path = frappe.get_app_path('erpnext', 'schools', 'doctype')
for doctype in os.listdir(base_path):
- if os.path.exists(os.path.join(base_path, doctype, doctype + '.json')):
- frappe.reload_doc('schools', 'doctype', doctype)
+ if os.path.exists(os.path.join(base_path, doctype, doctype + '.json')) \
+ and doctype not in ("fee_component", "assessment", "assessment_result"):
+ frappe.reload_doc('schools', 'doctype', doctype)
\ No newline at end of file
diff --git a/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py b/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py
new file mode 100644
index 0000000..509c0a8
--- /dev/null
+++ b/erpnext/patches/v7_0/move_employee_parent_to_child_in_salary_structure.py
@@ -0,0 +1,11 @@
+import frappe
+
+def execute():
+ frappe.reload_doc('hr', 'doctype', 'salary_structure')
+ frappe.reload_doc('hr', 'doctype', 'salary_structure_employee')
+ for ss in frappe.db.sql(""" select employee, name from `tabSalary Structure`""", as_dict=True):
+ ss_doc = frappe.get_doc('Salary Structure', ss.name)
+ se = ss_doc.append('employees',{})
+ se.employee = ss.employee
+ se.base = 0
+ ss_doc.save()
diff --git a/erpnext/patches/v7_0/rename_examination_to_assessment.py b/erpnext/patches/v7_0/rename_examination_to_assessment.py
index 3c79c51..1d6e688 100644
--- a/erpnext/patches/v7_0/rename_examination_to_assessment.py
+++ b/erpnext/patches/v7_0/rename_examination_to_assessment.py
@@ -9,7 +9,9 @@
def execute():
if frappe.db.exists("DocType", "Examination"):
frappe.rename_doc("DocType", "Examination", "Assessment")
- frappe.reload_doctype("Assessment")
+ frappe.rename_doc("DocType", "Examination Result", "Assessment Result")
+ frappe.reload_doc("schools", "doctype", "assessment")
+ frappe.reload_doc("schools", "doctype", "assessment_result")
rename_field("Assessment", "exam_name", "assessment_name")
rename_field("Assessment", "exam_code", "assessment_code")
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 300933a..e443435 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -27,7 +27,7 @@
self.assertEquals(salary_slip.total_working_hours, 2)
self.assertEquals(salary_slip.hour_rate, 50)
- self.assertEquals(salary_slip.net_pay, 150)
+ self.assertEquals(salary_slip.net_pay, 50)
self.assertEquals(salary_slip.timesheets[0].time_sheet, timesheet.name)
self.assertEquals(salary_slip.timesheets[0].working_hours, 2)
@@ -54,35 +54,42 @@
timesheet = frappe.get_doc('Timesheet', timesheet.name)
self.assertEquals(sales_invoice.total_billing_amount, 100)
self.assertEquals(timesheet.status, 'Billed')
+
def make_salary_structure(employee):
- name = frappe.db.get_value('Salary Structure', {'employee': employee, 'salary_slip_based_on_timesheet': 1}, 'name')
+ name = frappe.db.get_value('Salary Structure Employee', {'employee': employee}, 'parent')
if name:
salary_structure = frappe.get_doc('Salary Structure', name)
else:
salary_structure = frappe.new_doc("Salary Structure")
+ salary_structure.name = "Timesheet Salary Structure Test"
+ salary_structure.salary_slip_based_on_timesheet = 1
+ salary_structure.from_date = nowdate()
+ salary_structure.salary_component = "Basic"
+ salary_structure.hour_rate = 50.0
+ salary_structure.company= "_Test Company"
- salary_structure.salary_slip_based_on_timesheet = 1
- salary_structure.employee = employee
- salary_structure.from_date = nowdate()
- salary_structure.salary_component = "Basic"
- salary_structure.hour_rate = 50.0
- salary_structure.company= "_Test Company"
+ salary_structure.set('employees', [])
+ salary_structure.set('earnings', [])
+ salary_structure.set('deductions', [])
- salary_structure.set('earnings', [])
- salary_structure.set('deductions', [])
+ es = salary_structure.append('employees', {
+ "employee": employee,
+ "base": 1200
+ })
+
+
+ es = salary_structure.append('earnings', {
+ "salary_component": "_Test Allowance",
+ "amount": 100
+ })
- es = salary_structure.append('earnings', {
- "salary_component": "_Test Allowance",
- "amount": 100
- })
+ ds = salary_structure.append('deductions', {
+ "salary_component": "_Test Professional Tax",
+ "amount": 50
+ })
- ds = salary_structure.append('deductions', {
- "salary_component": "_Test Professional Tax",
- "amount": 50
- })
-
- salary_structure.save(ignore_permissions=True)
+ salary_structure.save(ignore_permissions=True)
return salary_structure
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 9140927..0379f90 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -159,8 +159,8 @@
existing = self.get_overlap_for(fieldname, args, value)
if existing:
- frappe.throw(_("Row {0}: From Time and To Time overlap with existing from and to time").format(args.idx),
- OverlapError)
+ frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
+ .format(args.idx, self.name, existing.name), OverlapError)
def get_overlap_for(self, fieldname, args, value):
cond = "ts.`{0}`".format(fieldname)
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 340ebdb..ac1c7c3 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -26,7 +26,8 @@
"public/js/payment/pos_payment.html",
"public/js/payment/payment_details.html",
"public/js/templates/item_selector.html",
- "public/js/utils/item_selector.js"
+ "public/js/utils/item_selector.js",
+ "public/js/help_links.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css
index d67f9e0..535f83a 100644
--- a/erpnext/public/css/erpnext.css
+++ b/erpnext/public/css/erpnext.css
@@ -138,12 +138,14 @@
font-size: 15px;
}
.pos-payment-row .col-xs-6 {
- padding: 10px;
+ padding: 15px;
}
.pos-payment-row {
border-bottom: 1px solid #d1d8dd;
margin: 2px 0px 5px 0px;
height: 60px;
+ margin-top: 0px;
+ margin-bottom: 0px;
}
.pos-payment-row:hover,
.pos-keyboard-key:hover {
@@ -203,3 +205,8 @@
.pos-invoice-list {
padding: 15px 10px;
}
+.write_off_amount,
+.change_amount {
+ margin: 15px;
+ width: 130px;
+}
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index 233bd2e..d17bba8 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -16,8 +16,25 @@
$('[data-link="docs"]').attr("href", "https://manual.erpnext.com")
$('[data-link="issues"]').attr("href", "https://github.com/frappe/erpnext/issues")
+
+
+ // default documentation goes to erpnext
+ $('[data-link-type="documentation"]').attr('data-path', 'user/manual/index');
+
+ // additional help links for erpnext
+ var $help_menu = $('.dropdown-help ul .documentation-links');
+
+ $('<li><a data-link-type="forum" href="https://discuss.erpnext.com" \
+ target="_blank">'+__('User Forum')+'</a></li>').insertBefore($help_menu);
+ $('<li><a href="https://gitter.im/frappe/erpnext" \
+ target="_blank">'+__('Chat')+'</a></li>').insertBefore($help_menu);
+ $('<li><a href="https://github.com/frappe/erpnext/issues" \
+ target="_blank">'+__('Report an Issue')+'</a></li>').insertBefore($help_menu);
+
});
+
+
// doctypes created via tree
$.extend(frappe.create_routes, {
"Customer Group": "Tree/Customer Group",
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 576bc71..c3f4b70 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -615,4 +615,18 @@
this.frm.doc.base_change_amount = flt(this.frm.doc.change_amount * this.frm.doc.conversion_rate,
precision("base_change_amount"));
},
+
+ calculate_write_off_amount: function(){
+ if(this.frm.doc.paid_amount > this.frm.doc.grand_total){
+ this.frm.doc.write_off_amount = flt(this.frm.doc.grand_total - this.frm.doc.paid_amount + this.frm.doc.change_amount,
+ precision("write_off_amount"))
+
+ this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate,
+ precision("base_write_off_amount"));
+ }else{
+ this.frm.doc.paid_amount = 0.0
+ }
+
+ this.calculate_outstanding_amount(false)
+ }
})
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
new file mode 100644
index 0000000..3595d77
--- /dev/null
+++ b/erpnext/public/js/help_links.js
@@ -0,0 +1,162 @@
+frappe.provide('frappe.help.help_links');
+
+frappe.help.help_links['Form/Rename Tool'] = [
+ { label: 'Bulk Rename', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/data/bulk-rename.html' },
+]
+
+frappe.help.help_links['List/Customer'] = [
+ { label: 'Customer', url: 'https://frappe.github.io/erpnext/user/manual/en/CRM/customer.html' },
+]
+
+frappe.help.help_links['Form/Naming Series'] = [
+ { label: 'Naming Series', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/settings/naming-series' },
+ { label: 'Setting the Current Value for Naming Series', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/naming-series-current-value' },
+]
+
+frappe.help.help_links['Form/Global Defaults'] = [
+ { label: 'Global Settings', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/settings/global-defaults' },
+]
+
+frappe.help.help_links['Form/Email Digest'] = [
+ { label: 'Email Digest', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/email/email-digest' },
+]
+
+frappe.help.help_links['List/Print Heading'] = [
+ { label: 'Print Heading', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/print-headings' },
+]
+
+frappe.help.help_links['List/Letter Head'] = [
+ { label: 'Letter Head', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/letter-head' },
+]
+
+frappe.help.help_links['List/Address Template'] = [
+ { label: 'Address Template', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/address-template' },
+]
+
+frappe.help.help_links['List/Terms and Conditions'] = [
+ { label: 'Terms and Conditions', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/terms-and-conditions' },
+]
+
+frappe.help.help_links['List/Cheque Print Template'] = [
+ { label: 'Cheque Print Template', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/print/cheque-print-template' },
+]
+
+frappe.help.help_links['List/Sales Taxes and Charges Template'] = [
+ { label: 'Setting Up Taxes', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/setting-up-taxes' },
+]
+
+frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [
+ { label: 'Setting Up Taxes', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/setting-up-taxes' },
+]
+
+frappe.help.help_links['List/POS Profile'] = [
+ { label: 'POS Profile', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/pos-setting' },
+]
+
+frappe.help.help_links['List/Price List'] = [
+ { label: 'Price List', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/price-lists' },
+]
+
+frappe.help.help_links['List/Authorization Rule'] = [
+ { label: 'Authorization Rule', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/authorization-rule' },
+]
+
+frappe.help.help_links['Form/SMS Settings'] = [
+ { label: 'SMS Settings', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/sms-setting' },
+]
+
+frappe.help.help_links['List/Stock Reconciliation'] = [
+ { label: 'Stock Reconciliation', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' },
+]
+
+frappe.help.help_links['Tree/Territory'] = [
+ { label: 'Territory', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/territory' },
+]
+
+frappe.help.help_links['Form/Dropbox Backup'] = [
+ { label: 'Dropbox Backup', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/third-party-backups' },
+ { label: 'Setting Up Dropbox Backup', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/setting-up-dropbox-backups' },
+]
+
+frappe.help.help_links['List/Workflow'] = [
+ { label: 'Workflow', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/workflows' },
+]
+
+frappe.help.help_links['List/Company'] = [
+ { label: 'Company', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/company-setup' },
+ { label: 'Managing Multiple Companies', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/managing-multiple-companies' },
+ { label: 'Delete All Related Transactions for a Company', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' },
+]
+
+frappe.help.help_links['permission-manager'] = [
+ { label: 'Role Permissions Manager', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/users-and-permissions/role-based-permissions' },
+ { label: 'Managing Perm Level in Permissions Manager', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/managing-perm-level' },
+]
+
+frappe.help.help_links['data-import-tool'] = [
+ { label: 'Importing and Exporting Data', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/data/data-import-tool.html' },
+ { label: 'Overwriting Data from Data Import Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' },
+]
+
+//Accounts
+
+frappe.help.help_links['modules/Accounts'] = [
+ { label: 'Introduction to Accounts', url: 'http://frappe.github.io/erpnext/user/manual/en/accounts/' },
+ { label: 'Chart of Accounts', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/chart-of-accounts.html' },
+ { label: 'Multi Currency Accounting', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/multi-currency-accounting' },
+]
+
+frappe.help.help_links['Tree/Account'] = [
+ { label: 'Chart of Accounts', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/chart-of-accounts' },
+]
+
+frappe.help.help_links['List/Sales Invoice'] = [
+ { label: 'Sales Invoice', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/sales-invoice' },
+]
+
+frappe.help.help_links['pos'] = [
+ { label: 'Point of Sale Invoice', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/point-of-sale-pos-invoice' },
+]
+
+frappe.help.help_links['List/Purchase Invoice'] = [
+ { label: 'Purchase Invoice', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/purchase-invoice' },
+]
+
+frappe.help.help_links['List/Journal Entry'] = [
+ { label: 'Journal Entry', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/journal-entry' },
+ { label: 'Advance Payment Entry', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/advance-payment-entry' },
+]
+
+frappe.help.help_links['List/Payment Entry'] = [
+ { label: 'Payment Entry', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/payment-entry' },
+]
+
+frappe.help.help_links['List/Payment Request'] = [
+ { label: 'Payment Request', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/payment-request' },
+]
+
+frappe.help.help_links['List/Asset'] = [
+ { label: 'Managing Fixed Assets', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/managing-fixed-assets' },
+]
+
+frappe.help.help_links['Tree/Cost Center'] = [
+ { label: 'Budgeting', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/budgeting' },
+]
+
+frappe.help.help_links['List/Item'] = [
+ { label: 'Barcode', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/bar-code' },
+ { label: 'Item Wise Taxation', url: 'https://frappe.github.io/erpnext/user/manual/en/accounts/item-wise-taxation' },
+]
+
+frappe.help.help_links['List/Purchase Receipt'] = [
+ { label: 'Barcode', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/bar-code' },
+]
+
+frappe.help.help_links['List/Delivery Note'] = [
+ { label: 'Barcode', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/bar-code' },
+]
+
+frappe.help.help_links['Tree'] = [
+ { label: 'Managing Tree Structure Masters', url: 'https://frappe.github.io/erpnext/user/manual/en/setting-up/articles/managing-tree-structure-masters' },
+]
+
diff --git a/erpnext/public/js/payment/payment_details.html b/erpnext/public/js/payment/payment_details.html
index 18f9d03..596f139 100644
--- a/erpnext/public/js/payment/payment_details.html
+++ b/erpnext/public/js/payment/payment_details.html
@@ -1,5 +1,5 @@
<div class="row pos-payment-row" type="{{type}}" idx={{idx}}>
- <div class="col-xs-6">{{mode_of_payment}}</div>
+ <div class="col-xs-6" style="padding:20px">{{mode_of_payment}}</div>
<div class="col-xs-6">
<div class="input-group">
<input disabled class="form-control text-right amount" idx="{{idx}}" type="text" value="{{format_number(amount, 2)}}">
diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js
index 7437f2a..06df425 100644
--- a/erpnext/public/js/payment/payments.js
+++ b/erpnext/public/js/payment/payments.js
@@ -13,6 +13,14 @@
this.$body = this.dialog.body;
this.set_payment_primary_action();
this.make_keyboard();
+ this.select_text()
+ },
+
+ select_text: function(){
+ var me = this;
+ $(this.$body).find('.form-control').click(function(){
+ $(this).select();
+ })
},
set_payment_primary_action: function(){
@@ -20,7 +28,7 @@
this.dialog.set_primary_action(__("Submit"), function() {
me.dialog.hide()
- me.write_off_amount()
+ me.submit_invoice()
})
},
@@ -78,7 +86,7 @@
//When user first time click on row
this.payment_val = flt(this.frm.doc.outstanding_amount)
this.selected_mode.val(format_number(this.payment_val, 2));
- this.update_paid_amount()
+ this.update_payment_amount()
}else if(flt(this.selected_mode.val()) > 0){
//If user click on existing row which has value
this.payment_val = flt(this.selected_mode.val());
@@ -90,17 +98,29 @@
bind_keyboard_event: function(){
var me = this;
this.payment_val = '';
- this.bind_payment_mode_keys_event();
- this.bind_keyboard_keys_event();
+ this.bind_form_control_event();
+ this.bind_numeric_keys_event();
},
- bind_payment_mode_keys_event: function(){
+ bind_form_control_event: function(){
var me = this;
$(this.$body).find('.pos-payment-row').click(function(){
- if(me.frm.doc.outstanding_amount > 0){
- me.idx = $(this).attr("idx");
- me.set_outstanding_amount()
- }
+ me.idx = $(this).attr("idx");
+ me.set_outstanding_amount()
+ })
+
+ $(this.$body).find('.form-control').click(function(){
+ me.idx = $(this).attr("idx");
+ me.set_outstanding_amount();
+ me.update_paid_amount();
+ })
+
+ $(this.$body).find('.write_off_amount').change(function(){
+ me.write_off_amount(flt($(this).val()));
+ })
+
+ $(this.$body).find('.change_amount').change(function(){
+ me.change_amount(flt($(this).val()));
})
},
@@ -113,13 +133,13 @@
this.selected_mode.attr('disabled', false);
},
- bind_keyboard_keys_event: function(){
+ bind_numeric_keys_event: function(){
var me = this;
$(this.$body).find('.pos-keyboard-key').click(function(){
me.payment_val += $(this).text();
me.selected_mode.val(format_number(me.payment_val, 2))
me.idx = me.selected_mode.attr("idx")
- me.update_paid_amount()
+ me.selected_mode.change()
})
$(this.$body).find('.delete-btn').click(function(){
@@ -137,15 +157,11 @@
me.payment_val = flt($(this).val()) || 0.0;
me.selected_mode.val(format_number(me.payment_val, 2))
me.idx = me.selected_mode.attr("idx")
- me.update_paid_amount()
- })
-
- this.selected_mode.click(function(){
- me.selected_mode.select();
+ me.update_payment_amount()
})
},
- clear_amount: function(){
+ clear_amount: function() {
var me = this;
$(this.$body).find('.clr').click(function(e){
e.stopPropagation();
@@ -154,25 +170,65 @@
me.payment_val = 0.0;
me.selected_mode.val(0.0);
me.highlight_selected_row();
- me.update_paid_amount();
+ me.update_payment_amount();
})
},
- update_paid_amount: function(){
+ write_off_amount: function(write_off_amount) {
var me = this;
+
+ if(this.frm.doc.paid_amount > 0){
+ this.frm.doc.write_off_amount = write_off_amount;
+ this.frm.doc.base_write_off_amount = flt(this.frm.doc.write_off_amount * this.frm.doc.conversion_rate,
+ precision("base_write_off_amount"));
+ this.calculate_outstanding_amount(false)
+ this.show_amounts()
+ }
+ },
+
+ change_amount: function(change_amount) {
+ var me = this;
+
+ this.frm.doc.change_amount = change_amount;
+ this.calculate_write_off_amount()
+ this.show_amounts()
+ },
+
+ update_paid_amount: function() {
+ var me = this;
+ if(in_list(['change_amount', 'write_off_amount'], this.idx)){
+ value = flt(me.selected_mode.val(), 2)
+ if(me.idx == 'change_amount'){
+ me.change_amount(value)
+ } else{
+ if(value == 0) {
+ value = me.frm.doc.outstanding_amount;
+ }
+ me.write_off_amount(value)
+ }
+ }else{
+ this.update_payment_amount()
+ }
+ },
+
+ update_payment_amount: function(){
+ var me = this;
+
$.each(this.frm.doc.payments, function(index, data){
if(cint(me.idx) == cint(data.idx)){
data.amount = flt(me.selected_mode.val(), 2)
}
})
+
this.calculate_outstanding_amount(false);
this.show_amounts();
},
-
+
show_amounts: function(){
var me = this;
+ $(this.$body).find(".write_off_amount").val(format_number(this.frm.doc.write_off_amount, 2));
$(this.$body).find('.paid_amount').text(format_currency(this.frm.doc.paid_amount, this.frm.doc.currency));
- $(this.$body).find('.change_amount').text(format_currency(this.frm.doc.change_amount, this.frm.doc.currency))
+ $(this.$body).find('.change_amount').val(format_number(this.frm.doc.change_amount, 2))
$(this.$body).find('.outstanding_amount').text(format_currency(this.frm.doc.outstanding_amount, this.frm.doc.currency))
this.update_invoice();
}
diff --git a/erpnext/public/js/payment/pos_payment.html b/erpnext/public/js/payment/pos_payment.html
index c5d963a..e94f3a5 100644
--- a/erpnext/public/js/payment/pos_payment.html
+++ b/erpnext/public/js/payment/pos_payment.html
@@ -1,22 +1,28 @@
<div class="pos_payment row">
+ <div class="row" style="padding: 0px 30px;">
+ <h3>Total Amount: <span class="label label-default" style="font-size:20px;padding:5px">{%= format_currency(grand_total, currency) %}</span></h3>
+ </div>
<div class="row amount-row">
<div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> Total <h3>{%= format_currency(grand_total, currency) %} </h3></p>
- </div>
- <div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> Paid <h3 class="paid_amount">{%= format_currency(paid_amount, currency) %}</h3></p>
</div>
<div class="col-xs-6 col-sm-3 text-center">
<p class="amount-label"> Outstanding <h3 class="outstanding_amount">{%= format_currency(outstanding_amount, currency) %} </h3></p>
</div>
<div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> Change <h3 class="change_amount">{%= format_currency(change_amount, currency) %}</h3>
+ <p class="amount-label"> Change <input class="form-control text-right change_amount bold" type="text" idx="change_amount" value="{{format_number(change_amount, 2)}}">
+ </p>
+ </div>
+ <div class="col-xs-6 col-sm-3 text-center">
+ <p class="amount-label"> Write off <input class="form-control text-right write_off_amount bold" type="text" idx="write_off_amount" value="{{format_number(write_off_amount, 2)}}">
</p>
</div>
</div>
<hr>
<div class="row">
- <div class="col-sm-6 multimode-payments">
+ <div class="col-sm-6 ">
+ <div class ="row multimode-payments">
+ </div>
</div>
<div class="col-sm-6 payment-toolbar">
{% for(var i=0; i<3; i++) { %}
diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less
index d39f924..2d74b5f 100644
--- a/erpnext/public/less/erpnext.less
+++ b/erpnext/public/less/erpnext.less
@@ -176,13 +176,15 @@
}
.pos-payment-row .col-xs-6 {
- padding :10px;
+ padding :15px;
}
.pos-payment-row {
border-bottom:1px solid #d1d8dd;
margin: 2px 0px 5px 0px;
height: 60px;
+ margin-top: 0px;
+ margin-bottom: 0px;
}
.pos-payment-row:hover, .pos-keyboard-key:hover{
@@ -248,3 +250,8 @@
.pos-invoice-list {
padding: 15px 10px;
}
+
+.write_off_amount, .change_amount {
+ margin: 15px;
+ width: 130px;
+}
diff --git a/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.json b/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.json
index cbbc774..b5547d3 100644
--- a/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.json
+++ b/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.json
@@ -191,6 +191,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "depends_on": "eval:doc.get_students_from==\"Program Enrollments\"",
"fieldname": "new_program",
"fieldtype": "Link",
"hidden": 0,
@@ -217,6 +218,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "depends_on": "eval:doc.get_students_from==\"Program Enrollments\"",
"fieldname": "new_academic_year",
"fieldtype": "Link",
"hidden": 0,
@@ -275,7 +277,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2016-07-25 01:24:05.632746",
+ "modified": "2016-08-17 07:50:40.399492",
"modified_by": "Administrator",
"module": "Schools",
"name": "Program Enrollment Tool",
diff --git a/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.py
index b483ef5..6c10880 100644
--- a/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.py
+++ b/erpnext/schools/doctype/program_enrollment_tool/program_enrollment_tool.py
@@ -6,6 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from erpnext.schools.api import enroll_student
class ProgramEnrollmentTool(Document):
def get_students(self):
@@ -29,11 +30,16 @@
def enroll_students(self):
for stud in self.students:
- prog_enrollment = frappe.new_doc("Program Enrollment")
- prog_enrollment.student = stud.student
- prog_enrollment.student_name = stud.student_name
- prog_enrollment.program = self.new_program
- prog_enrollment.academic_year = self.new_academic_year
- prog_enrollment.save()
+ if stud.student:
+ prog_enrollment = frappe.new_doc("Program Enrollment")
+ prog_enrollment.student = stud.student
+ prog_enrollment.student_name = stud.student_name
+ prog_enrollment.program = self.new_program
+ prog_enrollment.academic_year = self.new_academic_year
+ prog_enrollment.save()
+ elif stud.student_applicant:
+ prog_enrollment = enroll_student(stud.student_applicant)
+ prog_enrollment.academic_year = self.academic_year
+ prog_enrollment.save()
frappe.msgprint("Students have been enrolled.")
\ No newline at end of file
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index d58adce..1ec8029 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -66,7 +66,8 @@
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
this.frm.set_query("item_code", "items", function() {
return {
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.controllers.queries.item_query",
+ filters: {'is_sales_item': 1}
}
});
}
@@ -79,7 +80,7 @@
} else {
filters = {
'item_code': item.item_code,
- 'posting_date': me.frm.doc.posting_date || nowdate(),
+ 'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
}
if(item.warehouse) filters["warehouse"] = item.warehouse
diff --git a/erpnext/setup/setup_wizard/sample_data.py b/erpnext/setup/setup_wizard/sample_data.py
index 8078ebc..955f663 100644
--- a/erpnext/setup/setup_wizard/sample_data.py
+++ b/erpnext/setup/setup_wizard/sample_data.py
@@ -11,7 +11,7 @@
def make_sample_data():
"""Create a few opportunities, quotes, material requests, issues, todos, projects
to help the user get started"""
- items = frappe.get_all("Item")
+ items = frappe.get_all("Item", {'is_sales_item': 1})
customers = frappe.get_all("Customer")
warehouses = frappe.get_all("Warehouse")
@@ -25,7 +25,7 @@
make_projects()
if items and warehouses:
- make_material_request(items)
+ make_material_request(frappe.get_all("Item"))
frappe.db.commit()
diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py
index c9679bd..191f169 100644
--- a/erpnext/setup/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/setup_wizard/setup_wizard.py
@@ -318,6 +318,8 @@
"item_name": item,
"description": item,
"show_in_website": 1,
+ "is_sales_item": is_sales_item,
+ "is_purchase_item": is_purchase_item,
"is_stock_item": is_stock_item and 1 or 0,
"item_group": item_group,
"stock_uom": args.get("item_uom_" + str(i)),
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 65658d6..ec2da53 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -12,6 +12,9 @@
return (doc.docstatus==1 || doc.qty<=doc.actual_qty) ? "green" : "orange"
})
+ erpnext.queries.setup_queries(frm, "Warehouse", function() {
+ return erpnext.queries.warehouse(frm.doc);
+ });
})
erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend({
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index b86378d..18fdad7 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -1058,6 +1058,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "default": "1",
+ "fieldname": "is_purchase_item",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Is Purchase Item",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"default": "0.00",
"depends_on": "is_stock_item",
"description": "",
@@ -1487,6 +1513,32 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "default": "1",
+ "fieldname": "is_sales_item",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Is Sales Item",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
"default": "0",
"description": "Publish Item to hub.erpnext.com",
"fieldname": "publish_in_hub",
@@ -2335,7 +2387,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 1,
- "modified": "2016-08-03 17:30:51.323382",
+ "modified": "2016-08-17 17:30:51.323382",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/support/page/support_analytics/support_analytics.js b/erpnext/support/page/support_analytics/support_analytics.js
index 562a169..79a7fd6 100644
--- a/erpnext/support/page/support_analytics/support_analytics.js
+++ b/erpnext/support/page/support_analytics/support_analytics.js
@@ -44,7 +44,7 @@
var std_columns = [
{id: "_check", name: __("Plot"), field: "_check", width: 30,
formatter: this.check_formatter},
- {id: "status", name: __("Status"), field: "status", width: 100},
+ {id: "name", name: __("Status"), field: "name", width: 100},
];
this.make_date_range_columns();
this.columns = std_columns.concat(this.columns);
@@ -54,14 +54,14 @@
// add Opening, Closing, Totals rows
// if filtered by account and / or voucher
var me = this;
- var total_tickets = {status:"All Tickets", "id": "all-tickets",
+ var total_tickets = {name:"All Tickets", "id": "all-tickets",
checked:true};
- var days_to_close = {status:"Days to Close", "id":"days-to-close",
+ var days_to_close = {name:"Days to Close", "id":"days-to-close",
checked:false};
var total_closed = {};
- var hours_to_close = {status:"Hours to Close", "id":"hours-to-close",
+ var hours_to_close = {name:"Hours to Close", "id":"hours-to-close",
checked:false};
- var hours_to_respond = {status:"Hours to Respond", "id":"hours-to-respond",
+ var hours_to_respond = {name:"Hours to Respond", "id":"hours-to-respond",
checked:false};
var total_responded = {};