Merge branch 'develop' into sla_fix

This commit is contained in:
Himanshu 2019-05-15 00:08:45 +05:30 committed by GitHub
commit 8b2a76a03a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1511 additions and 1628 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

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

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

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

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

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

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

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

@ -4,5 +4,18 @@
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.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

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

@ -520,13 +520,20 @@ class SalarySlip(TransactionBase):
def email_salary_slip(self): def email_salary_slip(self):
receiver = frappe.db.get_value("Employee", self.employee, "prefered_email") receiver = frappe.db.get_value("Employee", self.employee, "prefered_email")
hr_settings = frappe.get_single("HR Settings")
message = "Please see attachment"
password = None
if hr_settings.encrypt_salary_slips_in_emails:
password = generate_password_for_pdf(hr_settings.password_policy, self.employee)
message += """<br>Note: Your salary slip is password protected,
the password to unlock the PDF is of the format {0}. """.format(hr_settings.password_policy)
if receiver: if receiver:
email_args = { email_args = {
"recipients": [receiver], "recipients": [receiver],
"message": _("Please see attachment"), "message": _(message),
"subject": 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date), "subject": 'Salary Slip - from {0} to {1}'.format(self.start_date, self.end_date),
"attachments": [frappe.attach_print(self.doctype, self.name, file_name=self.name)], "attachments": [frappe.attach_print(self.doctype, self.name, file_name=self.name, password=password)],
"reference_doctype": self.doctype, "reference_doctype": self.doctype,
"reference_name": self.name "reference_name": self.name
} }
@ -843,3 +850,7 @@ def unlink_ref_doc_from_salary_slip(ref_no):
for ss in linked_ss: for ss in linked_ss:
ss_doc = frappe.get_doc("Salary Slip", ss) ss_doc = frappe.get_doc("Salary Slip", ss)
frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "") frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "")
def generate_password_for_pdf(policy_template, employee):
employee = frappe.get_doc("Employee", employee)
return policy_template.format(**employee.as_dict())

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

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

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

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

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

@ -8,12 +8,7 @@
<div class='card-body'> <div class='card-body'>
<h5 class="card-title">{{ course.course_name }}</h5> <h5 class="card-title">{{ course.course_name }}</h5>
<span class="course-list text-muted" id="getting-started"> <span class="course-list text-muted" id="getting-started">
Topics {{ course.course_intro }}
<ul class="mb-0 mt-1" style="padding-left: 1.5em;">
<li v-for="topic in course.topics" :key="topic.name">
<div>{{ topic.topic_name }}</div>
</li>
</ul>
</span> </span>
</div> </div>
<div class='p-3' style="display: flex; justify-content: space-between;"> <div class='p-3' style="display: flex; justify-content: space-between;">

View File

@ -6,8 +6,7 @@
<div class="col-md-8"> <div class="col-md-8">
<h2>{{ contentData.name }}</h2> <h2>{{ contentData.name }}</h2>
<span class="text-muted"> <span class="text-muted">
<i class="octicon octicon-clock" title="Duration"></i> {{ contentData.duration }} Mins <i class="octicon octicon-clock" title="Duration"></i> <span v-if="contentData.duration"> {{ contentData.duration }} Mins &mdash; </span><span v-if="contentData.publish_date"> Published on {{ contentData.publish_date }}. </span>
&mdash; Published on {{ contentData.publish_date }}.
</span> </span>
</div> </div>
<div class="col-md-4 text-right"> <div class="col-md-4 text-right">

View File

@ -390,15 +390,6 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
} }
}) })
if (mandatory.length) {
frappe.msgprint({
title: __('Missing Values Required'),
message: __('Following fields have missing values:') + '<br><br><ul><li>' + mandatory.join('<li>') + '</ul>',
indicator: 'orange'
});
return {};
}
if (this.is_manufacturer) { if (this.is_manufacturer) {
$.each(this.manufacturer_fields, function(index, field) { $.each(this.manufacturer_fields, function(index, field) {
attribute[field.fieldname] = me.dialog.fields_dict[field.fieldname].input.value; attribute[field.fieldname] = me.dialog.fields_dict[field.fieldname].input.value;

View File

@ -227,18 +227,18 @@ class GSTR3BReport(Document):
for d in inter_state_supply.get("Unregistered", []): for d in inter_state_supply.get("Unregistered", []):
self.report_dict["inter_sup"]["unreg_details"].append(d) self.report_dict["inter_sup"]["unreg_details"].append(d)
self.report_dict["sup_details"]["osup_det"]["txval"] += d["txval"] self.report_dict["sup_details"]["osup_det"]["txval"] += flt(d["txval"], 2)
self.report_dict["sup_details"]["osup_det"]["iamt"] += d["iamt"] self.report_dict["sup_details"]["osup_det"]["iamt"] += flt(d["iamt"], 2)
for d in inter_state_supply.get("Registered Composition", []): for d in inter_state_supply.get("Registered Composition", []):
self.report_dict["inter_sup"]["comp_details"].append(d) self.report_dict["inter_sup"]["comp_details"].append(d)
self.report_dict["sup_details"]["osup_det"]["txval"] += d["txval"] self.report_dict["sup_details"]["osup_det"]["txval"] += flt(d["txval"], 2)
self.report_dict["sup_details"]["osup_det"]["iamt"] += d["iamt"] self.report_dict["sup_details"]["osup_det"]["iamt"] += flt(d["iamt"], 2)
for d in inter_state_supply.get("UIN Holders", []): for d in inter_state_supply.get("UIN Holders", []):
self.report_dict["inter_sup"]["uin_details"].append(d) self.report_dict["inter_sup"]["uin_details"].append(d)
self.report_dict["sup_details"]["osup_det"]["txval"] += d["txval"] self.report_dict["sup_details"]["osup_det"]["txval"] += flt(d["txval"], 2)
self.report_dict["sup_details"]["osup_det"]["iamt"] += d["iamt"] self.report_dict["sup_details"]["osup_det"]["iamt"] += flt(d["iamt"], 2)
def get_total_taxable_value(self, doctype, reverse_charge): def get_total_taxable_value(self, doctype, reverse_charge):

View File

@ -362,9 +362,9 @@ def make_fixtures(company=None):
doc.flags.ignore_permissions = True doc.flags.ignore_permissions = True
doc.insert() doc.insert()
except frappe.NameError: except frappe.NameError:
pass frappe.clear_messages()
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass frappe.clear_messages()
# create records for Tax Withholding Category # create records for Tax Withholding Category
set_tax_withholding_category(company) set_tax_withholding_category(company)

View File

@ -29,10 +29,10 @@ class Gstr1Report(object):
place_of_supply, place_of_supply,
ecommerce_gstin, ecommerce_gstin,
reverse_charge, reverse_charge,
gst_category, invoice_type,
return_against, return_against,
is_return, is_return,
gst_category, invoice_type,
export_type, export_type,
port_code, port_code,
shipping_bill_number, shipping_bill_number,
@ -324,8 +324,8 @@ class Gstr1Report(object):
"fieldtype": "Data" "fieldtype": "Data"
}, },
{ {
"fieldname": "gst_category", "fieldname": "invoice_type",
"label": "GST Category", "label": "Invoice Type",
"fieldtype": "Data" "fieldtype": "Data"
}, },
{ {
@ -577,7 +577,7 @@ def get_json():
download_json_file(report_name, filters["type_of_business"], gst_json) download_json_file(report_name, filters["type_of_business"], gst_json)
def get_b2b_json(res, gstin): def get_b2b_json(res, gstin):
inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, [] inv_type, out = {"Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
for gst_in in res: for gst_in in res:
b2b_item, inv = {"ctin": gst_in, "inv": []}, [] b2b_item, inv = {"ctin": gst_in, "inv": []}, []
if not gst_in: continue if not gst_in: continue
@ -586,7 +586,7 @@ def get_b2b_json(res, gstin):
inv_item = get_basic_invoice_detail(invoice[0]) inv_item = get_basic_invoice_detail(invoice[0])
inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]) inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
inv_item["rchrg"] = invoice[0]["reverse_charge"] inv_item["rchrg"] = invoice[0]["reverse_charge"]
inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"") inv_item["inv_typ"] = inv_type.get(invoice[0].get("invoice_type", ""),"")
if inv_item["pos"]=="00": continue if inv_item["pos"]=="00": continue
inv_item["itms"] = [] inv_item["itms"] = []

View File

@ -363,7 +363,12 @@ def add_sale_stages():
def install_company(args): def install_company(args):
records = [ records = [
# Fiscal Year # Fiscal Year
{ "doctype": "Fiscal Year", 'year': get_fy_details(args.fy_start_date, args.fy_end_date), 'year_start_date': args.fy_start_date, 'year_end_date': args.fy_end_date }, {
'doctype': "Fiscal Year",
'year': get_fy_details(args.fy_start_date, args.fy_end_date),
'year_start_date': args.fy_start_date,
'year_end_date': args.fy_end_date
},
# Company # Company
{ {

View File

@ -585,7 +585,7 @@ $.extend(erpnext.item, {
"label": row.attribute, "label": row.attribute,
"fieldname": row.attribute, "fieldname": row.attribute,
"fieldtype": fieldtype, "fieldtype": fieldtype,
"reqd": 1, "reqd": 0,
"description": desc "description": desc
}) })
} }
@ -600,6 +600,7 @@ $.extend(erpnext.item, {
if(!args) return; if(!args) return;
frappe.call({ frappe.call({
method:"erpnext.controllers.item_variant.get_variant", method:"erpnext.controllers.item_variant.get_variant",
btn: d.get_primary_btn(),
args: { args: {
"template": frm.doc.name, "template": frm.doc.name,
"args": d.get_values() "args": d.get_values()

View File

@ -3552,7 +3552,7 @@
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
@ -3585,7 +3585,7 @@
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
@ -4272,7 +4272,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2019-04-08 11:47:59.269724", "modified": "2019-05-09 18:47:30.475833",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -800,10 +800,12 @@ class Item(WebsiteGenerator):
def validate_variant_attributes(self): def validate_variant_attributes(self):
if self.is_new() and self.variant_of and self.variant_based_on == 'Item Attribute': if self.is_new() and self.variant_of and self.variant_based_on == 'Item Attribute':
# remove attributes with no attribute_value set
self.attributes = [d for d in self.attributes if cstr(d.attribute_value).strip()]
args = {} args = {}
for d in self.attributes: for i, d in enumerate(self.attributes):
if cstr(d.attribute_value).strip() == '': d.idx = i + 1
frappe.throw(_("Please specify Attribute Value for attribute {0}").format(d.attribute))
args[d.attribute] = d.attribute_value args[d.attribute] = d.attribute_value
variant = get_variant(self.variant_of, args, self.name) variant = get_variant(self.variant_of, args, self.name)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
frappe.listview_settings['Material Request'] = { frappe.listview_settings['Material Request'] = {
add_fields: ["material_request_type", "status", "per_ordered"], add_fields: ["material_request_type", "status", "per_ordered", "per_received"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.status=="Stopped") { if(doc.status=="Stopped") {
return [__("Stopped"), "red", "status,=,Stopped"]; return [__("Stopped"), "red", "status,=,Stopped"];
@ -8,7 +8,11 @@ frappe.listview_settings['Material Request'] = {
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {
return [__("Partially ordered"), "yellow", "per_ordered,<,100"]; return [__("Partially ordered"), "yellow", "per_ordered,<,100"];
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 100) { } else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 100) {
if (doc.material_request_type == "Purchase") { if (doc.material_request_type == "Purchase" && flt(doc.per_received, 2) < 100 && flt(doc.per_received, 2) > 0) {
return [__("Partially Received"), "yellow", "per_received,<,100"];
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, 2) == 100) {
return [__("Received"), "green", "per_received,=,100"];
} else if (doc.material_request_type == "Purchase") {
return [__("Ordered"), "green", "per_ordered,=,100"]; return [__("Ordered"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Material Transfer") { } else if (doc.material_request_type == "Material Transfer") {
return [__("Transfered"), "green", "per_ordered,=,100"]; return [__("Transfered"), "green", "per_ordered,=,100"];

View File

@ -629,7 +629,8 @@ def make_material_request(**args):
"item_code": args.item_code or "_Test Item", "item_code": args.item_code or "_Test Item",
"qty": args.qty or 10, "qty": args.qty or 10,
"schedule_date": args.schedule_date or today(), "schedule_date": args.schedule_date or today(),
"warehouse": args.warehouse or "_Test Warehouse - _TC" "warehouse": args.warehouse or "_Test Warehouse - _TC",
"cost_center": args.cost_center or "_Test Cost Center - _TC"
}) })
mr.insert() mr.insert()
if not args.do_not_submit: if not args.do_not_submit:

View File

@ -38,6 +38,7 @@
"projected_qty", "projected_qty",
"actual_qty", "actual_qty",
"ordered_qty", "ordered_qty",
"received_qty",
"accounting_details", "accounting_details",
"expense_account", "expense_account",
"column_break_35", "column_break_35",
@ -364,11 +365,16 @@
{ {
"fieldname": "section_break_37", "fieldname": "section_break_37",
"fieldtype": "Section Break" "fieldtype": "Section Break"
},
{
"fieldname": "received_qty",
"fieldtype": "Data",
"label": "Received Quantity"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-01 17:48:12.361976", "modified": "2019-05-08 10:27:25.008801",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Material Request Item", "name": "Material Request Item",

View File

@ -35,6 +35,17 @@ class PurchaseReceipt(BuyingController):
'percent_join_field': 'purchase_order', 'percent_join_field': 'purchase_order',
'overflow_type': 'receipt' 'overflow_type': 'receipt'
}, },
{
'source_dt': 'Purchase Receipt Item',
'target_dt': 'Material Request Item',
'join_field': 'material_request_item',
'target_field': 'received_qty',
'target_parent_dt': 'Material Request',
'target_parent_field': 'per_received',
'target_ref_field': 'qty',
'source_field': 'qty',
'percent_join_field': 'material_request'
},
{ {
'source_dt': 'Purchase Receipt Item', 'source_dt': 'Purchase Receipt Item',
'target_dt': 'Purchase Order Item', 'target_dt': 'Purchase Order Item',

View File

@ -64,7 +64,9 @@
"rejected_warehouse", "rejected_warehouse",
"quality_inspection", "quality_inspection",
"purchase_order", "purchase_order",
"material_request",
"purchase_order_item", "purchase_order_item",
"material_request_item",
"column_break_40", "column_break_40",
"is_fixed_asset", "is_fixed_asset",
"asset", "asset",
@ -753,11 +755,22 @@
"fieldname": "image_section", "fieldname": "image_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Image" "label": "Image"
},
{
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request"
},
{
"fieldname": "material_request_item",
"fieldtype": "Data",
"label": "Material Request Item"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-05-01 17:45:17.447900", "modified": "2019-05-08 10:25:27.157675",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt Item", "name": "Purchase Receipt Item",

View File

@ -129,6 +129,10 @@ class StockEntry(StockController):
if self.purpose not in valid_purposes: if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes))) frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
if self.job_card and self.purpose != 'Material Transfer for Manufacture':
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
.format(self.job_card))
def set_transfer_qty(self): def set_transfer_qty(self):
for item in self.get("items"): for item in self.get("items"):
if not flt(item.qty): if not flt(item.qty):

View File

@ -5,50 +5,35 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from six import iteritems
def get_level(): def get_level():
activation_level = 0 activation_level = 0
sales_data = []
min_count = 0
doctypes = {"Item": 5, "Customer": 5, "Sales Order": 2, "Sales Invoice": 2, "Purchase Order": 2, "Employee": 3, "Lead": 3, "Quotation": 3,
"Payment Entry": 2, "User": 5, "Student": 5, "Instructor": 5, "BOM": 3, "Journal Entry": 3, "Stock Entry": 3}
for doctype, min_count in iteritems(doctypes):
count = frappe.db.count(doctype)
if count > min_count:
activation_level += 1
sales_data.append({doctype: count})
if frappe.db.get_single_value('System Settings', 'setup_complete'): if frappe.db.get_single_value('System Settings', 'setup_complete'):
activation_level = 1
if frappe.db.count('Item') > 5:
activation_level += 1 activation_level += 1
if frappe.db.count('Customer') > 5: communication_number = frappe.db.count('Communication', dict(communication_medium='Email'))
activation_level += 1 if communication_number > 10:
if frappe.db.count('Sales Order') > 2:
activation_level += 1
if frappe.db.count('Purchase Order') > 2:
activation_level += 1
if frappe.db.count('Employee') > 3:
activation_level += 1
if frappe.db.count('Lead') > 3:
activation_level += 1
if frappe.db.count('Payment Entry') > 2:
activation_level += 1
if frappe.db.count('Communication', dict(communication_medium='Email')) > 10:
activation_level += 1
if frappe.db.count('User') > 5:
activation_level += 1
if frappe.db.count('Student') > 5:
activation_level += 1
if frappe.db.count('Instructor') > 5:
activation_level += 1 activation_level += 1
sales_data.append({"Communication": communication_number})
# recent login # recent login
if frappe.db.sql('select name from tabUser where last_login > date_sub(now(), interval 2 day) limit 1'): if frappe.db.sql('select name from tabUser where last_login > date_sub(now(), interval 2 day) limit 1'):
activation_level += 1 activation_level += 1
return activation_level level = {"activation_level": activation_level, "sales_data": sales_data}
return level
def get_help_messages(): def get_help_messages():
'''Returns help messages to be shown on Desktop''' '''Returns help messages to be shown on Desktop'''

View File

@ -5,6 +5,35 @@
{% block navbar %}{% endblock %} {% block navbar %}{% endblock %}
{% block content %} {% block content %}
{% if lms_enabled %}
<div id="lms-app"></div> <div id="lms-app"></div>
<script type="text/javascript" src="/assets/js/lms.min.js"></script> <script type="text/javascript" src="/assets/js/lms.min.js"></script>
{% else %}
<style>
.hero-and-content {
background-color: #f5f7fa;
}
header, footer {
display: none;
}
html, body {
background-color: #f5f7fa;
}
{% include "templates/styles/card_style.css" %}
</style>
<div class='page-card'>
<div class='page-card-head'>
<span class='indicator darkgrey'>{{_("Page Missing or Moved")}}</span>
</div>
<p>{{_("The page you are looking for is missing. This could be because it is moved or there is a typo in the link.")}}</p>
<div><a href='/' class='btn btn-primary btn-sm'>{{ _("Home") }}</a></div>
</div>
<p class='text-muted text-center small' style='margin-top: -20px;'>{{ _("Error Code: {0}").format('404') }}</p>
<style>
.hero-and-content {
background-color: #f5f7fa;
}
</style>
{% endif %}
{% endblock %} {% endblock %}