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

This commit is contained in:
Himanshu Warekar 2019-05-17 00:48:23 +05:30
commit 628ea47c6d
138 changed files with 5390 additions and 8183 deletions

View File

@ -42,15 +42,14 @@ frappe.ui.form.on('Account', {
// show / hide convert buttons // show / hide convert buttons
frm.trigger('add_toolbar_buttons'); frm.trigger('add_toolbar_buttons');
} }
frm.add_custom_button(__('Update Account Name / Number'), function () { if (frm.has_perm('write')) {
frm.trigger("update_account_number"); frm.add_custom_button(__('Update Account Name / Number'), function () {
}); frm.trigger("update_account_number");
} });
frm.add_custom_button(__('Merge Account'), function () {
if(!frm.doc.__islocal) { frm.trigger("merge_account");
frm.add_custom_button(__('Merge Account'), function () { });
frm.trigger("merge_account"); }
});
} }
}, },
account_type: function (frm) { account_type: function (frm) {

View File

@ -268,7 +268,7 @@ def update_account_number(name, account_name, account_number=None):
new_name = get_account_autoname(account_number, account_name, account.company) new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name: if name != new_name:
frappe.rename_doc("Account", name, new_name, ignore_permissions=1) frappe.rename_doc("Account", name, new_name, force=1)
return new_name return new_name
@frappe.whitelist() @frappe.whitelist()
@ -287,7 +287,7 @@ def merge_account(old, new, is_group, root_type, company):
frappe.db.set_value("Account", new, "parent_account", frappe.db.set_value("Account", new, "parent_account",
frappe.db.get_value("Account", old, "parent_account")) frappe.db.get_value("Account", old, "parent_account"))
frappe.rename_doc("Account", old, new, merge=1, ignore_permissions=1) frappe.rename_doc("Account", old, new, merge=1, force=1)
return new return new

View File

@ -12,7 +12,7 @@
"Accrued Rebates Due from Suppliers": { "Accrued Rebates Due from Suppliers": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Accured Income from Suppliers": { "Accrued Income from Suppliers": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Other Debtors": { "Other Debtors": {
@ -54,7 +54,7 @@
} }
}, },
"Petty Cash": { "Petty Cash": {
"Petty Cash - Admininistration": { "Petty Cash - Administration": {
"account_type": "Cash" "account_type": "Cash"
}, },
"Petty Cash - Others": { "Petty Cash - Others": {
@ -85,13 +85,13 @@
"Handling Difference in Inventory": { "Handling Difference in Inventory": {
"account_type": "Stock Adjustment" "account_type": "Stock Adjustment"
}, },
"Items Delivered to Customs on temprary Base": {} "Items Delivered to Customs on temporary Base": {}
}, },
"Stock in Hand": { "Stock in Hand": {
"account_type": "Stock" "account_type": "Stock"
} }
}, },
"Perliminary and Preoperating Expenses": { "Preliminary and Preoperating Expenses": {
"Preoperating Expenses": {} "Preoperating Expenses": {}
}, },
"Prepayments & Deposits": { "Prepayments & Deposits": {
@ -150,16 +150,16 @@
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Leasehold Improvement": {}, "Leasehold Improvement": {},
"Motor Vehicules": { "Motor Vehicles": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Work In Progrees": {}, "Work In Progress": {},
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
} }
}, },
"Intangible Assets": { "Intangible Assets": {
"Computer Card Renewal": {}, "Computer Card Renewal": {},
"Dispoal of Outlets": {}, "Disposal of Outlets": {},
"Registration of Trademarks": {} "Registration of Trademarks": {}
}, },
"Intercompany Accounts": {}, "Intercompany Accounts": {},
@ -218,7 +218,7 @@
}, },
"MISC Charges": { "MISC Charges": {
"Other Charges": { "Other Charges": {
"Captial Loss": { "Capital Loss": {
"Disposal of Business Branch": {}, "Disposal of Business Branch": {},
"Loss On Fixed Assets Disposal": {}, "Loss On Fixed Assets Disposal": {},
"Loss on Difference on Exchange": {} "Loss on Difference on Exchange": {}
@ -253,14 +253,14 @@
"Other Bank Charges": {} "Other Bank Charges": {}
}, },
"Communications": { "Communications": {
"Courrier": {}, "Courier": {},
"Others - Communication": {}, "Others - Communication": {},
"Telephone": {}, "Telephone": {},
"Web Site Hosting Fees": {} "Web Site Hosting Fees": {}
}, },
"Office & Various Expenses": { "Office & Various Expenses": {
"Cleaning": {}, "Cleaning": {},
"Convoyance Expenses": {}, "Conveyance Expenses": {},
"Gifts & Donations": {}, "Gifts & Donations": {},
"Insurance": {}, "Insurance": {},
"Kitchen and Buffet Expenses": {}, "Kitchen and Buffet Expenses": {},
@ -325,7 +325,7 @@
"Current Liabilities": { "Current Liabilities": {
"Accounts Payable": { "Accounts Payable": {
"Payables": { "Payables": {
"Advance Paybale to Suppliers": { "Advance Payable to Suppliers": {
"account_type": "Payable" "account_type": "Payable"
}, },
"Consigned Payable": { "Consigned Payable": {

View File

@ -22,7 +22,5 @@ class BankGuarantee(Document):
@frappe.whitelist() @frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname): def get_vouchar_detials(column_list, doctype, docname):
print (column_list, doctype, docname)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0] .format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]

View File

@ -100,6 +100,7 @@ class PurchaseInvoice(BuyingController):
self.validate_fixed_asset() self.validate_fixed_asset()
self.create_remarks() self.create_remarks()
self.set_status() self.set_status()
self.validate_purchase_receipt_if_update_stock()
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference) validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
def validate_release_date(self): def validate_release_date(self):
@ -284,7 +285,7 @@ class PurchaseInvoice(BuyingController):
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.extend([{ self.status_updater.append({
'source_dt': 'Purchase Invoice Item', 'source_dt': 'Purchase Invoice Item',
'target_dt': 'Purchase Order Item', 'target_dt': 'Purchase Order Item',
'join_field': 'po_detail', 'join_field': 'po_detail',
@ -292,28 +293,29 @@ class PurchaseInvoice(BuyingController):
'target_parent_dt': 'Purchase Order', 'target_parent_dt': 'Purchase Order',
'target_parent_field': 'per_received', 'target_parent_field': 'per_received',
'target_ref_field': 'qty', 'target_ref_field': 'qty',
'source_field': 'qty', 'source_field': 'received_qty',
'second_source_dt': 'Purchase Receipt Item',
'second_source_field': 'received_qty',
'second_join_field': 'purchase_order_item',
'percent_join_field':'purchase_order', 'percent_join_field':'purchase_order',
# 'percent_join_field': 'prevdoc_docname',
'overflow_type': 'receipt', 'overflow_type': 'receipt',
'extra_cond': """ and exists(select name from `tabPurchase Invoice` 'extra_cond': """ and exists(select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""" where name=`tabPurchase Invoice Item`.parent and update_stock = 1)"""
}, })
{ if cint(self.is_return):
'source_dt': 'Purchase Invoice Item', self.status_updater.append({
'target_dt': 'Purchase Order Item', 'source_dt': 'Purchase Invoice Item',
'join_field': 'po_detail', 'target_dt': 'Purchase Order Item',
'target_field': 'returned_qty', 'join_field': 'po_detail',
'target_parent_dt': 'Purchase Order', 'target_field': 'returned_qty',
# 'target_parent_field': 'per_received', 'source_field': '-1 * qty',
# 'target_ref_field': 'qty', 'second_source_dt': 'Purchase Receipt Item',
'source_field': '-1 * qty', 'second_source_field': '-1 * qty',
# 'percent_join_field': 'prevdoc_docname', 'second_join_field': 'purchase_order_item',
# 'overflow_type': 'receipt', 'overflow_type': 'receipt',
'extra_cond': """ and exists (select name from `tabPurchase Invoice` 'extra_cond': """ and exists (select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)""" where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)"""
} })
])
def validate_purchase_receipt_if_update_stock(self): def validate_purchase_receipt_if_update_stock(self):
if self.update_stock: if self.update_stock:
@ -327,13 +329,13 @@ class PurchaseInvoice(BuyingController):
self.check_prev_docstatus() self.check_prev_docstatus()
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total) self.company, self.base_grand_total)
if not self.is_return: if not self.is_return:
self.update_against_document_in_jv() self.update_against_document_in_jv()
self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() self.update_billing_status_in_pr()
@ -763,9 +765,9 @@ class PurchaseInvoice(BuyingController):
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status()
if not self.is_return: if not self.is_return:
self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() self.update_billing_status_in_pr()

View File

@ -402,9 +402,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.save() pi.save()
pi.submit() pi.submit()
self.assertEqual(pi.payment_schedule[0].payment_amount, 756.15) self.assertEqual(pi.payment_schedule[0].payment_amount, 606.15)
self.assertEqual(pi.payment_schedule[0].due_date, pi.posting_date) self.assertEqual(pi.payment_schedule[0].due_date, pi.posting_date)
self.assertEqual(pi.payment_schedule[1].payment_amount, 756.15) self.assertEqual(pi.payment_schedule[1].payment_amount, 606.15)
self.assertEqual(pi.payment_schedule[1].due_date, add_days(pi.posting_date, 30)) self.assertEqual(pi.payment_schedule[1].due_date, add_days(pi.posting_date, 30))
pi.load_from_db() pi.load_from_db()

View File

@ -254,7 +254,7 @@ class SalesInvoice(SellingController):
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.extend([{ self.status_updater.append({
'source_dt':'Sales Invoice Item', 'source_dt':'Sales Invoice Item',
'target_dt':'Sales Order Item', 'target_dt':'Sales Order Item',
'target_parent_dt':'Sales Order', 'target_parent_dt':'Sales Order',
@ -272,21 +272,20 @@ class SalesInvoice(SellingController):
'overflow_type': 'delivery', 'overflow_type': 'delivery',
'extra_cond': """ and exists(select name from `tabSales Invoice` 'extra_cond': """ and exists(select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and update_stock = 1)""" where name=`tabSales Invoice Item`.parent and update_stock = 1)"""
}, })
{ if cint(self.is_return):
'source_dt': 'Sales Invoice Item', self.status_updater.append({
'target_dt': 'Sales Order Item', 'source_dt': 'Sales Invoice Item',
'join_field': 'so_detail', 'target_dt': 'Sales Order Item',
'target_field': 'returned_qty', 'join_field': 'so_detail',
'target_parent_dt': 'Sales Order', 'target_field': 'returned_qty',
# 'target_parent_field': 'per_delivered', 'target_parent_dt': 'Sales Order',
# 'target_ref_field': 'qty', 'source_field': '-1 * qty',
'source_field': '-1 * qty', 'second_source_dt': 'Delivery Note Item',
# 'percent_join_field': 'sales_order', 'second_source_field': '-1 * qty',
# 'overflow_type': 'delivery', 'second_join_field': 'so_detail',
'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)""" 'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)"""
} })
])
def check_credit_limit(self): def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
@ -506,8 +505,8 @@ class SalesInvoice(SellingController):
for i in dic: for i in dic:
if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes': if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes':
for d in self.get('items'): for d in self.get('items'):
if frappe.get_cached_value('Item', d.item_code, 'is_stock_item') == 1 \ if (d.item_code and frappe.get_cached_value('Item', d.item_code, 'is_stock_item') == 1
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1]): and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1) msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
@ -1166,6 +1165,9 @@ class SalesInvoice(SellingController):
self.set_missing_values(for_validate = True) self.set_missing_values(for_validate = True)
def validate_inter_company_party(doctype, party, company, inter_company_reference): def validate_inter_company_party(doctype, party, company, inter_company_reference):
if not party:
return
if doctype in ["Sales Invoice", "Sales Order"]: if doctype in ["Sales Invoice", "Sales Order"]:
partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer" partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer"

View File

@ -111,10 +111,10 @@
<th style="width: 4%">{%= __("Age (Days)") %}</th> <th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<th style="width: 16%">{%= __("Reference") %}</th> <th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th> <th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %} {% } else { %}
<th style="width: 26%">{%= __("Reference") %}</th> <th style="width: 24%">{%= __("Reference") %}</th>
{% } %} {% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th> <th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
@ -127,7 +127,7 @@
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Customer LPO No.") %}</th> <th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %} {% } %}
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th> <th style="width: 10%">{%= __("PDC/LC Ref") %}</th>
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th> <th style="width: 10%">{%= __("PDC/LC Amount") %}</th>

View File

@ -23,6 +23,12 @@ frappe.query_reports["Gross Profit"] = {
"fieldtype": "Date", "fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date") "default": frappe.defaults.get_user_default("year_end_date")
}, },
{
"fieldname":"sales_invoice",
"label": __("Sales Invoice"),
"fieldtype": "Link",
"options": "Sales Invoice"
},
{ {
"fieldname":"group_by", "fieldname":"group_by",
"label": __("Group By"), "label": __("Group By"),

View File

@ -302,6 +302,12 @@ class GrossProfitGenerator(object):
sales_person_cols = "" sales_person_cols = ""
sales_team_table = "" sales_team_table = ""
if self.filters.get("sales_invoice"):
conditions += " and `tabSales Invoice`.name = %(sales_invoice)s"
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
self.si_list = frappe.db.sql(""" self.si_list = frappe.db.sql("""
select select
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent, `tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,

View File

@ -4,6 +4,13 @@
frappe.query_reports["Inactive Sales Items"] = { frappe.query_reports["Inactive Sales Items"] = {
"filters": [ "filters": [
{
fieldname: "territory",
label: __("Territory"),
fieldtype: "Link",
options: "Territory",
reqd: 1,
},
{ {
fieldname: "item", fieldname: "item",
label: __("Item"), label: __("Item"),

View File

@ -7,13 +7,11 @@ from frappe.utils import getdate, add_days, today, cint
from frappe import _ from frappe import _
def execute(filters=None): def execute(filters=None):
columns = get_columns() columns = get_columns()
data = get_data(filters) data = get_data(filters)
return columns, data return columns, data
def get_columns(): def get_columns():
columns = [ columns = [
{ {
"fieldname": "territory", "fieldname": "territory",
@ -74,36 +72,39 @@ def get_columns():
def get_data(filters): def get_data(filters):
data = [] data = []
items = get_items(filters) items = get_items(filters)
territories = get_territories(filters)
sales_invoice_data = get_sales_details(filters) sales_invoice_data = get_sales_details(filters)
for item in items: for territory in territories:
row = { for item in items:
row = {
"territory": territory.name,
"item_group": item.item_group, "item_group": item.item_group,
"item": item.name, "item": item.name,
"item_name": item.item_name "item_name": item.item_name
} }
if sales_invoice_data.get(item.name): if sales_invoice_data.get((territory.name,item.name)):
item_obj = sales_invoice_data[item.name] item_obj = sales_invoice_data[(territory.name,item.name)]
if item_obj.days_since_last_order > cint(filters['days']): if item_obj.days_since_last_order > cint(filters['days']):
row.update({ row.update({
"territory": item_obj.territory, "territory": item_obj.territory,
"customer": item_obj.customer, "customer": item_obj.customer,
"last_order_date": item_obj.last_order_date, "last_order_date": item_obj.last_order_date,
"qty": item_obj.qty, "qty": item_obj.qty,
"days_since_last_order": item_obj.days_since_last_order "days_since_last_order": item_obj.days_since_last_order
}) })
else:
continue
data.append(row) data.append(row)
return data return data
def get_sales_details(filters): def get_sales_details(filters):
data = [] data = []
item_details_map = {} item_details_map = {}
@ -118,12 +119,21 @@ def get_sales_details(filters):
.format(date_field = date_field, doctype = filters['based_on']), as_dict=1) .format(date_field = date_field, doctype = filters['based_on']), as_dict=1)
for d in sales_data: for d in sales_data:
item_details_map.setdefault(d.item_name, d) item_details_map.setdefault((d.territory,d.item_name), d)
return item_details_map return item_details_map
def get_items(filters): def get_territories(filters):
filter_dict = {}
if filters.get("territory"):
filter_dict.update({'name': filters['territory']})
territories = frappe.get_all("Territory", fields=["name"], filters=filter_dict)
return territories
def get_items(filters):
filters_dict = { filters_dict = {
"disabled": 0, "disabled": 0,
"is_stock_item": 1 "is_stock_item": 1

View File

@ -135,3 +135,25 @@ def get_appropriate_company(filters):
company = get_default_company() company = get_default_company()
return company return company
@frappe.whitelist()
def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=None, with_item_data=False):
from erpnext.accounts.report.gross_profit.gross_profit import GrossProfitGenerator
sales_invoice = sales_invoice or frappe.form_dict.get('sales_invoice')
item_code = item_code or frappe.form_dict.get('item_code')
company = company or frappe.get_cached_value("Sales Invoice", sales_invoice, 'company')
filters = {
'sales_invoice': sales_invoice,
'item_code': item_code,
'company': company,
'group_by': 'Invoice'
}
gross_profit_data = GrossProfitGenerator(filters)
result = gross_profit_data.grouped_data
if not with_item_data:
result = sum([d.gross_profit for d in result])
return result

View File

@ -369,7 +369,9 @@ def make_purchase_receipt(source_name, target_doc=None):
"field_map": { "field_map": {
"name": "purchase_order_item", "name": "purchase_order_item",
"parent": "purchase_order", "parent": "purchase_order",
"bom": "bom" "bom": "bom",
"material_request": "material_request",
"material_request_item": "material_request_item"
}, },
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1

View File

@ -108,6 +108,69 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.get("items")[0].amount, 1400) self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
def test_update_qty(self):
po = create_purchase_order()
make_pr_against_po(po.name, 6)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
# Check received_qty after make_purchase_invoice without update_stock checked
pi1 = make_purchase_invoice(po.name)
pi1.get("items")[0].qty = 6
pi1.insert()
pi1.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
# Check received_qty after make_purchase_invoice with update_stock checked
pi2 = make_purchase_invoice(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
pi2.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 9)
def test_return_against_purchase_order(self):
po = create_purchase_order()
pr = make_pr_against_po(po.name, 6)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
pi2 = make_purchase_invoice(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
pi2.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 9)
# Make return purchase receipt, purchase invoice and check quantity
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
import make_purchase_receipt as make_purchase_receipt_return
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice \
import make_purchase_invoice as make_purchase_invoice_return
pr1 = make_purchase_receipt_return(is_return=1, return_against=pr.name, qty=-3, do_not_submit=True)
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
pr1.submit()
pi1= make_purchase_invoice_return(is_return=1, return_against=pi2.name, qty=-1, update_stock=1, do_not_submit=True)
pi1.items[0].purchase_order = po.name
pi1.items[0].po_detail = po.items[0].name
pi1.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True) po = create_purchase_order(do_not_submit=True)
@ -510,6 +573,13 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value("Accounts Settings", "Accounts Settings", frappe.db.set_value("Accounts Settings", "Accounts Settings",
"unlink_advance_payment_on_cancelation_of_order", 0) "unlink_advance_payment_on_cancelation_of_order", 0)
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
pr.get("items")[0].qty = received_qty or 5
pr.insert()
pr.submit()
return pr
def make_subcontracted_item(item_code): def make_subcontracted_item(item_code):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

View File

@ -0,0 +1,39 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Procurement Tracker"] = {
"filters": [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
},
{
fieldname: "cost_center",
label: __("Cost Center"),
fieldtype: "Link",
options: "Cost Center",
},
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project",
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
},
]
}

View File

@ -0,0 +1,20 @@
{
"add_total_row": 1,
"creation": "2019-03-29 17:05:45.196949",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-03-29 17:18:06.678728",
"modified_by": "Administrator",
"module": "Buying",
"name": "Procurement Tracker",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Purchase Order",
"report_name": "Procurement Tracker",
"report_type": "Script Report",
"roles": []
}

View File

@ -0,0 +1,282 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters)
return columns, data
def get_columns(filters):
columns = [
{
"label": _("Material Request Date"),
"fieldname": "material_request_date",
"fieldtype": "Date",
"width": 140
},
{
"label": _("Material Request No"),
"options": "Material Request",
"fieldname": "material_request_no",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Cost Center"),
"options": "Cost Center",
"fieldname": "cost_center",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Project"),
"options": "Project",
"fieldname": "project",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Requesting Site"),
"options": "Warehouse",
"fieldname": "requesting_site",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Requestor"),
"options": "Employee",
"fieldname": "requestor",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 200
},
{
"label": _("Quantity"),
"fieldname": "quantity",
"fieldtype": "Int",
"width": 140
},
{
"label": _("Unit of Measure"),
"options": "UOM",
"fieldname": "unit_of_measurement",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Status"),
"fieldname": "status",
"fieldtype": "data",
"width": 140
},
{
"label": _("Purchase Order Date"),
"fieldname": "purchase_order_date",
"fieldtype": "Date",
"width": 140
},
{
"label": _("Purchase Order"),
"options": "Purchase Order",
"fieldname": "purchase_order",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Supplier"),
"options": "Supplier",
"fieldname": "supplier",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Estimated Cost"),
"fieldname": "estimated_cost",
"fieldtype": "Float",
"width": 140
},
{
"label": _("Actual Cost"),
"fieldname": "actual_cost",
"fieldtype": "Float",
"width": 140
},
{
"label": _("Purchase Order Amount"),
"fieldname": "purchase_order_amt",
"fieldtype": "Float",
"width": 140
},
{
"label": _("Purchase Order Amount(Company Currency)"),
"fieldname": "purchase_order_amt_usd",
"fieldtype": "Float",
"width": 140
},
{
"label": _("Expected Delivery Date"),
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"width": 140
},
{
"label": _("Actual Delivery Date"),
"fieldname": "actual_delivery_date",
"fieldtype": "Date",
"width": 140
},
]
return columns
def get_conditions(filters):
conditions = ""
if filters.get("company"):
conditions += " AND company='%s'"% filters.get('company')
if filters.get("cost_center") or filters.get("project"):
conditions += """
AND (cost_center='%s'
OR project='%s')
"""% (filters.get('cost_center'), filters.get('project'))
if filters.get("from_date"):
conditions += "AND transaction_date>=%s"% filters.get('from_date')
if filters.get("to_date"):
conditions += "AND transaction_date<=%s"% filters.get('to_date')
return conditions
def get_data(filters):
conditions = get_conditions(filters)
purchase_order_entry = get_po_entries(conditions)
mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions)
pr_records = get_mapped_pr_records()
pi_records = get_mapped_pi_records()
print(pi_records)
procurement_record=[]
if procurement_record_against_mr:
procurement_record += procurement_record_against_mr
for po in purchase_order_entry:
# fetch material records linked to the purchase order item
mr_record = mr_records.get(po.material_request_item, [{}])[0]
procurement_detail = {
"material_request_date": mr_record.get('transaction_date'),
"cost_center": po.cost_center,
"project": po.project,
"requesting_site": po.warehouse,
"requestor": po.owner,
"material_request_no": po.material_request,
"description": po.description,
"quantity": po.qty,
"unit_of_measurement": po.stock_uom,
"status": po.status,
"purchase_order_date": po.transaction_date,
"purchase_order": po.parent,
"supplier": po.supplier,
"estimated_cost": mr_record.get('amount'),
"actual_cost": pi_records.get(po.name),
"purchase_order_amt": po.amount,
"purchase_order_amt_in_company_currency": po.base_amount,
"expected_delivery_date": po.schedule_date,
"actual_delivery_date": pr_records.get(po.name)
}
procurement_record.append(procurement_detail)
return procurement_record
def get_mapped_mr_details(conditions):
mr_records = {}
mr_details = frappe.db.sql("""
SELECT
mr.transaction_date,
mr.per_ordered,
mr_item.name,
mr_item.parent,
mr_item.amount
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
WHERE
mr.per_ordered>=0
AND mr.name=mr_item.parent
AND mr.docstatus=1
{conditions}
""".format(conditions=conditions), as_dict=1) #nosec
procurement_record_against_mr = []
for record in mr_details:
if record.per_ordered:
mr_records.setdefault(record.name, []).append(frappe._dict(record))
else:
procurement_record_details = dict(
material_request_date=record.transaction_date,
material_request_no=record.parent,
estimated_cost=record.amount
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
def get_mapped_pi_records():
return frappe._dict(frappe.db.sql("""
SELECT
pi_item.po_detail,
pi_item.base_amount
FROM `tabPurchase Invoice Item` as pi_item
INNER JOIN `tabPurchase Order` as po
ON pi_item.`purchase_order` = po.`name`
WHERE
pi_item.docstatus = 1
AND po.status not in ("Closed","Completed","Cancelled")
AND pi_item.po_detail IS NOT NULL
"""))
def get_mapped_pr_records():
return frappe._dict(frappe.db.sql("""
SELECT
pr_item.purchase_order_item,
pr.posting_date
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item
WHERE
pr.docstatus=1
AND pr.name=pr_item.parent
AND pr_item.purchase_order_item IS NOT NULL
AND pr.status not in ("Closed","Completed","Cancelled")
"""))
def get_po_entries(conditions):
return frappe.db.sql("""
SELECT
po_item.name,
po_item.parent,
po_item.cost_center,
po_item.project,
po_item.warehouse,
po_item.material_request,
po_item.material_request_item,
po_item.description,
po_item.stock_uom,
po_item.qty,
po_item.amount,
po_item.base_amount,
po_item.schedule_date,
po.transaction_date,
po.supplier,
po.status,
po.owner
FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item
WHERE
po.docstatus = 1
AND po.name = po_item.parent
AND po.status not in ("Closed","Completed","Cancelled")
{conditions}
GROUP BY
po.name,po_item.item_code
""".format(conditions=conditions), as_dict=1) #nosec

View File

@ -0,0 +1,67 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import unittest
from datetime import datetime
import frappe
from erpnext.buying.report.procurement_tracker.procurement_tracker import execute
from erpnext.stock.doctype.material_request.test_material_request import make_material_request
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestProcurementTracker(unittest.TestCase):
def test_result_for_procurement_tracker(self):
filters = {
'company': '_Test Procurement Company',
'cost_center': '_Test Cost Center - _TC'
}
expected_data = self.generate_expected_data()
report = execute(filters)
length = len(report[1])
self.assertEqual(expected_data, report[1][length-1])
def generate_expected_data(self):
if not frappe.db.exists("Company", "_Test Procurement Company"):
frappe.get_doc(dict(
doctype="Company",
company_name="_Test Procurement Company",
abbr="_TPC",
default_currency="INR",
country="India"
)).insert()
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse)
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.get("items")[0].cost_center = "_Test Cost Center - _TC"
po.submit()
pr = make_purchase_receipt(po.name)
pr.submit()
frappe.db.commit()
date_obj = datetime.date(datetime.now())
expected_data = {
"material_request_date": date_obj,
"cost_center": "_Test Cost Center - _TC",
"project": None,
"requesting_site": "_Test Procurement Warehouse - _TPC",
"requestor": "Administrator",
"material_request_no": mr.name,
"description": '_Test Item 1',
"quantity": 10.0,
"unit_of_measurement": "_Test UOM",
"status": "To Bill",
"purchase_order_date": date_obj,
"purchase_order": po.name,
"supplier": "_Test Supplier",
"estimated_cost": 0.0,
"actual_cost": None,
"purchase_order_amt": 5000.0,
"purchase_order_amt_in_company_currency": 300000.0,
"expected_delivery_date": date_obj,
"actual_delivery_date": date_obj
}
return expected_data

View File

@ -0,0 +1,29 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Subcontracted Item To Be Received"] = {
"filters": [
{
fieldname: "supplier",
label: __("Supplier"),
fieldtype: "Link",
options: "Supplier",
reqd: 1
},
{
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
reqd: 1
},
]
};

View File

@ -0,0 +1,30 @@
{
"add_total_row": 1,
"creation": "2019-05-03 11:25:03.685247",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-05-03 11:25:03.685247",
"modified_by": "Administrator",
"module": "Buying",
"name": "Subcontracted Item To Be Received",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Purchase Order",
"report_name": "Subcontracted Item To Be Received",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
}
]
}

View File

@ -0,0 +1,104 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute(filters=None):
if filters.from_date >= filters.to_date:
frappe.msgprint(_("To Date must be greater than From Date"))
data = []
columns = get_columns()
get_data(data , filters)
return columns, data
def get_columns():
return [
{
"label": _("Purchase Order"),
"fieldtype": "Link",
"fieldname": "purchase_order",
"options": "Purchase Order",
"width": 150
},
{
"label": _("Date"),
"fieldtype": "Date",
"fieldname": "date",
"hidden": 1,
"width": 150
},
{
"label": _("Supplier"),
"fieldtype": "Link",
"fieldname": "supplier",
"options": "Supplier",
"width": 150
},
{
"label": _("Finished Good Item Code"),
"fieldtype": "Data",
"fieldname": "fg_item_code",
"width": 100
},
{
"label": _("Item name"),
"fieldtype": "Data",
"fieldname": "item_name",
"width": 100
},
{
"label": _("Required Quantity"),
"fieldtype": "Float",
"fieldname": "required_qty",
"width": 100
},
{
"label": _("Received Quantity"),
"fieldtype": "Float",
"fieldname": "received_qty",
"width": 100
},
{
"label": _("Pending Quantity"),
"fieldtype": "Float",
"fieldname": "pending_qty",
"width": 100
}
]
def get_data(data, filters):
po = get_po(filters)
po_name = [v.name for v in po]
sub_items = get_purchase_order_item_supplied(po_name)
for item in sub_items:
for order in po:
if order.name == item.parent and item.received_qty < item.qty:
row ={
'purchase_order': item.parent,
'date': order.transaction_date,
'supplier': order.supplier,
'fg_item_code': item.item_code,
'item_name': item.item_name,
'required_qty': item.qty,
'received_qty':item.received_qty,
'pending_qty':item.qty - item.received_qty
}
data.append(row)
def get_po(filters):
record_filters = [
["is_subcontracted", "=", "Yes"],
["supplier", "=", filters.supplier],
["transaction_date", "<=", filters.to_date],
["transaction_date", ">=", filters.from_date],
["docstatus", "=", 1]
]
return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
def get_purchase_order_item_supplied(po):
return frappe.get_all("Purchase Order Item", filters=[
('parent', 'IN', po)
], fields=["parent", "item_code", "item_name", "qty", "received_qty"])

View File

@ -0,0 +1,36 @@
# Python bytecode 2.7 (62211)
# Embedded file name: /Users/anuragmishra/frappe-develop/apps/erpnext/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
# Compiled at: 2019-05-06 09:51:46
# Decompiled by https://python-decompiler.com
from __future__ import unicode_literals
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_to_be_received import execute
import frappe, unittest
from pprint import pprint
class TestSubcontractedItemToBeReceived(unittest.TestCase):
def test_pending_and_received_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
transfer_param = []
make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
make_purchase_receipt_against_po(po.name)
po.reload()
col, data = execute(filters=frappe._dict({'supplier': po.supplier,
'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
self.assertEqual(data[0]['pending_qty'], 5)
self.assertEqual(data[0]['received_qty'], 5)
self.assertEqual(data[0]['purchase_order'], po.name)
self.assertEqual(data[0]['supplier'], po.supplier)
def make_purchase_receipt_against_po(po, quantity=5):
pr = make_purchase_receipt(po)
pr.items[0].qty = quantity
pr.supplier_warehouse = '_Test Warehouse 1 - _TC'
pr.insert()
pr.submit()

View File

@ -0,0 +1,29 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
"filters": [
{
fieldname: "supplier",
label: __("Supplier"),
fieldtype: "Link",
options: "Supplier",
reqd: 1
},
{
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
reqd: 1
},
]
}

View File

@ -0,0 +1,30 @@
{
"add_total_row": 1,
"creation": "2019-05-03 12:04:14.438345",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-05-03 12:04:24.203721",
"modified_by": "Administrator",
"module": "Buying",
"name": "Subcontracted Raw Materials To Be Transferred",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Purchase Order",
"report_name": "Subcontracted Raw Materials To Be Transferred",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
}
]
}

View File

@ -0,0 +1,137 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute(filters=None):
if filters.from_date >= filters.to_date:
frappe.msgprint(_("To Date must be greater than From Date"))
data = []
columns = get_columns()
get_data(data , filters)
return columns, data
def get_columns():
return [
{
"label": _("Purchase Order"),
"fieldtype": "Link",
"fieldname": "purchase_order",
"options": "Purchase Order",
"width": 150
},
{
"label": _("Date"),
"fieldtype": "Date",
"fieldname": "date",
"hidden": 1,
"width": 150
},
{
"label": _("Supplier"),
"fieldtype": "Link",
"fieldname": "supplier",
"options": "Supplier",
"width": 150
},
{
"label": _("Item Code"),
"fieldtype": "Data",
"fieldname": "rm_item_code",
"width": 100
},
{
"label": _("Required Quantity"),
"fieldtype": "Float",
"fieldname": "r_qty",
"width": 100
},
{
"label": _("Transferred Quantity"),
"fieldtype": "Float",
"fieldname": "t_qty",
"width": 100
},
{
"label": _("Pending Quantity"),
"fieldtype": "Float",
"fieldname": "p_qty",
"width": 100
}
]
def get_data(data, filters):
po = get_po(filters)
po_transferred_qty_map = frappe._dict(get_transferred_quantity([v.name for v in po]))
sub_items = get_purchase_order_item_supplied([v.name for v in po])
for order in po:
for item in sub_items:
if order.name == item.parent and order.name in po_transferred_qty_map and \
item.required_qty != po_transferred_qty_map.get(order.name).get(item.rm_item_code):
transferred_qty = po_transferred_qty_map.get(order.name).get(item.rm_item_code) \
if po_transferred_qty_map.get(order.name).get(item.rm_item_code) else 0
row ={
'purchase_order': item.parent,
'date': order.transaction_date,
'supplier': order.supplier,
'rm_item_code': item.rm_item_code,
'r_qty': item.required_qty,
't_qty':transferred_qty,
'p_qty':item.required_qty - transferred_qty
}
data.append(row)
return(data)
def get_po(filters):
record_filters = [
["is_subcontracted", "=", "Yes"],
["supplier", "=", filters.supplier],
["transaction_date", "<=", filters.to_date],
["transaction_date", ">=", filters.from_date],
["docstatus", "=", 1]
]
return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
def get_transferred_quantity(po_name):
stock_entries = get_stock_entry(po_name)
stock_entries_detail = get_stock_entry_detail([v.name for v in stock_entries])
po_transferred_qty_map = {}
for entry in stock_entries:
for details in stock_entries_detail:
if details.parent == entry.name:
details["Purchase_order"] = entry.purchase_order
if entry.purchase_order not in po_transferred_qty_map:
po_transferred_qty_map[entry.purchase_order] = {}
po_transferred_qty_map[entry.purchase_order][details.item_code] = details.qty
else:
po_transferred_qty_map[entry.purchase_order][details.item_code] = po_transferred_qty_map[entry.purchase_order].get(details.item_code, 0) + details.qty
return po_transferred_qty_map
def get_stock_entry(po):
return frappe.get_all("Stock Entry", filters=[
('purchase_order', 'IN', po),
('stock_entry_type', '=', 'Send to Subcontractor'),
('docstatus', '=', 1)
], fields=["name", "purchase_order"])
def get_stock_entry_detail(se):
return frappe.get_all("Stock Entry Detail", filters=[
["parent", "in", se]
],
fields=["parent", "item_code", "qty"])
def get_purchase_order_item_supplied(po):
return frappe.get_all("Purchase Order Item Supplied", filters=[
('parent', 'IN', po)
], fields=['parent', 'rm_item_code', 'required_qty'])

View File

@ -0,0 +1,44 @@
# Python bytecode 2.7 (62211)
# Embedded file name: /Users/anuragmishra/frappe-develop/apps/erpnext/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
# Compiled at: 2019-05-06 10:24:35
# Decompiled by https://python-decompiler.com
from __future__ import unicode_literals
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import execute
import json, frappe, unittest
class TestSubcontractedItemToBeReceived(unittest.TestCase):
def test_pending_and_received_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
make_stock_entry(item_code='_Test Item', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse 1 - _TC', qty=100, basic_rate=100)
transfer_subcontracted_raw_materials(po.name)
col, data = execute(filters=frappe._dict({'supplier': po.supplier,
'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
self.assertEqual(data[0]['purchase_order'], po.name)
self.assertIn(data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
self.assertIn(data[0]['p_qty'], [9, 18])
self.assertIn(data[0]['t_qty'], [1, 2])
self.assertEqual(data[1]['purchase_order'], po.name)
self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
self.assertIn(data[1]['p_qty'], [9, 18])
self.assertIn(data[1]['t_qty'], [1, 2])
def transfer_subcontracted_raw_materials(po):
rm_item = [
{'item_code': '_Test Item', 'rm_item_code': '_Test Item', 'item_name': '_Test Item', 'qty': 1,
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100, 'stock_uom': 'Nos'},
{'item_code': '_Test Item Home Desktop 100', 'rm_item_code': '_Test Item Home Desktop 100', 'item_name': '_Test Item Home Desktop 100', 'qty': 2,
'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
rm_item_string = json.dumps(rm_item)
se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
se.to_warehouse = '_Test Warehouse 1 - _TC'
se.stock_entry_type = 'Send to Subcontractor'
se.save()
se.submit()

View File

@ -10,7 +10,7 @@ def get_data():
{ {
"type": "doctype", "type": "doctype",
"name": "Customer", "name": "Customer",
"description": _("Customer database."), "description": _("Customer Database."),
"onboard": 1, "onboard": 1,
}, },
{ {
@ -34,6 +34,13 @@ def get_data():
"onboard": 1, "onboard": 1,
"dependencies": ["Item", "Customer"], "dependencies": ["Item", "Customer"],
}, },
{
"type": "doctype",
"name": "Blanket Order",
"description": _("Blanket Orders from Costumers."),
"onboard": 1,
"dependencies": ["Item", "Customer"],
},
{ {
"type": "doctype", "type": "doctype",
"name": "Sales Partner", "name": "Sales Partner",
@ -292,6 +299,12 @@ def get_data():
"name": "Customers Without Any Sales Transactions", "name": "Customers Without Any Sales Transactions",
"doctype": "Customer" "doctype": "Customer"
}, },
{
"type": "report",
"is_query_report": True,
"name": "Sales Partners Commission",
"doctype": "Customer"
}
] ]
}, },
{ {

View File

@ -90,12 +90,6 @@ def get_data():
"label": _("Email"), "label": _("Email"),
"icon": "fa fa-envelope", "icon": "fa fa-envelope",
"items": [ "items": [
{
"type": "doctype",
"name": "Feedback Trigger",
"label": _("Feedback Trigger"),
"description": _("Automatically triggers the feedback request based on conditions.")
},
{ {
"type": "doctype", "type": "doctype",
"name": "Email Digest", "name": "Email Digest",

View File

@ -787,6 +787,9 @@ class AccountsController(TransactionBase):
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount) grand_total = grand_total - flt(self.write_off_amount)
if self.get("total_advance"):
grand_total -= self.get("total_advance")
if not self.get("payment_schedule"): if not self.get("payment_schedule"):
if self.get("payment_terms_template"): if self.get("payment_terms_template"):
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
@ -832,6 +835,9 @@ class AccountsController(TransactionBase):
total = flt(total, self.precision("grand_total")) total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
if self.get("total_advance"):
grand_total -= self.get("total_advance")
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount) grand_total = grand_total - flt(self.write_off_amount)
if total != grand_total: if total != grand_total:

View File

@ -124,16 +124,8 @@ def find_variant(template, args, variant_item_code=None):
conditions = " or ".join(conditions) conditions = " or ".join(conditions)
# use approximate match and shortlist possible variant matches from erpnext.portal.product_configurator.utils import get_item_codes_by_attributes
# it is approximate because we are matching using OR condition possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
# and it need not be exact match at this stage
# this uses a simpler query instead of using multiple exists conditions
possible_variants = frappe.db.sql_list("""select name from `tabItem` item
where variant_of=%s and exists (
select name from `tabItem Variant Attribute` iv_attribute
where iv_attribute.parent=item.name
and ({conditions}) and parent != %s
)""".format(conditions=conditions), (template, cstr(variant_item_code)))
for variant in possible_variants: for variant in possible_variants:
variant = frappe.get_doc("Item", variant) variant = frappe.get_doc("Item", variant)
@ -317,7 +309,7 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
}, as_dict=True) }, as_dict=True)
if not item_attribute: if not item_attribute:
return continue
# frappe.throw(_('Invalid attribute {0} {1}').format(frappe.bold(attr.attribute), # frappe.throw(_('Invalid attribute {0} {1}').format(frappe.bold(attr.attribute),
# frappe.bold(attr.attribute_value)), title=_('Invalid Attribute'), # frappe.bold(attr.attribute_value)), title=_('Invalid Attribute'),
# exc=InvalidItemAttributeValueError) # exc=InvalidItemAttributeValueError)

View File

@ -253,11 +253,13 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
and return_against in (select name from `tabDelivery Note` where per_billed < 100) and return_against in (select name from `tabDelivery Note` where per_billed < 100)
) )
) )
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc %(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
""" % { """ % {
"key": searchfield, "key": searchfield,
"fcond": get_filters_cond(doctype, filters, []), "fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
"start": start,
"page_len": page_len,
"txt": "%(txt)s" "txt": "%(txt)s"
}, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict) }, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)

View File

@ -96,7 +96,9 @@ status_map = {
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"], ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"], ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"], ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"] ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"]
] ]
} }

View File

@ -5,29 +5,32 @@ frappe.provide("erpnext");
cur_frm.email_field = "email_id"; cur_frm.email_field = "email_id";
erpnext.LeadController = frappe.ui.form.Controller.extend({ erpnext.LeadController = frappe.ui.form.Controller.extend({
setup: function() { setup: function () {
this.frm.fields_dict.customer.get_query = function(doc, cdt, cdn) { this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {
return { query: "erpnext.controllers.queries.customer_query" } } return { query: "erpnext.controllers.queries.customer_query" }
}
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
}, },
onload: function() { onload: function () {
if (cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) {
if(cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) { cur_frm.fields_dict.lead_owner.get_query = function (doc, cdt, cdn) {
cur_frm.fields_dict.lead_owner.get_query = function(doc, cdt, cdn) {
return { query: "frappe.core.doctype.user.user.user_query" } return { query: "frappe.core.doctype.user.user.user_query" }
} }
} }
if(cur_frm.fields_dict.contact_by.df.options.match(/^User/)) { if (cur_frm.fields_dict.contact_by.df.options.match(/^User/)) {
cur_frm.fields_dict.contact_by.get_query = function(doc, cdt, cdn) { cur_frm.fields_dict.contact_by.get_query = function (doc, cdt, cdn) {
return { query: "frappe.core.doctype.user.user.user_query" } } return { query: "frappe.core.doctype.user.user.user_query" }
}
} }
}, },
refresh: function() { refresh: function () {
var doc = this.frm.doc; var doc = this.frm.doc;
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
frappe.dynamic_link = {doc: doc, fieldname: 'name', doctype: 'Lead'} frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) { if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.create_customer, __('Create')); this.frm.add_custom_button(__("Customer"), this.create_customer, __('Create'));
@ -35,49 +38,46 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
this.frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create')); this.frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create'));
} }
if(!this.frm.doc.__islocal) { if (!this.frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(cur_frm); frappe.contacts.render_address_and_contact(cur_frm);
} else { } else {
frappe.contacts.clear_address_and_contact(cur_frm); frappe.contacts.clear_address_and_contact(cur_frm);
} }
}, },
create_customer: function() { create_customer: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_customer", method: "erpnext.crm.doctype.lead.lead.make_customer",
frm: cur_frm frm: cur_frm
}) })
}, },
create_opportunity: function() { create_opportunity: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_opportunity", method: "erpnext.crm.doctype.lead.lead.make_opportunity",
frm: cur_frm frm: cur_frm
}) })
}, },
make_quotation: function() { make_quotation: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_quotation", method: "erpnext.crm.doctype.lead.lead.make_quotation",
frm: cur_frm frm: cur_frm
}) })
}, },
organization_lead: function() { organization_lead: function () {
if (this.frm.doc.organization_lead == 1) { this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
this.frm.set_df_property('company_name', 'reqd', 1); this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead);
} else {
this.frm.set_df_property('company_name', 'reqd', 0);
}
}, },
company_name: function() { company_name: function () {
if (this.frm.doc.organization_lead == 1) { if (this.frm.doc.organization_lead == 1) {
this.frm.set_value("lead_name", this.frm.doc.company_name); this.frm.set_value("lead_name", this.frm.doc.company_name);
} }
}, },
contact_date: function() { contact_date: function () {
if (this.frm.doc.contact_date) { if (this.frm.doc.contact_date) {
let d = moment(this.frm.doc.contact_date); let d = moment(this.frm.doc.contact_date);
d.add(1, "hours"); d.add(1, "hours");
@ -86,4 +86,4 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
} }
}); });
$.extend(cur_frm.cscript, new erpnext.LeadController({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));

View File

@ -145,7 +145,7 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
@ -268,7 +268,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Lead Owner", "label": "Lead Owner",
"length": 0, "length": 0,
@ -1419,17 +1419,18 @@
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 5, "idx": 5,
"image_field": "image", "image_field": "image",
"image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "modified": "2019-05-10 03:22:57.283628",
"modified": "2019-04-11 22:12:50.029368",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead", "name": "Lead",

View File

@ -109,7 +109,11 @@ class Lead(SellingController):
def set_lead_name(self): def set_lead_name(self):
if not self.lead_name: if not self.lead_name:
frappe.db.set_value("Lead", self.name, "lead_name", self.company_name) # Check for leads being created through data import
if not self.company_name:
frappe.throw(_("A Lead requires either a person's name or an organization's name"))
self.lead_name = self.company_name
@frappe.whitelist() @frappe.whitelist()
def make_customer(source_name, target_doc=None): def make_customer(source_name, target_doc=None):

View File

@ -21,6 +21,17 @@ frappe.ui.form.on("Opportunity", {
frm.trigger('set_contact_link'); frm.trigger('set_contact_link');
}, },
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
party_name: function(frm) {
if (frm.doc.opportunity_from == "Customer") {
frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm);
}
},
with_items: function(frm) { with_items: function(frm) {
frm.trigger('toggle_mandatory'); frm.trigger('toggle_mandatory');
}, },
@ -180,4 +191,3 @@ cur_frm.cscript.lead = function(doc, cdt, cdn) {
frm: cur_frm frm: cur_frm
}); });
} }

View File

@ -20,9 +20,9 @@ class Course(Document):
frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%")) frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%"))
def get_topics(self): def get_topics(self):
try: topic_data= []
topic_list = self.get_all_children() for topic in self.topics:
topic_data = [frappe.get_doc("Topic", topic.topic) for topic in topic_list] topic_doc = frappe.get_doc("Topic", topic.topic)
except frappe.DoesNotExistError: if topic_doc.topic_content:
return None topic_data.append(topic_doc)
return topic_data return topic_data

View File

@ -3,6 +3,7 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
from erpnext.education.doctype.topic.test_topic import make_topic from erpnext.education.doctype.topic.test_topic import make_topic
from erpnext.education.doctype.topic.test_topic import make_topic_and_linked_content
import frappe import frappe
import unittest import unittest
@ -11,6 +12,8 @@ import unittest
class TestCourse(unittest.TestCase): class TestCourse(unittest.TestCase):
def setUp(self): def setUp(self):
make_topic_and_linked_content("_Test Topic 1", [{"type":"Article", "name": "_Test Article 1"}])
make_topic_and_linked_content("_Test Topic 2", [{"type":"Article", "name": "_Test Article 2"}])
make_course_and_linked_topic("_Test Course 1", ["_Test Topic 1", "_Test Topic 2"]) make_course_and_linked_topic("_Test Course 1", ["_Test Topic 1", "_Test Topic 2"])
def test_get_topics(self): def test_get_topics(self):

View File

@ -21,7 +21,10 @@ class CourseEnrollment(Document):
progress = [] progress = []
for topic in topics: for topic in topics:
progress.append(student.get_topic_progress(self.name, topic)) progress.append(student.get_topic_progress(self.name, topic))
return reduce(lambda x,y: x+y, progress) # Flatten out the List if progress:
return reduce(lambda x,y: x+y, progress) # Flatten out the List
else:
return []
def validate_duplication(self): def validate_duplication(self):
enrollment = frappe.get_all("Course Enrollment", filters={ enrollment = frappe.get_all("Course Enrollment", filters={

View File

@ -1,492 +1,136 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-04-05 13:33:04.519313", "creation": "2017-04-05 13:33:04.519313",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"current_academic_year",
"current_academic_term",
"attendance_freeze_date",
"column_break_4",
"validate_batch",
"validate_course",
"academic_term_reqd",
"section_break_7",
"instructor_created_by",
"web_academy_settings_section",
"enable_lms",
"portal_title",
"description"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_academic_year", "fieldname": "current_academic_year",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Academic Year", "label": "Current Academic Year",
"length": 0, "options": "Academic Year"
"no_copy": 0,
"options": "Academic Year",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "current_academic_term", "fieldname": "current_academic_term",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Academic Term", "label": "Current Academic Term",
"length": 0, "options": "Academic Term"
"no_copy": 0,
"options": "Academic Term",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "attendance_freeze_date", "fieldname": "attendance_freeze_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "label": "Attendance Freeze Date"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attendance Freeze Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.", "description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.",
"fieldname": "validate_batch", "fieldname": "validate_batch",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Validate Batch for Students in Student Group"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Validate Batch for Students in Student Group",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.", "description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.",
"fieldname": "validate_course", "fieldname": "validate_course",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Validate Enrolled Course for Students in Student Group"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Validate Enrolled Course for Students in Student Group",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0", "default": "0",
"description": "If enabled, field Academic Term will be Mandatory in Program Enrollment Tool.", "description": "If enabled, field Academic Term will be Mandatory in Program Enrollment Tool.",
"fieldname": "academic_term_reqd", "fieldname": "academic_term_reqd",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Make Academic Term Mandatory"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Make Academic Term Mandatory",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7", "fieldname": "section_break_7",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Full Name", "default": "Full Name",
"fieldname": "instructor_created_by", "fieldname": "instructor_created_by",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Instructor Records to be created by", "label": "Instructor Records to be created by",
"length": 0, "options": "Full Name\nNaming Series\nEmployee Number"
"no_copy": 0,
"options": "Full Name\nNaming Series\nEmployee Number",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "web_academy_settings_section", "fieldname": "web_academy_settings_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "LMS Settings"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LMS Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "depends_on": "eval: doc.enable_lms",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "portal_title", "fieldname": "portal_title",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "label": "LMS Title"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Portal Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "depends_on": "eval: doc.enable_lms",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description", "fieldname": "description",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"hidden": 0, "label": "Description"
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0, {
"in_filter": 0, "fieldname": "enable_lms",
"in_global_search": 0, "fieldtype": "Check",
"in_list_view": 0, "label": "Enable LMS"
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "modified": "2019-05-13 18:36:13.127563",
"max_attachments": 0,
"modified": "2018-12-11 15:49:15.045116",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Education Settings", "name": "Education Settings",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "Education Manager", "role": "Education Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "Guest", "role": "Guest",
"set_user_permissions": 0, "share": 1
"share": 1,
"submit": 0,
"write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education", "restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -34,3 +34,6 @@ class EducationSettings(Document):
make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") make_property_setter('Instructor', "naming_series", "hidden", 0, "Check")
else: else:
make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") make_property_setter('Instructor', "naming_series", "hidden", 1, "Check")
def update_website_context(context):
context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms

View File

@ -9,6 +9,6 @@ from frappe.model.document import Document
class Program(Document): class Program(Document):
def get_course_list(self): def get_course_list(self):
program_course_list = self.get_all_children() program_course_list = self.courses
course_list = [frappe.get_doc("Course", program_course.course) for program_course in program_course_list] course_list = [frappe.get_doc("Course", program_course.course) for program_course in program_course_list]
return course_list return course_list

View File

@ -90,13 +90,14 @@ class Student(Document):
""" """
contents = topic.get_contents() contents = topic.get_contents()
progress = [] progress = []
for content in contents: if contents:
if content.doctype in ('Article', 'Video'): for content in contents:
status = check_content_completion(content.name, content.doctype, course_enrollment_name) if content.doctype in ('Article', 'Video'):
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status}) status = check_content_completion(content.name, content.doctype, course_enrollment_name)
elif content.doctype == 'Quiz': progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
status, score, result = check_quiz_completion(content, course_enrollment_name) elif content.doctype == 'Quiz':
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result}) status, score, result = check_quiz_completion(content, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
return progress return progress
def enroll_in_program(self, program_name): def enroll_in_program(self, program_name):

View File

@ -9,7 +9,7 @@ from frappe.model.document import Document
class Topic(Document): class Topic(Document):
def get_contents(self): def get_contents(self):
try: try:
topic_content_list = self.get_all_children() topic_content_list = self.topic_content
content_data = [frappe.get_doc(topic_content.content_type, topic_content.content) for topic_content in topic_content_list] content_data = [frappe.get_doc(topic_content.content_type, topic_content.content) for topic_content in topic_content_list]
except Exception as e: except Exception as e:
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())

View File

@ -1,140 +1,44 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-12-12 11:42:57.987434", "creation": "2018-12-12 11:42:57.987434",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"content_type",
"column_break_2",
"content"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content_type", "fieldname": "content_type",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Content Type", "label": "Content Type",
"length": 0,
"no_copy": 0,
"options": "\nArticle\nVideo\nQuiz", "options": "\nArticle\nVideo\nQuiz",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2", "fieldname": "column_break_2",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content", "fieldname": "content",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Content", "label": "Content",
"length": 0,
"no_copy": 0,
"options": "content_type", "options": "content_type",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "modified": "2019-05-14 11:12:49.153771",
"modified": "2018-12-12 11:46:46.112018",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Topic Content", "name": "Topic Content",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -50,7 +50,7 @@ on_logout = "erpnext.shopping_cart.utils.clear_cart_count"
treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Group', 'Sales Person', 'Territory', 'Assessment Group'] treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Group', 'Sales Person', 'Territory', 'Assessment Group']
# website # website
update_website_context = "erpnext.shopping_cart.utils.update_website_context" update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
my_account_context = "erpnext.shopping_cart.utils.update_my_account_context" my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"] email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"]

View File

@ -3,13 +3,8 @@
frappe.ui.form.on('Additional Salary', { frappe.ui.form.on('Additional Salary', {
setup: function(frm) { setup: function(frm) {
frm.set_query("salary_component", function() { frm.add_fetch("salary_component", "deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date");
return {
filters: {
is_additional_component: true
}
};
});
frm.set_query("employee", function() { frm.set_query("employee", function() {
return { return {
filters: { filters: {

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
@ -21,6 +22,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -54,6 +56,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee", "fieldname": "employee",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -87,6 +90,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_component", "fieldname": "salary_component",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -120,6 +124,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
@ -153,6 +158,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "overwrite_salary_structure_amount", "fieldname": "overwrite_salary_structure_amount",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -185,6 +191,40 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5", "fieldname": "column_break_5",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -217,6 +257,8 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"description": "Date on which this component is applied",
"fetch_if_empty": 0,
"fieldname": "payroll_date", "fieldname": "payroll_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@ -250,6 +292,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name", "fieldname": "employee_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@ -284,6 +327,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "employee.department", "fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -317,6 +361,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -350,6 +395,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_slip", "fieldname": "salary_slip",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -384,6 +430,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "salary_component.type", "fetch_from": "salary_component.type",
"fetch_if_empty": 0,
"fieldname": "type", "fieldname": "type",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@ -417,6 +464,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -453,7 +501,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 14:44:22.921926", "modified": "2019-05-09 19:53:37.475839",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Additional Salary", "name": "Additional Salary",

View File

@ -9,6 +9,12 @@ from frappe import _
from frappe.utils import getdate, date_diff from frappe.utils import getdate, date_diff
class AdditionalSalary(Document): class AdditionalSalary(Document):
def before_insert(self):
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company}):
frappe.throw(_("Additional Salary Component Exists."))
def validate(self): def validate(self):
self.validate_dates() self.validate_dates()
if self.amount < 0: if self.amount < 0:
@ -35,7 +41,8 @@ class AdditionalSalary(Document):
@frappe.whitelist() @frappe.whitelist()
def get_additional_salary_component(employee, start_date, end_date): def get_additional_salary_component(employee, start_date, end_date):
additional_components = frappe.db.sql(""" additional_components = frappe.db.sql("""
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount from `tabAdditional Salary` select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary`
where employee=%(employee)s where employee=%(employee)s
and docstatus = 1 and docstatus = 1
and payroll_date between %(from_date)s and %(to_date)s and payroll_date between %(from_date)s and %(to_date)s
@ -48,16 +55,20 @@ def get_additional_salary_component(employee, start_date, end_date):
}, as_dict=1) }, as_dict=1)
additional_components_list = [] additional_components_list = []
component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
for d in additional_components: for d in additional_components:
component = frappe.get_doc("Salary Component", d.salary_component) struct_row = frappe._dict({'salary_component': d.salary_component})
struct_row = {'salary_component': d.salary_component} component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
for field in ["depends_on_payment_days", "abbr", "is_tax_applicable", "variable_based_on_taxable_salary", "is_additional_component"]: if component:
struct_row[field] = component.get(field) struct_row.update(component[0])
additional_components_list.append({ struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
struct_row['is_additional_component'] = 1
additional_components_list.append(frappe._dict({
'amount': d.amount, 'amount': d.amount,
'type': component.type, 'type': component[0].type,
'struct_row': struct_row, 'struct_row': struct_row,
'overwrite': d.overwrite_salary_structure_amount 'overwrite': d.overwrite_salary_structure_amount,
}) }))
return additional_components_list return additional_components_list

View File

@ -3,13 +3,18 @@
frappe.ui.form.on('Employee Benefit Application', { frappe.ui.form.on('Employee Benefit Application', {
setup: function(frm) { setup: function(frm) {
frm.set_query("earning_component", "employee_benefits", function() { if(!frm.doc.employee || !frm.doc.date) {
return { frappe.throw(__("Please select Employee and Date first"));
query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components", } else {
filters: {date: frm.doc.date, employee: frm.doc.employee} frm.set_query("earning_component", "employee_benefits", function() {
}; return {
}); query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components",
filters: {date: frm.doc.date, employee: frm.doc.employee}
};
});
}
}, },
employee: function(frm) { employee: function(frm) {
var method, args; var method, args;
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){

View File

@ -7,7 +7,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
@ -131,7 +131,7 @@ def get_max_benefits_remaining(employee, on_date, payroll_period):
salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component) salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component)
if salary_component.depends_on_payment_days == 1 and salary_component.pay_against_benefit_claim != 1: if salary_component.depends_on_payment_days == 1 and salary_component.pay_against_benefit_claim != 1:
have_depends_on_payment_days = True have_depends_on_payment_days = True
benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, salary_component.max_benefit_amount) benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, salary_component.max_benefit_amount)
amount_per_day = benefit_amount / payroll_period_days amount_per_day = benefit_amount / payroll_period_days
per_day_amount_total += amount_per_day per_day_amount_total += amount_per_day
@ -167,16 +167,14 @@ def calculate_lwp(employee, start_date, holidays, working_days):
lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
return lwp return lwp
def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal_struct, period_length, frequency): def get_benefit_component_amount(employee, start_date, end_date, salary_component, sal_struct, payroll_frequency, payroll_period):
payroll_period, period_factor, actual_payroll_days = get_payroll_period_days(start_date, end_date, employee)
if not payroll_period: if not payroll_period:
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.") frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}")
.format(struct_row.salary_component)) .format(salary_component))
return False return False
# Considering there is only one application for a year # Considering there is only one application for a year
benefit_application_name = frappe.db.sql(""" benefit_application = frappe.db.sql("""
select name select name
from `tabEmployee Benefit Application` from `tabEmployee Benefit Application`
where where
@ -185,69 +183,58 @@ def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal
and docstatus = 1 and docstatus = 1
""", { """, {
'employee': employee, 'employee': employee,
'payroll_period': payroll_period 'payroll_period': payroll_period.name
}) })
if frappe.db.get_value("Salary Component", struct_row.salary_component, "depends_on_payment_days") != 1: current_benefit_amount = 0.0
if frequency == "Monthly" and actual_payroll_days in range(360, 370): component_max_benefit, depends_on_payment_days = frappe.db.get_value("Salary Component",
period_length = 1 salary_component, ["max_benefit_amount", "depends_on_payment_days"])
period_factor = 12
if period_factor: if benefit_application:
# If there is application for benefit then fetch the amount from the application. benefit_amount = frappe.db.get_value("Employee Benefit Application Detail",
# else Split the max benefits to the pro-rata components with the ratio of their max_benefit_amount {"parent": benefit_application[0][0], "earning_component": salary_component}, "amount")
if benefit_application_name: elif component_max_benefit:
benefit_application = frappe.get_doc("Employee Benefit Application", benefit_application_name[0][0]) benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit)
return get_benefit_amount(benefit_application, struct_row, period_factor, period_length)
# TODO: Check if there is benefit claim for employee then pro-rata divide the rest of amount (Late Benefit Application) current_benefit_amount = 0
else: if benefit_amount:
component_max = frappe.db.get_value("Salary Component", struct_row.salary_component, "max_benefit_amount") total_sub_periods = get_period_factor(employee,
if component_max: start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days)[0]
benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, component_max)
return get_amount(period_factor, benefit_amount, period_length)
return False
def get_benefit_pro_rata_ratio_amount(sal_struct, component_max): current_benefit_amount = benefit_amount / total_sub_periods
total_pro_rata_max = 0
return current_benefit_amount
def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit):
max_benefits_total = 0
benefit_amount = 0 benefit_amount = 0
for sal_struct_row in sal_struct.get("earnings"): for d in sal_struct.get("earnings"):
pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", if d.is_flexible_benefit == 1:
sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) component = frappe.db.get_value("Salary Component", d.salary_component, ["max_benefit_amount", "pay_against_benefit_claim"], as_dict=1)
if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1: if not component.pay_against_benefit_claim:
total_pro_rata_max += max_benefit_amount max_benefits_total += component.max_benefit_amount
if total_pro_rata_max > 0:
benefit_amount = component_max * sal_struct.max_benefits / total_pro_rata_max if max_benefits_total > 0:
if benefit_amount > component_max: benefit_amount = sal_struct.max_benefits * component.max_benefit_amount / max_benefits_total
benefit_amount = component_max if benefit_amount > component_max_benefit:
benefit_amount = component_max_benefit
return benefit_amount return benefit_amount
def get_benefit_amount(application, struct_row, period_factor, period_length):
amount = 0
for employee_benefit in application.employee_benefits:
if employee_benefit.earning_component == struct_row.salary_component:
amount += get_amount(period_factor, employee_benefit.amount, period_length)
return amount if amount > 0 else False
def get_amount(period_factor, amount, period_length):
amount_per_day = amount / period_factor
total_amount = amount_per_day * period_length
return total_amount
def get_earning_components(doctype, txt, searchfield, start, page_len, filters): def get_earning_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 2: if len(filters) < 2:
return {} return {}
employee = filters['employee']
date = filters['date'] salary_structure = get_assigned_salary_structure(filters['employee'], filters['date'])
salary_structure = get_assigned_salary_structure(employee, date)
if salary_structure: if salary_structure:
query = """select salary_component from `tabSalary Detail` where parent = '{salary_structure}' return frappe.db.sql("""
and is_flexible_benefit = 1 select salary_component
order by name""" from `tabSalary Detail`
where parent = %s and is_flexible_benefit = 1
return frappe.db.sql(query.format(**{ order by name
"salary_structure": salary_structure """, salary_structure)
})) else:
frappe.throw(_("Salary Structure not found for employee {0} and date {1}")
return {} .format(filters['employee'], filters['date']))

View File

@ -5,9 +5,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
from erpnext.hr.utils import get_payroll_period, get_previous_claimed_amount from erpnext.hr.utils import get_previous_claimed_amount
from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
class EmployeeBenefitClaim(Document): class EmployeeBenefitClaim(Document):
@ -97,31 +99,28 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct):
benefit_amount_total += benefit_amount benefit_amount_total += benefit_amount
return benefit_amount_total return benefit_amount_total
def get_benefit_claim_amount(employee, start_date, end_date, salary_component): def get_benefit_claim_amount(employee, start_date, end_date, salary_component=None):
query = """select claimed_amount from `tabEmployee Benefit Claim` query = """
where employee=%(employee)s select sum(claimed_amount)
and docstatus = 1 and pay_against_benefit_claim = 1 from `tabEmployee Benefit Claim`
where
employee=%(employee)s
and docstatus = 1
and pay_against_benefit_claim = 1
and claim_date between %(start_date)s and %(end_date)s
""" """
if not start_date:
query += "and claim_date <= %(end_date)s"
else:
query += "and (claim_date between %(start_date)s and %(end_date)s)"
if salary_component: if salary_component:
query += "and earning_component = %(earning_component)s" query += " and earning_component = %(earning_component)s"
benefit_claim_details = frappe.db.sql(query, { claimed_amount = flt(frappe.db.sql(query, {
'employee': employee, 'employee': employee,
'start_date': start_date, 'start_date': start_date,
'end_date': end_date, 'end_date': end_date,
'earning_component': salary_component 'earning_component': salary_component
}, as_dict = True) })[0][0])
if benefit_claim_details:
claimed_amount = 0 return claimed_amount
for claim_detail in benefit_claim_details:
claimed_amount += claim_detail.claimed_amount
return claimed_amount
return False
def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period): def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period):
pro_rata_amount = 0 pro_rata_amount = 0
@ -140,11 +139,11 @@ def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payro
else: else:
pro_rata_amount = get_benefit_pro_rata_ratio_amount(employee, sal_slip_start_date, sal_struct) pro_rata_amount = get_benefit_pro_rata_ratio_amount(employee, sal_slip_start_date, sal_struct)
claimed_amount += get_benefit_claim_amount(employee, payroll_period.start_date, payroll_period.end_date, False) claimed_amount += get_benefit_claim_amount(employee, payroll_period.start_date, payroll_period.end_date)
return claimed_amount + pro_rata_amount return claimed_amount + pro_rata_amount
def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end_date, current_flexi_amount, payroll_period, sal_struct): def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end_date, payroll_period, sal_struct):
max_benefits = get_max_benefits(employee, payroll_period.end_date) max_benefits = get_max_benefits(employee, payroll_period.end_date)
if not max_benefits: if not max_benefits:
max_benefits = 0 max_benefits = 0

View File

@ -446,6 +446,72 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@ -458,7 +524,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-25 16:38:05.847925", "modified": "2019-05-11 16:13:50.472670",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Declaration", "name": "Employee Tax Exemption Declaration",

View File

@ -25,7 +25,8 @@ class EmployeeTaxExemptionDeclaration(Document):
filters = { filters = {
"employee": self.employee, "employee": self.employee,
"payroll_period": self.payroll_period, "payroll_period": self.payroll_period,
"name": ["!=", self.name] "name": ["!=", self.name],
"docstatus": ["!=", 2]
} }
) )
if duplicate: if duplicate:

View File

@ -448,6 +448,72 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -558,7 +624,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-25 17:06:36.569549", "modified": "2019-05-13 12:17:18.045171",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",

View File

@ -4,5 +4,20 @@
frappe.ui.form.on('HR Settings', { frappe.ui.form.on('HR Settings', {
refresh: function(frm) { refresh: function(frm) {
},
encrypt_salary_slips_in_emails: function(frm) {
let encrypt_state = frm.doc.encrypt_salary_slips_in_emails;
frm.set_df_property('password_policy', 'reqd', encrypt_state);
},
validate: function(frm) {
let policy = frm.doc.password_policy;
if (policy) {
if (policy.includes(' ') || policy.includes('--')) {
frappe.msgprint("Password policy cannot contain spaces or simultaneous hyphens. The format will be restructured automatically");
}
frm.set_value('password_policy', policy.split(new RegExp(" |-", 'g')).filter((token) => token).join('-'));
}
} }
}); });

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
@ -13,10 +14,12 @@
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee_settings", "fieldname": "employee_settings",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -43,12 +46,14 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"description": "Enter retirement age in years", "description": "Enter retirement age in years",
"fetch_if_empty": 0,
"fieldname": "retirement_age", "fieldname": "retirement_age",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@ -76,12 +81,14 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Naming Series", "default": "Naming Series",
"description": "Employee record is created using selected field. ", "description": "Employee record is created using selected field. ",
"fetch_if_empty": 0,
"fieldname": "emp_created_by", "fieldname": "emp_created_by",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -109,11 +116,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "leave_approval_notification_template", "fieldname": "leave_approval_notification_template",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -142,10 +151,12 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "leave_status_notification_template", "fieldname": "leave_status_notification_template",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -174,10 +185,12 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -204,11 +217,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Don't send Employee Birthday Reminders", "description": "Don't send Employee Birthday Reminders",
"fetch_if_empty": 0,
"fieldname": "stop_birthday_reminders", "fieldname": "stop_birthday_reminders",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -235,10 +250,12 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "maintain_bill_work_hours_same", "fieldname": "maintain_bill_work_hours_same",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -266,11 +283,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "leave_approver_mandatory_in_leave_application", "fieldname": "leave_approver_mandatory_in_leave_application",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -298,11 +317,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "expense_approver_mandatory_in_expense_claim", "fieldname": "expense_approver_mandatory_in_expense_claim",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -330,10 +351,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "payroll_settings", "fieldname": "payroll_settings",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -360,11 +384,13 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day", "description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day",
"fetch_if_empty": 0,
"fieldname": "include_holidays_in_total_working_days", "fieldname": "include_holidays_in_total_working_days",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -391,12 +417,14 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"description": "Emails salary slip to employee based on preferred email selected in Employee", "description": "Emails salary slip to employee based on preferred email selected in Employee",
"fetch_if_empty": 0,
"fieldname": "email_salary_slip_to_employee", "fieldname": "email_salary_slip_to_employee",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -424,10 +452,82 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval: doc.email_salary_slip_to_employee == 1;",
"description": "The salary slip emailed to the employee will be password protected, the password will be generated based on the password policy.",
"fetch_if_empty": 0,
"fieldname": "encrypt_salary_slips_in_emails",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Encrypt Salary Slips in Emails",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.encrypt_salary_slips_in_emails == 1",
"description": "<b>Example:</b> SAL-{first_name}-{date_of_birth.year} <br>This will generate a password like SAL-Jane-1972",
"fetch_if_empty": 0,
"fieldname": "password_policy",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Password Policy",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "max_working_hours_against_timesheet", "fieldname": "max_working_hours_against_timesheet",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "hidden": 0,
@ -455,10 +555,12 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "leave_settings", "fieldname": "leave_settings",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -481,14 +583,17 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "show_leaves_of_all_department_members_in_calendar", "fieldname": "show_leaves_of_all_department_members_in_calendar",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -511,22 +616,21 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-05-03 15:36:13.015466", "modified": "2019-04-25 15:08:12.983571",
"modified_by": "Administrator", "modified_by": "shivam@example.com",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",
"owner": "Administrator", "owner": "Administrator",
@ -553,9 +657,9 @@
], ],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 0, "track_changes": 0,
"track_seen": 0 "track_seen": 0,
"track_views": 0
} }

View File

@ -5,11 +5,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class HRSettings(Document): class HRSettings(Document):
def validate(self): def validate(self):
self.set_naming_series()
self.validate_password_policy()
def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Employee", "employee_number", set_by_naming_series("Employee", "employee_number",
self.get("emp_created_by")=="Naming Series", hide_name_field=True) self.get("emp_created_by")=="Naming Series", hide_name_field=True)
def validate_password_policy(self):
if self.email_salary_slip_to_employee and self.encrypt_salary_slips_in_emails:
if not self.password_policy:
frappe.throw(_("Password policy for Salary Slips is not set"))

View File

@ -172,18 +172,20 @@ frappe.ui.form.on('Loan', {
}, },
mode_of_payment: function (frm) { mode_of_payment: function (frm) {
frappe.call({ if (frm.doc.mode_of_payment && frm.doc.company) {
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", frappe.call({
args: { method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
"mode_of_payment": frm.doc.mode_of_payment, args: {
"company": frm.doc.company "mode_of_payment": frm.doc.mode_of_payment,
}, "company": frm.doc.company
callback: function (r, rt) { },
if (r.message) { callback: function (r, rt) {
frm.set_value("payment_account", r.message.account); if (r.message) {
frm.set_value("payment_account", r.message.account);
}
} }
} });
}); }
}, },
loan_application: function (frm) { loan_application: function (frm) {

View File

@ -21,6 +21,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break0", "fieldname": "section_break0",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -53,6 +54,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -86,6 +88,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Today", "default": "Today",
"fetch_if_empty": 0,
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@ -120,6 +123,7 @@
"columns": 0, "columns": 0,
"default": "", "default": "",
"depends_on": "eval:doc.salary_slip_based_on_timesheet == 0", "depends_on": "eval:doc.salary_slip_based_on_timesheet == 0",
"fetch_if_empty": 0,
"fieldname": "payroll_frequency", "fieldname": "payroll_frequency",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -153,6 +157,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -186,6 +191,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -220,6 +226,7 @@
"collapsible": 0, "collapsible": 0,
"collapsible_depends_on": "", "collapsible_depends_on": "",
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8", "fieldname": "section_break_8",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -252,6 +259,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "branch", "fieldname": "branch",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -285,6 +293,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -318,6 +327,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_10", "fieldname": "column_break_10",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -349,6 +359,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "designation", "fieldname": "designation",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -382,6 +393,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "number_of_employees", "fieldname": "number_of_employees",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 0, "hidden": 0,
@ -414,6 +426,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "sec_break20", "fieldname": "sec_break20",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -445,6 +458,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "employees", "fieldname": "employees",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0, "hidden": 0,
@ -478,6 +492,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_13", "fieldname": "section_break_13",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -509,6 +524,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "validate_attendance", "fieldname": "validate_attendance",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -541,6 +557,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "attendance_detail_html", "fieldname": "attendance_detail_html",
"fieldtype": "HTML", "fieldtype": "HTML",
"hidden": 0, "hidden": 0,
@ -572,6 +589,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_12", "fieldname": "section_break_12",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -604,6 +622,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "0", "default": "0",
"fetch_if_empty": 0,
"fieldname": "salary_slip_based_on_timesheet", "fieldname": "salary_slip_based_on_timesheet",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -637,6 +656,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "select_payroll_period", "fieldname": "select_payroll_period",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -670,6 +690,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "start_date", "fieldname": "start_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@ -703,6 +724,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "end_date", "fieldname": "end_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@ -735,6 +757,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_11", "fieldname": "column_break_11",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -766,6 +789,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unclaimed_employee_benefits", "fieldname": "deduct_tax_for_unclaimed_employee_benefits",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -798,6 +822,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof", "fieldname": "deduct_tax_for_unsubmitted_tax_exemption_proof",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@ -830,6 +855,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_16", "fieldname": "section_break_16",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -864,6 +890,7 @@
"columns": 0, "columns": 0,
"default": ":Company", "default": ":Company",
"fetch_from": "", "fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -897,6 +924,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_18", "fieldname": "column_break_18",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -928,6 +956,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "project", "fieldname": "project",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -961,6 +990,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break2", "fieldname": "column_break2",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@ -993,6 +1023,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "account", "fieldname": "account",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -1026,6 +1057,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Select Payment Account to make Bank Entry", "description": "Select Payment Account to make Bank Entry",
"fetch_if_empty": 0,
"fieldname": "payment_account", "fieldname": "payment_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -1059,6 +1091,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break2", "fieldname": "section_break2",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -1090,6 +1123,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@ -1122,6 +1156,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_slips_created", "fieldname": "salary_slips_created",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
@ -1154,6 +1189,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_slips_submitted", "fieldname": "salary_slips_submitted",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
@ -1181,17 +1217,16 @@
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 0, "idx": 0,
"image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-02-05 10:41:08.865842", "menu_index": 0,
"modified": "2019-03-26 16:55:04.158800",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Payroll Entry", "name": "Payroll Entry",
@ -1210,7 +1245,7 @@
"permlevel": 0, "permlevel": 0,
"print": 0, "print": 0,
"read": 1, "read": 1,
"report": 0, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
@ -1220,7 +1255,6 @@
], ],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@ -543,14 +543,15 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr
payroll_entry.email_salary_slip(submitted_ss) payroll_entry.email_salary_slip(submitted_ss)
payroll_entry.db_set("salary_slips_submitted", 1) payroll_entry.db_set("salary_slips_submitted", 1)
payroll_entry.notify_update() payroll_entry.notify_update()
if not submitted_ss and not not_submitted_ss: if not submitted_ss and not not_submitted_ss:
frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted")) frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted"))
if not_submitted_ss: if not_submitted_ss:
frappe.msgprint(_("Could not submit some Salary Slips")) frappe.msgprint(_("Could not submit some Salary Slips"))
def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters): def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" return frappe.db.sql("""
select name from `tabPayroll Entry` select name from `tabPayroll Entry`

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, getdate, formatdate, cint from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import get_holidays_for_employee from erpnext.hr.utils import get_holidays_for_employee
@ -48,12 +48,13 @@ class PayrollPeriod(Document):
def get_payroll_period_days(start_date, end_date, employee): def get_payroll_period_days(start_date, end_date, employee):
company = frappe.db.get_value("Employee", employee, "company") company = frappe.db.get_value("Employee", employee, "company")
payroll_period = frappe.db.sql(""" payroll_period = frappe.db.sql("""
select name, start_date, end_date from `tabPayroll Period` select name, start_date, end_date
where company=%(company)s from `tabPayroll Period`
and ( where
(%(start_date)s between start_date and end_date) company=%(company)s
and (%(end_date)s between start_date and end_date) and %(start_date)s between start_date and end_date
)""", { and %(end_date)s between start_date and end_date
""", {
'company': company, 'company': company,
'start_date': start_date, 'start_date': start_date,
'end_date': end_date 'end_date': end_date
@ -67,3 +68,38 @@ def get_payroll_period_days(start_date, end_date, employee):
working_days -= len(holidays) working_days -= len(holidays)
return payroll_period[0][0], working_days, actual_no_of_days return payroll_period[0][0], working_days, actual_no_of_days
return False, False, False return False, False, False
def get_payroll_period(from_date, to_date, company):
payroll_period = frappe.db.sql("""
select name, start_date, end_date
from `tabPayroll Period`
where start_date<=%s and end_date>= %s and company=%s
""", (from_date, to_date, company), as_dict=1)
return payroll_period[0] if payroll_period else None
def get_period_factor(employee, start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days=0):
# TODO if both deduct checked update the factor to make tax consistent
period_start, period_end = payroll_period.start_date, payroll_period.end_date
joining_date, relieving_date = frappe.db.get_value("Employee", employee, ["date_of_joining", "relieving_date"])
if getdate(joining_date) > getdate(period_start):
period_start = joining_date
if relieving_date and getdate(relieving_date) < getdate(period_end):
period_end = relieving_date
total_sub_periods, remaining_sub_periods = 0.0, 0.0
if payroll_frequency == "Monthly" and not depends_on_payment_days:
total_sub_periods = month_diff(payroll_period.end_date, payroll_period.start_date)
remaining_sub_periods = month_diff(period_end, start_date)
else:
salary_days = date_diff(end_date, start_date) + 1
days_in_payroll_period = date_diff(payroll_period.end_date, payroll_period.start_date) + 1
total_sub_periods = flt(days_in_payroll_period) / flt(salary_days)
remaining_days_in_payroll_period = date_diff(period_end, start_date) + 1
remaining_sub_periods = flt(remaining_days_in_payroll_period) / flt(salary_days)
return total_sub_periods, remaining_sub_periods

View File

@ -24,14 +24,6 @@ frappe.ui.form.on('Salary Component', {
is_flexible_benefit: function(frm) { is_flexible_benefit: function(frm) {
if(frm.doc.is_flexible_benefit){ if(frm.doc.is_flexible_benefit){
set_value_for_condition_and_formula(frm); set_value_for_condition_and_formula(frm);
frm.set_value("is_additional_component", 0);
frm.set_value("formula", '');
frm.set_value("amount", 0);
}
},
is_additional_component: function(frm) {
if(frm.doc.is_additional_component){
frm.set_value("is_flexible_benefit", 0);
frm.set_value("formula", ''); frm.set_value("formula", '');
frm.set_value("amount", 0); frm.set_value("amount", 0);
} }

View File

@ -115,40 +115,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_flexible_benefit != 1",
"fetch_if_empty": 0,
"fieldname": "is_additional_component",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Additional Component",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -285,6 +251,39 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -424,7 +423,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.type==\"Earning\" && doc.is_additional_component != 1 && doc.statistical_component!=1", "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "flexible_benefits", "fieldname": "flexible_benefits",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -458,7 +457,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.is_additional_component != 1", "depends_on": "",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "is_flexible_benefit", "fieldname": "is_flexible_benefit",
"fieldtype": "Check", "fieldtype": "Check",
@ -1035,7 +1034,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-16 19:08:55.323567", "modified": "2019-05-13 12:55:55.424370",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Component", "name": "Salary Component",

View File

@ -35,7 +35,6 @@
"salary_component": "Leave Encashment", "salary_component": "Leave Encashment",
"type": "Earning", "type": "Earning",
"is_payable": 1, "is_payable": 1,
"is_tax_applicable": 1, "is_tax_applicable": 1
"is_additional_component": 1
} }
] ]

View File

@ -19,7 +19,5 @@ def create_salary_component(component_name, **args):
"salary_component": component_name, "salary_component": component_name,
"type": args.get("type") or "Earning", "type": args.get("type") or "Earning",
"is_payable": args.get("is_payable") or 1, "is_payable": args.get("is_payable") or 1,
"is_tax_applicable": args.get("is_tax_applicable") or 1, "is_tax_applicable": args.get("is_tax_applicable") or 1
"is_additional_component": args.get("is_additional_component") or 1
}).insert() }).insert()

View File

@ -218,41 +218,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "salary_component.is_additional_component",
"fetch_if_empty": 0,
"fieldname": "is_additional_component",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Additional Component",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -323,6 +288,39 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -566,6 +564,41 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "additional_amount",
"fieldtype": "Currency",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Additional Amount",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -713,7 +746,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-16 19:09:31.726597", "modified": "2019-05-11 17:33:08.508653",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Detail", "name": "Salary Detail",

View File

@ -11,7 +11,7 @@ frappe.ui.form.on("Salary Slip", {
{fieldname: 'salary_component', columns: 6}, {fieldname: 'salary_component', columns: 6},
{fieldname: 'amount', columns: 4} {fieldname: 'amount', columns: 4}
]; ];
}) });
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){ frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
return { return {
@ -19,31 +19,39 @@ frappe.ui.form.on("Salary Slip", {
employee: frm.doc.employee employee: frm.doc.employee
} }
} }
} };
frm.set_query("salary_component", "earnings", function() { frm.set_query("salary_component", "earnings", function() {
return { return {
filters: { filters: {
type: "earning" type: "earning"
} }
} }
}) });
frm.set_query("salary_component", "deductions", function() { frm.set_query("salary_component", "deductions", function() {
return { return {
filters: { filters: {
type: "deduction" type: "deduction"
} }
} }
}) });
frm.set_query("employee", function() {
return{
query: "erpnext.controllers.queries.employee_query"
}
});
}, },
start_date: function(frm, dt, dn){ start_date: function(frm){
if(frm.doc.start_date){ if(frm.doc.start_date){
frm.trigger("set_end_date"); frm.trigger("set_end_date");
} }
}, },
end_date: function(frm, dt, dn) { end_date: function(frm) {
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
}, },
set_end_date: function(frm){ set_end_date: function(frm){
@ -70,43 +78,54 @@ frappe.ui.form.on("Salary Slip", {
refresh: function(frm) { refresh: function(frm) {
frm.trigger("toggle_fields") frm.trigger("toggle_fields")
frm.trigger("toggle_reqd_fields")
var salary_detail_fields = ["formula", "abbr", "statistical_component", "is_tax_applicable", var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
"is_flexible_benefit", "variable_based_on_taxable_salary", "is_additional_component"]
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false); cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false); cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
}, },
salary_slip_based_on_timesheet: function(frm, dt, dn) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
}, },
payroll_frequency: function(frm, dt, dn) { payroll_frequency: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
frm.set_value('end_date', ''); frm.set_value('end_date', '');
}, },
employee: function(frm, dt, dn) { employee: function(frm) {
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
},
leave_without_pay: function(frm){
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({
method: 'process_salary_based_on_leave',
doc: frm.doc,
args: {"lwp": frm.doc.leave_without_pay},
callback: function(r, rt) {
frm.refresh();
}
});
}
}, },
toggle_fields: function(frm) { toggle_fields: function(frm) {
frm.toggle_display(['hourly_wages', 'timesheets'], frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)===1);
cint(frm.doc.salary_slip_based_on_timesheet)==1);
frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'], frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'],
frm.doc.payroll_frequency!=""); frm.doc.payroll_frequency!="");
}
})
frappe.ui.form.on('Salary Detail', {
earnings_remove: function(frm, dt, dn) {
calculate_all(frm.doc, dt, dn);
}, },
deductions_remove: function(frm, dt, dn) {
calculate_all(frm.doc, dt, dn); get_emp_and_leave_details: function(frm) {
return frappe.call({
method: 'get_emp_and_leave_details',
doc: frm.doc,
callback: function(r, rt) {
frm.refresh();
}
});
} }
}) })
@ -119,133 +138,29 @@ frappe.ui.form.on('Salary Slip Timesheet', {
} }
}); });
// Get leave details
//---------------------------------------------------------------------
var get_emp_and_leave_details = function(doc, dt, dn) {
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
}
cur_frm.cscript.leave_without_pay = function(doc,dt,dn){
if (doc.employee && doc.start_date && doc.end_date) {
return $c_obj(doc, 'get_leave_details', {"lwp": doc.leave_without_pay}, function(r, rt) {
var doc = locals[dt][dn];
cur_frm.refresh();
calculate_all(doc, dt, dn);
});
}
}
var calculate_all = function(doc, dt, dn) {
calculate_earning_total(doc, dt, dn);
calculate_ded_total(doc, dt, dn);
calculate_net_pay(doc, dt, dn);
}
cur_frm.cscript.amount = function(doc,dt,dn){
var child = locals[dt][dn];
if(!doc.salary_structure){
frappe.model.set_value(dt,dn, "default_amount", child.amount)
}
calculate_all(doc, dt, dn);
}
cur_frm.cscript.depends_on_payment_days = function(doc,dt,dn){
calculate_earning_total(doc, dt, dn, true);
calculate_ded_total(doc, dt, dn, true);
calculate_net_pay(doc, dt, dn);
refresh_many(['amount','gross_pay', 'rounded_total', 'net_pay', 'loan_repayment']);
};
// Calculate earning total
// ------------------------------------------------------------------------
var calculate_earning_total = function(doc, dt, dn, reset_amount) {
var tbl = doc.earnings || [];
var total_earn = 0;
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_payment_days) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
cint(doc.total_working_days)*100)/100;
} else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount;
}
if(!tbl[i].do_not_include_in_total) {
total_earn += flt(tbl[i].amount);
}
}
doc.gross_pay = total_earn;
refresh_many(['earnings', 'amount','gross_pay']);
}
// Calculate deduction total
// ------------------------------------------------------------------------
var calculate_ded_total = function(doc, dt, dn, reset_amount) {
var tbl = doc.deductions || [];
var total_ded = 0;
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_payment_days) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
} else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount;
}
if(!tbl[i].do_not_include_in_total) {
total_ded += flt(tbl[i].amount);
}
}
doc.total_deduction = total_ded;
refresh_many(['deductions', 'total_deduction']);
}
var calculate_net_pay = function(doc, dt, dn) {
doc.net_pay = flt(doc.gross_pay) - flt(doc.total_deduction);
doc.rounded_total = Math.round(doc.net_pay);
refresh_many(['net_pay', 'rounded_total']);
}
cur_frm.cscript.validate = function(doc, dt, dn) {
calculate_all(doc, dt, dn);
}
cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
return{
query: "erpnext.controllers.queries.employee_query"
}
}
// calculate total working hours, earnings based on hourly wages and totals // calculate total working hours, earnings based on hourly wages and totals
// ------------------------------------------------------------------------
var total_work_hours = function(frm, dt, dn) { var total_work_hours = function(frm, dt, dn) {
frm.set_value('total_working_hours', 0); var total_working_hours = 0.0;
$.each(frm.doc["timesheets"] || [], function(i, timesheet) { $.each(frm.doc["timesheets"] || [], function(i, timesheet) {
frm.doc.total_working_hours += timesheet.working_hours; total_working_hours += timesheet.working_hours;
}); });
frm.refresh_field('total_working_hours'); frm.set_value('total_working_hours', total_working_hours);
var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate; var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate;
frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => { frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => {
frm.set_value('gross_pay', 0); var gross_pay = 0.0;
$.each(frm.doc["earnings"], function(i, earning) { $.each(frm.doc["earnings"], function(i, earning) {
if (earning.salary_component == r.salary_component) { if (earning.salary_component == r.salary_component) {
earning.amount = wages_amount; earning.amount = wages_amount;
frm.refresh_fields('earnings'); frm.refresh_fields('earnings');
} }
frm.doc.gross_pay += earning.amount; gross_pay += earning.amount;
}); });
frm.set_value('gross_pay', gross_pay);
frm.refresh_field('gross_pay'); frm.doc.net_pay = flt(frm.doc.gross_pay) - flt(frm.doc.total_deduction);
calculate_net_pay(frm.doc, dt, dn); frm.doc.rounded_total = Math.round(frm.doc.net_pay);
refresh_many(['net_pay', 'rounded_total']);
}); });
} }

View File

@ -1019,71 +1019,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_01", "fetch_if_empty": 0,
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Salary Slip",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_32", "fieldname": "section_break_32",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -1893,6 +1829,41 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Salary Slip",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@ -1906,7 +1877,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-02-18 18:54:36.161027", "modified": "2019-05-13 13:10:14.524119",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

File diff suppressed because it is too large Load Diff

View File

@ -45,12 +45,12 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.total_working_days, no_of_days[0]) self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, no_of_days[0]) self.assertEqual(ss.payment_days, no_of_days[0])
self.assertEqual(ss.earnings[0].amount, 25000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000) self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000) self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 40500) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 29918) self.assertEqual(ss.net_pay, 67418.0)
def test_salary_slip_with_holidays_excluded(self): def test_salary_slip_with_holidays_excluded(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
@ -64,13 +64,13 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1]) self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1]) self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1])
self.assertEqual(ss.earnings[0].amount, 25000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[0].default_amount, 25000) self.assertEqual(ss.earnings[0].default_amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000) self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000) self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 40500) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 29918) self.assertEqual(ss.net_pay, 67418.0)
def test_payment_days(self): def test_payment_days(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
@ -211,7 +211,7 @@ class TestSalarySlip(unittest.TestCase):
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
# total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200 # total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200
annual_tax = 29700 annual_tax = 113567.79
try: try:
self.assertEqual(tax_paid, annual_tax) self.assertEqual(tax_paid, annual_tax)
except AssertionError: except AssertionError:
@ -250,7 +250,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 416000, 166000 @ 5% ie. 8300 # total taxable income 416000, 166000 @ 5% ie. 8300
try: try:
self.assertEqual(tax_paid, 8300) self.assertEqual(tax_paid, 88607.79)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@ -265,7 +265,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200 # total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
try: try:
self.assertEqual(tax_paid, 25700) self.assertEqual(tax_paid, 121211.48)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@ -365,7 +365,7 @@ def make_earning_salary_component(setup=False, test_tax=False):
"salary_component": 'Basic Salary', "salary_component": 'Basic Salary',
"abbr":'BS', "abbr":'BS',
"condition": 'base > 10000', "condition": 'base > 10000',
"formula": 'base*.5', "formula": 'base',
"type": "Earning", "type": "Earning",
"amount_based_on_formula": 1 "amount_based_on_formula": 1
}, },
@ -386,7 +386,6 @@ def make_earning_salary_component(setup=False, test_tax=False):
{ {
"salary_component": "Leave Encashment", "salary_component": "Leave Encashment",
"abbr": 'LE', "abbr": 'LE',
"is_additional_component": 1,
"type": "Earning" "type": "Earning"
} }
] ]
@ -398,7 +397,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"is_flexible_benefit": 1, "is_flexible_benefit": 1,
"type": "Earning", "type": "Earning",
"pay_against_benefit_claim": 1, "pay_against_benefit_claim": 1,
"max_benefit_amount": 100000 "max_benefit_amount": 100000,
"depends_on_payment_days": 0
}, },
{ {
"salary_component": "Medical Allowance", "salary_component": "Medical Allowance",
@ -409,9 +409,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"max_benefit_amount": 15000 "max_benefit_amount": 15000
}, },
{ {
"salary_component": "Perfomance Bonus", "salary_component": "Performance Bonus",
"abbr": 'B', "abbr": 'B',
"is_additional_component": 1,
"type": "Earning" "type": "Earning"
} }
]) ])
@ -442,7 +441,8 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"abbr":'T', "abbr":'T',
"formula": 'base*.1', "formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1 "amount_based_on_formula": 1,
"depends_on_payment_days": 0
} }
] ]
if not test_tax: if not test_tax:
@ -512,21 +512,23 @@ def create_tax_slab(payroll_period):
{ {
"from_amount": 250000, "from_amount": 250000,
"to_amount": 500000, "to_amount": 500000,
"percent_deduction": 5 "percent_deduction": 5.2,
"condition": "annual_taxable_earning > 500000"
}, },
{ {
"from_amount": 500000, "from_amount": 500001,
"to_amount": 1000000, "to_amount": 1000000,
"percent_deduction": 20 "percent_deduction": 20.8
}, },
{ {
"from_amount": 1000000, "from_amount": 1000001,
"percent_deduction": 30 "percent_deduction": 31.2
} }
] ]
payroll_period.taxable_salary_slabs = [] payroll_period.taxable_salary_slabs = []
for item in data: for item in data:
payroll_period.append("taxable_salary_slabs", item) payroll_period.append("taxable_salary_slabs", item)
payroll_period.standard_tax_exemption_amount = 52500
payroll_period.save() payroll_period.save()
def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True): def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
@ -559,7 +561,7 @@ def create_additional_salary(employee, payroll_period, amount):
"doctype": "Additional Salary", "doctype": "Additional Salary",
"employee": employee, "employee": employee,
"company": erpnext.get_default_company(), "company": erpnext.get_default_company(),
"salary_component": "Perfomance Bonus", "salary_component": "Performance Bonus",
"payroll_date": salary_date, "payroll_date": salary_date,
"amount": amount, "amount": amount,
"type": "Earning" "type": "Earning"

View File

@ -147,7 +147,8 @@ frappe.ui.form.on('Salary Structure', {
source_name: frm.doc.name, source_name: frm.doc.name,
employee: values.employee, employee: values.employee,
as_print: 1, as_print: 1,
print_format: print_format print_format: print_format,
for_preview: 1
}, },
callback: function(r) { callback: function(r) {
var new_window = window.open(); var new_window = window.open();

View File

@ -137,7 +137,7 @@ def get_existing_assignments(employees, salary_structure,from_date):
return salary_structures_assignments return salary_structures_assignments
@frappe.whitelist() @frappe.whitelist()
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None): def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0):
def postprocess(source, target): def postprocess(source, target):
if employee: if employee:
employee_details = frappe.db.get_value("Employee", employee, employee_details = frappe.db.get_value("Employee", employee,
@ -147,7 +147,7 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
target.branch = employee_details.branch target.branch = employee_details.branch
target.designation = employee_details.designation target.designation = employee_details.designation
target.department = employee_details.department target.department = employee_details.department
target.run_method('process_salary_structure') target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, { doc = get_mapped_doc("Salary Structure", source_name, {
"Salary Structure": { "Salary Structure": {

View File

@ -39,18 +39,19 @@ class TestSalaryStructure(unittest.TestCase):
holiday_list.save() holiday_list.save()
def test_amount_totals(self): def test_amount_totals(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"})
if not sal_slip: if not sal_slip:
sal_slip = make_employee_salary_slip("test_employee_2@salary.com", "Monthly", "Salary Structure Sample") sal_slip = make_employee_salary_slip("test_employee_2@salary.com", "Monthly", "Salary Structure Sample")
self.assertEqual(sal_slip.get("salary_structure"), 'Salary Structure Sample') self.assertEqual(sal_slip.get("salary_structure"), 'Salary Structure Sample')
self.assertEqual(sal_slip.get("earnings")[0].amount, 25000) self.assertEqual(sal_slip.get("earnings")[0].amount, 50000)
self.assertEqual(sal_slip.get("earnings")[1].amount, 3000) self.assertEqual(sal_slip.get("earnings")[1].amount, 3000)
self.assertEqual(sal_slip.get("earnings")[2].amount, 12500) self.assertEqual(sal_slip.get("earnings")[2].amount, 25000)
self.assertEqual(sal_slip.get("gross_pay"), 40500) self.assertEqual(sal_slip.get("gross_pay"), 78000)
self.assertEqual(sal_slip.get("deductions")[0].amount, 5000) self.assertEqual(sal_slip.get("deductions")[0].amount, 5000)
self.assertEqual(sal_slip.get("deductions")[1].amount, 5000) self.assertEqual(sal_slip.get("deductions")[1].amount, 5000)
self.assertEqual(sal_slip.get("total_deduction"), 10000) self.assertEqual(sal_slip.get("total_deduction"), 10000)
self.assertEqual(sal_slip.get("net_pay"), 30500) self.assertEqual(sal_slip.get("net_pay"), 68000)
def test_whitespaces_in_formula_conditions_fields(self): def test_whitespaces_in_formula_conditions_fields(self):
salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True) salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True)

View File

@ -5,9 +5,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
from frappe.utils import time_diff_in_seconds
from erpnext.hr.doctype.employee.employee import get_employee_emails from erpnext.hr.doctype.employee.employee import get_employee_emails
class TrainingEvent(Document): class TrainingEvent(Document):
def validate(self): def validate(self):
self.set_employee_emails()
self.validate_period()
def set_employee_emails(self):
self.employee_emails = ', '.join(get_employee_emails([d.employee self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees])) for d in self.employees]))
def validate_period(self):
if time_diff_in_seconds(self.end_time, self.start_time) <= 0:
frappe.throw(_('End time cannot be before start time'))

View File

@ -0,0 +1,28 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Bank Remittance"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
},
{
fieldname:"from_date",
label: __("From Date"),
fieldtype: "Date",
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
},
]
}

View File

@ -0,0 +1,25 @@
{
"add_total_row": 0,
"creation": "2019-03-26 16:57:52.558895",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2019-03-26 16:57:52.558895",
"modified_by": "Administrator",
"module": "HR",
"name": "Bank Remittance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Payroll Entry",
"report_name": "Bank Remittance",
"report_type": "Script Report",
"roles": [
{
"role": "HR Manager"
}
]
}

View File

@ -0,0 +1,154 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import formatdate
import itertools
from frappe import _, get_all
def execute(filters=None):
columns = [
{
"label": _("Payroll Number"),
"fieldtype": "Link",
"fieldname": "payroll_no",
"options": "Payroll Entry",
"width": 150
},
{
"label": _("Debit A/C Number"),
"fieldtype": "Int",
"fieldname": "debit_account",
"hidden": 1,
"width": 200
},
{
"label": _("Payment Date"),
"fieldtype": "Data",
"fieldname": "payment_date",
"width": 100
},
{
"label": _("Employee Name"),
"fieldtype": "Link",
"fieldname": "employee_name",
"options": "Employee",
"width": 200
},
{
"label": _("Bank Name"),
"fieldtype": "Data",
"fieldname": "bank_name",
"width": 50
},
{
"label": _("Employee A/C Number"),
"fieldtype": "Int",
"fieldname": "employee_account_no",
"width": 50
},
{
"label": _("IFSC Code"),
"fieldtype": "Data",
"fieldname": "bank_code",
"width": 100
},
{
"label": _("Currency"),
"fieldtype": "Data",
"fieldname": "currency",
"width": 50
},
{
"label": _("Net Salary Amount"),
"fieldtype": "Currency",
"options": "currency",
"fieldname": "amount",
"width": 100
}
]
data = []
accounts = get_bank_accounts()
payroll_entries = get_payroll_entries(accounts, filters)
salary_slips = get_salary_slips(payroll_entries)
get_emp_bank_ifsc_code(salary_slips)
for salary in salary_slips:
if salary.bank_name and salary.bank_account_no and salary.debit_acc_no and salary.status in ["Submitted", "Paid"]:
row = {
"payroll_no": salary.payroll_entry,
"debit_account": salary.debit_acc_no,
"payment_date": frappe.utils.formatdate(salary.modified.strftime('%Y-%m-%d')),
"bank_name": salary.bank_name,
"employee_account_no": salary.bank_account_no,
"bank_code": salary.ifsc_code,
"employee_name": salary.employee+": " + salary.employee_name,
"currency": frappe.get_cached_value('Company', filters.company, 'default_currency'),
"amount": salary.net_pay,
}
data.append(row)
return columns, data
def get_bank_accounts():
accounts = [d.name for d in get_all("Account", filters={"account_type": "Bank"})]
return accounts
def get_payroll_entries(accounts, filters):
payroll_filter = [
('payment_account', 'IN', accounts),
('number_of_employees', '>', 0),
('Company', '=', filters.company)
]
if filters.to_date:
payroll_filter.append(('posting_date', '<', filters.to_date))
if filters.from_date:
payroll_filter.append(('posting_date', '>', filters.from_date))
entries = get_all("Payroll Entry", payroll_filter, ["name", "payment_account"])
payment_accounts = [d.payment_account for d in entries]
set_company_account(payment_accounts, entries)
return entries
def get_salary_slips(payroll_entries):
payroll = [d.name for d in payroll_entries]
salary_slips = get_all("Salary Slip", filters = [("payroll_entry", "IN", payroll)],
fields = ["modified", "net_pay", "bank_name", "bank_account_no", "payroll_entry", "employee", "employee_name", "status"]
)
payroll_entry_map = {}
for entry in payroll_entries:
payroll_entry_map[entry.name] = entry
# appending company debit accounts
for slip in salary_slips:
slip["debit_acc_no"] = payroll_entry_map[slip.payroll_entry]['company_account']
return salary_slips
def get_emp_bank_ifsc_code(salary_slips):
emp_names = [d.employee for d in salary_slips]
ifsc_codes = get_all("Employee", [("name", "IN", emp_names)], ["ifsc_code", "name"])
ifsc_codes_map = {}
for code in ifsc_codes:
ifsc_codes_map[code.name] = code
for slip in salary_slips:
slip["ifsc_code"] = ifsc_codes_map[code.name]['ifsc_code']
return salary_slips
def set_company_account(payment_accounts, payroll_entries):
company_accounts = get_all("Bank Account", [("account", "in", payment_accounts)], ["account", "bank_account_no"])
company_accounts_map = {}
for acc in company_accounts:
company_accounts_map[acc.account] = acc
for entry in payroll_entries:
entry["company_account"] = company_accounts_map[entry.payment_account]['bank_account_no']
return payroll_entries

View File

@ -35,6 +35,8 @@ def execute(filters=None):
for d in ded_types: for d in ded_types:
row.append(ss_ded_map.get(ss.name, {}).get(d)) row.append(ss_ded_map.get(ss.name, {}).get(d))
row.append(ss.total_loan_repayment)
row += [ss.total_deduction, ss.net_pay] row += [ss.total_deduction, ss.net_pay]
data.append(row) data.append(row)
@ -67,7 +69,7 @@ def get_columns(salary_slips):
columns = columns + [(e + ":Currency:120") for e in salary_components[_("Earning")]] + \ columns = columns + [(e + ":Currency:120") for e in salary_components[_("Earning")]] + \
[_("Gross Pay") + ":Currency:120"] + [(d + ":Currency:120") for d in salary_components[_("Deduction")]] + \ [_("Gross Pay") + ":Currency:120"] + [(d + ":Currency:120") for d in salary_components[_("Deduction")]] + \
[_("Total Deduction") + ":Currency:120", _("Net Pay") + ":Currency:120"] [_("Loan Repayment") + ":Currency:120", _("Total Deduction") + ":Currency:120", _("Net Pay") + ":Currency:120"]
return columns, salary_components[_("Earning")], salary_components[_("Deduction")] return columns, salary_components[_("Earning")], salary_components[_("Deduction")]

View File

@ -126,9 +126,9 @@ def get_employee_fields_label():
fields = [] fields = []
for df in frappe.get_meta("Employee").get("fields"): for df in frappe.get_meta("Employee").get("fields"):
if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type", if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type",
"holiday_list", "branch", "department", "designation", "grade", "holiday_list", "branch", "department", "designation", "grade",
"notice_number_of_days", "reports_to", "leave_policy", "company_email"]: "notice_number_of_days", "reports_to", "leave_policy", "company_email"]:
fields.append({"value": df.fieldname, "label": df.label}) fields.append({"value": df.fieldname, "label": df.label})
return fields return fields
@frappe.whitelist() @frappe.whitelist()
@ -263,12 +263,6 @@ def get_leave_period(from_date, to_date, company):
if leave_period: if leave_period:
return leave_period return leave_period
def get_payroll_period(from_date, to_date, company):
payroll_period = frappe.db.sql("""select name, start_date, end_date from
`tabPayroll Period`
where start_date<=%s and end_date>= %s and company=%s""", (from_date, to_date, company), as_dict=1)
return payroll_period[0] if payroll_period else None
def allocate_earned_leaves(): def allocate_earned_leaves():
'''Allocate earned leaves to Employees''' '''Allocate earned leaves to Employees'''
e_leave_types = frappe.get_all("Leave Type", e_leave_types = frappe.get_all("Leave Type",

View File

@ -31,5 +31,9 @@ frappe.ui.form.on('Blanket Order', {
}); });
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
},
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
} }
}); });

View File

@ -0,0 +1,29 @@
{
"attach_print": 0,
"channel": "Email",
"condition": "doc.status == \"Received\" or doc.status == \"Partially Received\"",
"creation": "2019-04-29 11:53:23.981418",
"days_in_advance": 0,
"docstatus": 0,
"doctype": "Notification",
"document_type": "Material Request",
"enabled": 1,
"event": "Value Change",
"idx": 0,
"is_standard": 1,
"message": "<b>Material Request Type</b>: {{ doc.material_request_type }}<br>\n<b>Company</b>: {{ doc.company }}\n\n<h3>Order Summary</h3>\n\n<table border=2 >\n <tr align=\"center\">\n <th>Item Name</th>\n <th>Received Quantity</th>\n </tr>\n {% for item in doc.items %}\n {% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}\n <tr align=\"center\">\n <td>{{ item.item_code }}</td>\n <td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>\n </tr>\n {% endif %}\n {% endfor %}\n</table>",
"method": "",
"modified": "2019-05-01 18:02:51.090037",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Receipt Notification",
"owner": "Administrator",
"recipients": [
{
"email_by_document_field": "requested_by"
}
],
"sender_email": "",
"subject": "{{ doc.name }} has been received",
"value_changed": "status"
}

View File

@ -0,0 +1,19 @@
<b>Material Request Type</b>: {{ doc.material_request_type }}<br>
<b>Company</b>: {{ doc.company }}
<h3>Order Summary</h3>
<table border=2 >
<tr align="center">
<th>Item Name</th>
<th>Received Quantity</th>
</tr>
{% for item in doc.items %}
{% if frappe.utils.flt(item.received_qty, 2) > 0.0 %}
<tr align="center">
<td>{{ item.item_code }}</td>
<td>{{ frappe.utils.flt(item.received_qty, 2) }}</td>
</tr>
{% endif %}
{% endfor %}
</table>

View File

@ -0,0 +1,7 @@
from __future__ import unicode_literals
import frappe
def get_context(context):
# do your magic here
pass

View File

@ -512,6 +512,7 @@ erpnext.patches.v11_0.rename_employee_loan_to_loan
erpnext.patches.v11_0.move_leave_approvers_from_employee #13-06-2018 erpnext.patches.v11_0.move_leave_approvers_from_employee #13-06-2018
erpnext.patches.v11_0.update_department_lft_rgt erpnext.patches.v11_0.update_department_lft_rgt
erpnext.patches.v11_0.add_default_email_template_for_leave erpnext.patches.v11_0.add_default_email_template_for_leave
execute:frappe.reload_doc("HR", "doctype", "HR Settings")
erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018 erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018
erpnext.patches.v11_0.uom_conversion_data #30-06-2018 erpnext.patches.v11_0.uom_conversion_data #30-06-2018
erpnext.patches.v10_0.taxes_issue_with_pos erpnext.patches.v10_0.taxes_issue_with_pos

View File

@ -70,7 +70,7 @@ def update_instructors(comp_dict):
emp_details = frappe.get_all("Employee", fields=["name", "company"]) emp_details = frappe.get_all("Employee", fields=["name", "company"])
for employee in emp_details: for employee in emp_details:
records = comp_dict[employee.company] records = comp_dict[employee.company] if employee.company else []
for department in records: for department in records:
when_then.append(''' when_then.append('''

View File

@ -8,6 +8,17 @@ def execute():
return return
frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Declaration") frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Declaration")
frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Proof Submission") frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Proof Submission")
frappe.reload_doc("hr", "doctype", "Employee Grade")
frappe.reload_doc("hr", "doctype", "Leave Policy")
frappe.reload_doc("accounts", "doctype", "Bank Account")
frappe.reload_doc("accounts", "doctype", "Tax Withholding Category")
frappe.reload_doc("accounts", "doctype", "Allowed To Transact With")
frappe.reload_doc("accounts", "doctype", "Finance Book")
frappe.reload_doc("accounts", "doctype", "Loyalty Program")
frappe.reload_doc("stock", "doctype", "Item Barcode")
make_custom_fields() make_custom_fields()
frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges") frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges")

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
import frappe import frappe
def execute(): def execute():
hr_settings = frappe.get_single("HR Settings") hr_settings = frappe.get_single("HR Settings")
hr_settings.leave_approval_notification_template = "Leave Approval Notification" hr_settings.leave_approval_notification_template = "Leave Approval Notification"
hr_settings.leave_status_notification_template = "Leave Status Notification" hr_settings.leave_status_notification_template = "Leave Status Notification"

View File

@ -7,6 +7,9 @@ def execute():
if not company: if not company:
return return
frappe.reload_doc('hr', 'doctype', 'payroll_period')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration_category')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission_detail')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission')

View File

@ -102,6 +102,9 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
for attribute, values in attribute_filters.items(): for attribute, values in attribute_filters.items():
attribute_values = values attribute_values = values
if not isinstance(attribute_values, list):
attribute_values = [attribute_values]
if not attribute_values: continue if not attribute_values: continue
wheres = [] wheres = []

View File

@ -65,7 +65,7 @@ class Project(Document):
'name': ("not in", self.deleted_task_list) 'name': ("not in", self.deleted_task_list)
}) })
return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc") return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc, status asc")
def validate(self): def validate(self):
self.validate_weights() self.validate_weights()
@ -485,16 +485,17 @@ def daily_reminder():
projects = get_projects_for_collect_progress("Daily", fields) projects = get_projects_for_collect_progress("Daily", fields)
for project in projects: for project in projects:
if not check_project_update_exists(project.name, project.get("daily_time_to_send")): if allow_to_make_project_update(project.name, project.get("daily_time_to_send"), "Daily"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def twice_daily_reminder(): def twice_daily_reminder():
fields = ["first_email", "second_email"] fields = ["first_email", "second_email"]
projects = get_projects_for_collect_progress("Twice Daily", fields) projects = get_projects_for_collect_progress("Twice Daily", fields)
fields.remove("name")
for project in projects: for project in projects:
for d in fields: for d in fields:
if not check_project_update_exists(project.name, project.get(d)): if allow_to_make_project_update(project.name, project.get(d), "Twicely"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def weekly_reminder(): def weekly_reminder():
@ -506,14 +507,19 @@ def weekly_reminder():
if current_day != project.day_to_send: if current_day != project.day_to_send:
continue continue
if not check_project_update_exists(project.name, project.get("weekly_time_to_send")): if allow_to_make_project_update(project.name, project.get("weekly_time_to_send"), "Weekly"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def check_project_update_exists(project, time): def allow_to_make_project_update(project, time, frequency):
data = frappe.db.sql(""" SELECT name from `tabProject Update` data = frappe.db.sql(""" SELECT name from `tabProject Update`
WHERE project = %s and date = %s and time >= %s """, (project, today(), time)) WHERE project = %s and date = %s """, (project, today()))
return True if data and data[0][0] else False # len(data) > 1 condition is checked for twicely frequency
if data and (frequency in ['Daily', 'Weekly'] or len(data) > 1):
return False
if get_time(nowtime()) >= get_time(time):
return True
def get_projects_for_collect_progress(frequency, fields): def get_projects_for_collect_progress(frequency, fields):
fields.extend(["name"]) fields.extend(["name"])

View File

@ -196,6 +196,7 @@
{ {
"fieldname": "description", "fieldname": "description",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_preview": 1,
"label": "Task Description", "label": "Task Description",
"oldfieldname": "description", "oldfieldname": "description",
"oldfieldtype": "Text Editor", "oldfieldtype": "Text Editor",
@ -351,7 +352,7 @@
"icon": "fa fa-check", "icon": "fa fa-check",
"idx": 1, "idx": 1,
"max_attachments": 5, "max_attachments": 5,
"modified": "2019-05-01 13:30:29.458916", "modified": "2019-05-16 09:51:15.599416",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Task", "name": "Task",
@ -371,6 +372,7 @@
], ],
"search_fields": "subject", "search_fields": "subject",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"show_preview_popup": 1,
"sort_order": "DESC", "sort_order": "DESC",
"timeline_field": "project", "timeline_field": "project",
"title_field": "subject", "title_field": "subject",

View File

@ -267,6 +267,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) { if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
this.setup_item_selector(); this.setup_item_selector();
this.frm.get_field("items").grid.set_multiple_add("item_code", "qty");
} }
}, },

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