From b4ad5c01583356365866dc24bd0ad6c74d617e4b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 4 May 2019 20:29:21 +0530 Subject: [PATCH 01/96] feat: Ability to add custom dimensions --- .../accounts/doctype/dimension/__init__.py | 0 .../accounts/doctype/dimension/dimension.js | 8 +++ .../accounts/doctype/dimension/dimension.json | 48 ++++++++++++++ .../accounts/doctype/dimension/dimension.py | 65 +++++++++++++++++++ .../doctype/dimension/test_dimension.py | 10 +++ 5 files changed, 131 insertions(+) create mode 100644 erpnext/accounts/doctype/dimension/__init__.py create mode 100644 erpnext/accounts/doctype/dimension/dimension.js create mode 100644 erpnext/accounts/doctype/dimension/dimension.json create mode 100644 erpnext/accounts/doctype/dimension/dimension.py create mode 100644 erpnext/accounts/doctype/dimension/test_dimension.py diff --git a/erpnext/accounts/doctype/dimension/__init__.py b/erpnext/accounts/doctype/dimension/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/dimension/dimension.js b/erpnext/accounts/doctype/dimension/dimension.js new file mode 100644 index 00000000000..4926fff7da8 --- /dev/null +++ b/erpnext/accounts/doctype/dimension/dimension.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Dimension', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/accounts/doctype/dimension/dimension.json b/erpnext/accounts/doctype/dimension/dimension.json new file mode 100644 index 00000000000..4937e4d7a23 --- /dev/null +++ b/erpnext/accounts/doctype/dimension/dimension.json @@ -0,0 +1,48 @@ +{ + "autoname": "field:label", + "creation": "2019-05-04 18:13:37.002352", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "label", + "fieldname" + ], + "fields": [ + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "fieldname", + "fieldtype": "Data", + "label": "Fieldname" + } + ], + "modified": "2019-05-04 18:59:14.969008", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Dimension", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/dimension/dimension.py b/erpnext/accounts/doctype/dimension/dimension.py new file mode 100644 index 00000000000..524b6acb007 --- /dev/null +++ b/erpnext/accounts/doctype/dimension/dimension.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe import scrub + +class Dimension(Document): + + def before_insert(self): + self.set_fieldname() + self.make_dimension_in_accounting_doctypes() + + def on_trash(self): + self.delete_dimension() + + def set_fieldname(self): + if not self.fieldname: + self.fieldname = scrub(self.label) + + def make_dimension_in_accounting_doctypes(self): + last_created_dimension = get_last_created_dimension() + + doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", + "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] + + df = { + "fieldname": self.fieldname, + "label": self.label, + "fieldtype": "Data", + "insert_after": last_created_dimension if last_created_dimension else "project" + } + + for doctype in doclist: + create_custom_field(doctype, df) + + def delete_dimension(self): + doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", + "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] + + frappe.db.sql(""" + DELETE FROM `tabCustom Field` + WHERE fieldname = %s + AND dt IN (%s)""" % + ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) + + frappe.db.sql(""" + DELETE FROM `tabProperty Setter` + WHERE field_name = %s + AND doc_type IN (%s)""" % + ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) + + for doc in doclist: + frappe.clear_cache(doctype=doc) + + +def get_last_created_dimension(): + last_created_dimension = frappe.db.sql("select fieldname, max(creation) from `tabDimension`", as_dict=1) + + if last_created_dimension[0]: + return last_created_dimension[0].fieldname diff --git a/erpnext/accounts/doctype/dimension/test_dimension.py b/erpnext/accounts/doctype/dimension/test_dimension.py new file mode 100644 index 00000000000..f726e9da66b --- /dev/null +++ b/erpnext/accounts/doctype/dimension/test_dimension.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDimension(unittest.TestCase): + pass From d83cf65be11c781cac7f1cb7b4a4a02993aaf3e4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 12 May 2019 18:34:23 +0530 Subject: [PATCH 02/96] fix: Added dimensions in financial reports and general ledger --- .../__init__.py | 0 .../accounting_dimension.js} | 2 +- .../accounting_dimension.json} | 13 +++++-- .../accounting_dimension.py} | 38 ++++++++++++------- .../test_accounting_dimension.py} | 2 +- .../accounts/report/financial_statements.py | 31 ++++++++++----- .../report/general_ledger/general_ledger.js | 21 ++++++++++ .../report/general_ledger/general_ledger.py | 8 ++++ erpnext/controllers/accounts_controller.py | 9 +++++ erpnext/public/js/financial_statements.js | 25 +++++++++++- 10 files changed, 119 insertions(+), 30 deletions(-) rename erpnext/accounts/doctype/{dimension => accounting_dimension}/__init__.py (100%) rename erpnext/accounts/doctype/{dimension/dimension.js => accounting_dimension/accounting_dimension.js} (78%) rename erpnext/accounts/doctype/{dimension/dimension.json => accounting_dimension/accounting_dimension.json} (76%) rename erpnext/accounts/doctype/{dimension/dimension.py => accounting_dimension/accounting_dimension.py} (57%) rename erpnext/accounts/doctype/{dimension/test_dimension.py => accounting_dimension/test_accounting_dimension.py} (79%) diff --git a/erpnext/accounts/doctype/dimension/__init__.py b/erpnext/accounts/doctype/accounting_dimension/__init__.py similarity index 100% rename from erpnext/accounts/doctype/dimension/__init__.py rename to erpnext/accounts/doctype/accounting_dimension/__init__.py diff --git a/erpnext/accounts/doctype/dimension/dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js similarity index 78% rename from erpnext/accounts/doctype/dimension/dimension.js rename to erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 4926fff7da8..0676731840e 100644 --- a/erpnext/accounts/doctype/dimension/dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -1,7 +1,7 @@ // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Dimension', { +frappe.ui.form.on('Accounting Dimension', { // refresh: function(frm) { // } diff --git a/erpnext/accounts/doctype/dimension/dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json similarity index 76% rename from erpnext/accounts/doctype/dimension/dimension.json rename to erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index 4937e4d7a23..13902d6335f 100644 --- a/erpnext/accounts/doctype/dimension/dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -4,6 +4,7 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "document_type", "label", "fieldname" ], @@ -13,19 +14,25 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Label", - "reqd": 1, "unique": 1 }, { "fieldname": "fieldname", "fieldtype": "Data", "label": "Fieldname" + }, + { + "fieldname": "document_type", + "fieldtype": "Link", + "label": "Document Type", + "options": "DocType", + "reqd": 1 } ], - "modified": "2019-05-04 18:59:14.969008", + "modified": "2019-05-09 15:30:55.339917", "modified_by": "Administrator", "module": "Accounts", - "name": "Dimension", + "name": "Accounting Dimension", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/dimension/dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py similarity index 57% rename from erpnext/accounts/doctype/dimension/dimension.py rename to erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 524b6acb007..93a25e7c0a6 100644 --- a/erpnext/accounts/doctype/dimension/dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -8,22 +8,26 @@ from frappe.model.document import Document from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe import scrub +from frappe.utils import cstr -class Dimension(Document): +class AccountingDimension(Document): def before_insert(self): - self.set_fieldname() - self.make_dimension_in_accounting_doctypes() + self.set_fieldname_and_label() + self.make_accounting_dimension_in_accounting_doctypes() def on_trash(self): - self.delete_dimension() + self.delete_accounting_dimension() + + def set_fieldname_and_label(self): + if not self.label: + self.label = cstr(self.document_type) - def set_fieldname(self): if not self.fieldname: self.fieldname = scrub(self.label) - def make_dimension_in_accounting_doctypes(self): - last_created_dimension = get_last_created_dimension() + def make_accounting_dimension_in_accounting_doctypes(self): + last_created_accounting_dimension = get_last_created_accounting_dimension() doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] @@ -31,14 +35,15 @@ class Dimension(Document): df = { "fieldname": self.fieldname, "label": self.label, - "fieldtype": "Data", - "insert_after": last_created_dimension if last_created_dimension else "project" + "fieldtype": "Link", + "options": self.document_type, + "insert_after": last_created_accounting_dimension if last_created_accounting_dimension else "project" } for doctype in doclist: create_custom_field(doctype, df) - def delete_dimension(self): + def delete_accounting_dimension(self): doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] @@ -58,8 +63,13 @@ class Dimension(Document): frappe.clear_cache(doctype=doc) -def get_last_created_dimension(): - last_created_dimension = frappe.db.sql("select fieldname, max(creation) from `tabDimension`", as_dict=1) +def get_last_created_accounting_dimension(): + last_created_accounting_dimension = frappe.db.sql("select fieldname, max(creation) from `tabAccounting Dimension`", as_dict=1) - if last_created_dimension[0]: - return last_created_dimension[0].fieldname + if last_created_accounting_dimension[0]: + return last_created_accounting_dimension[0].fieldname + +def get_accounting_dimensions(): + accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["fieldname"]) + + return [d.fieldname for d in accounting_dimensions] diff --git a/erpnext/accounts/doctype/dimension/test_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py similarity index 79% rename from erpnext/accounts/doctype/dimension/test_dimension.py rename to erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index f726e9da66b..b4368c4e61e 100644 --- a/erpnext/accounts/doctype/dimension/test_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestDimension(unittest.TestCase): +class TestAccountingDimension(unittest.TestCase): pass diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 214031582f0..7f0cbaa2638 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -16,6 +16,7 @@ from frappe import _ from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate) from six import itervalues +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions def get_period_list(from_fiscal_year, to_fiscal_year, periodicity, accumulated_values=False, company=None, reset_period_on_fy_change=True): @@ -348,20 +349,23 @@ def set_gl_entries_by_account( additional_conditions += " and account in ({})"\ .format(", ".join([frappe.db.escape(d) for d in accounts])) + gl_filters = { + "company": company, + "from_date": from_date, + "to_date": to_date, + } + + for key, value in filters.items(): + if value: + gl_filters.update({ + key: value + }) + gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` where company=%(company)s {additional_conditions} and posting_date <= %(to_date)s - order by account, posting_date""".format(additional_conditions=additional_conditions), - { - "company": company, - "from_date": from_date, - "to_date": to_date, - "cost_center": filters.cost_center, - "project": filters.project, - "finance_book": filters.get("finance_book") - }, - as_dict=True) + order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) if filters and filters.get('presentation_currency'): convert_to_presentation_currency(gl_entries, get_currency(filters)) @@ -375,6 +379,8 @@ def set_gl_entries_by_account( def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions = [] + accounting_dimensions = get_accounting_dimensions() + if ignore_closing_entries: additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'") @@ -395,6 +401,11 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): if filters.get("finance_book"): additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") + if accounting_dimensions: + for dimension in accounting_dimensions: + if filters.get(dimension): + additional_conditions.append("{0} in (%({0})s)".format(dimension)) + return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" def get_cost_centers_with_children(cost_centers): diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 2826760dd89..7db8786c35f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -214,3 +214,24 @@ frappe.query_reports["General Ledger"] = { } ] } + +let dimension_filters = get_dimension_filters(); + +dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["General Ledger"].filters.push({ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); +}); + +async function get_dimension_filters() { + let dimensions = await frappe.db.get_list('Accounting Dimension', { + fields: ['label', 'fieldname', 'document_type'], + }); + + return dimensions; +} diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 44ca8d3549a..033b9ba36a0 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -10,6 +10,7 @@ from frappe import _, _dict from erpnext.accounts.utils import get_account_currency from erpnext.accounts.report.financial_statements import get_cost_centers_with_children from six import iteritems +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions def execute(filters=None): if not filters: @@ -195,6 +196,13 @@ def get_conditions(filters): if match_conditions: conditions.append(match_conditions) + accounting_dimensions = get_accounting_dimensions() + + if accounting_dimensions: + for dimension in accounting_dimensions: + if filters.get(dimension): + conditions.append("{0} in (%({0})s)".format(dimension)) + return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 155a996a15a..4f4084a2d77 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -16,6 +16,7 @@ from erpnext.accounts.party import get_party_account_currency, validate_party_fr from erpnext.accounts.doctype.pricing_rule.utils import validate_pricing_rules from erpnext.exceptions import InvalidCurrency from six import text_type +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") @@ -365,6 +366,14 @@ class AccountsController(TransactionBase): 'party': None, 'project': self.get("project") }) + + accounting_dimensions = get_accounting_dimensions() + dimension_dict = frappe._dict() + + for dimension in accounting_dimensions: + dimension_dict[dimension] = self.get(dimension) + + gl_dict.update(dimension_dict) gl_dict.update(args) if not account_currency: diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 36746cddc30..77e67c49edc 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -63,7 +63,7 @@ erpnext.financial_statements = { }; function get_filters(){ - return [ + let filters = [ { "fieldname":"company", "label": __("Company"), @@ -149,4 +149,27 @@ function get_filters(){ "options": erpnext.get_presentation_currency_list() } ] + + let dimension_filters = get_dimension_filters() + + dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + filters.push({ + "fieldname": dimension["fieldname"], + "label": __(dimension["label"]), + "fieldtype": "Link", + "options": dimension["document_type"] + }); + }); + }); + + return filters; +} + +async function get_dimension_filters() { + let dimensions = await frappe.db.get_list('Accounting Dimension', { + fields: ['label', 'fieldname', 'document_type'], + }); + + return dimensions; } From e64c357216142559b8784055f152716aa3b6510d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 14 May 2019 08:50:45 +0530 Subject: [PATCH 03/96] fix: Commified function to get dimensions --- .../accounting_dimension.py | 21 +++++++++++++++-- .../budget_variance_report.js | 8 +++++++ .../budget_variance_report.py | 23 ++++++++++++------- .../report/general_ledger/general_ledger.js | 9 +------- erpnext/public/js/financial_statements.js | 8 +------ erpnext/public/js/utils.js | 8 +++++++ 6 files changed, 52 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 93a25e7c0a6..e56492c18d4 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe import scrub from frappe.utils import cstr @@ -41,7 +40,25 @@ class AccountingDimension(Document): } for doctype in doclist: - create_custom_field(doctype, df) + + if doctype == "Budget": + df.update({ + "depends_on": "eval:doc.budget_against == '{0}'".format(self.document_type) + }) + + create_custom_field(doctype, df) + + property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") + + if property_setter: + else: + frappe.get_doc({ + "doctype": "Property Setter", + "doc_type": "Budget", + "fieldname": "budget_against" + }) + else: + create_custom_field(doctype, df) def delete_accounting_dimension(self): doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index cd9f9d956b5..b2072f06f12 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -62,3 +62,11 @@ frappe.query_reports["Budget Variance Report"] = { }, ] } + +let dimension_filters = erpnext.get_dimension_filters(); + +dimension_filters.then((dimensions) => { + dimensions.forEach((dimension) => { + frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]); + }); +}); diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index fe8de367f2f..fb4f5d0da23 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -19,9 +19,12 @@ def execute(filters=None): else: cost_centers = get_cost_centers(filters) + print(cost_centers) + period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) cam_map = get_cost_center_account_month_map(filters) + print(cam_map) data = [] for cost_center in cost_centers: cost_center_items = cam_map.get(cost_center) @@ -45,8 +48,8 @@ def execute(filters=None): if(filters.get("show_cumulative")): last_total = period_data[0] - period_data[1] - - period_data[2] = period_data[0] - period_data[1] + + period_data[2] = period_data[0] - period_data[1] row += period_data totals[2] = totals[0] - totals[1] if filters["period"] != "Yearly" : @@ -56,7 +59,7 @@ def execute(filters=None): return columns, data def validate_filters(filters): - if filters.get("budget_against")=="Project" and filters.get("cost_center"): + if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"): frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) def get_columns(filters): @@ -92,8 +95,11 @@ def get_cost_centers(filters): if filters.get("budget_against") == "Cost Center": cond = "order by lft" - return frappe.db.sql_list("""select name from `tab{tab}` where company=%s - {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) + if filters.get("budget_against") in ["Cost Center", "Project"]: + return frappe.db.sql_list("""select name from `tab{tab}` where company=%s + {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) + else: + return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #Get cost center & target details def get_cost_center_target_details(filters): @@ -109,7 +115,7 @@ def get_cost_center_target_details(filters): """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + #Get target distribution details of accounts of cost center def get_target_distribution_details(filters): @@ -118,7 +124,7 @@ def get_target_distribution_details(filters): from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1): target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation)) - + return target_details #Get actual details from gl entry @@ -129,7 +135,7 @@ def get_actual_details(name, filters): if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt) - + ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year, MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b @@ -153,6 +159,7 @@ def get_actual_details(name, filters): def get_cost_center_account_month_map(filters): import datetime cost_center_target_details = get_cost_center_target_details(filters) + print(cost_center_target_details) tdd = get_target_distribution_details(filters) cam_map = {} diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 7db8786c35f..481107e498b 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -215,7 +215,7 @@ frappe.query_reports["General Ledger"] = { ] } -let dimension_filters = get_dimension_filters(); +let dimension_filters = erpnext.get_dimension_filters(); dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { @@ -228,10 +228,3 @@ dimension_filters.then((dimensions) => { }); }); -async function get_dimension_filters() { - let dimensions = await frappe.db.get_list('Accounting Dimension', { - fields: ['label', 'fieldname', 'document_type'], - }); - - return dimensions; -} diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 77e67c49edc..2e4b8f28db4 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -150,7 +150,7 @@ function get_filters(){ } ] - let dimension_filters = get_dimension_filters() + let dimension_filters = erpnext.get_dimension_filters() dimension_filters.then((dimensions) => { dimensions.forEach((dimension) => { @@ -166,10 +166,4 @@ function get_filters(){ return filters; } -async function get_dimension_filters() { - let dimensions = await frappe.db.get_list('Accounting Dimension', { - fields: ['label', 'fieldname', 'document_type'], - }); - return dimensions; -} diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6860d6a2384..540b5ea0ec4 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -62,6 +62,14 @@ $.extend(erpnext, { $btn.on("click", function() { me.show_serial_batch_selector(grid_row.frm, grid_row.doc); }); + }, + + get_dimension_filters: async function() { + let dimensions = await frappe.db.get_list('Accounting Dimension', { + fields: ['label', 'fieldname', 'document_type'], + }); + + return dimensions; } }); From 073f36b76689a9ff0436abc2b42575ca9b770177 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 15 May 2019 12:20:37 +0530 Subject: [PATCH 04/96] fix: Create property setter for budget if dimension is created --- .../accounting_dimension.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index e56492c18d4..ab66fe3b3d3 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -13,6 +13,8 @@ class AccountingDimension(Document): def before_insert(self): self.set_fieldname_and_label() + + def after_insert(self): self.make_accounting_dimension_in_accounting_doctypes() def on_trash(self): @@ -51,12 +53,26 @@ class AccountingDimension(Document): property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") if property_setter: + property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") + property_setter_doc.doc_type = 'Budget' + property_setter_doc.doctype_or_field = "DocField" + property_setter_doc.fiel_dname = "budget_against" + property_setter_doc.property = "options" + property_setter_doc.property_type = "Text" + property_setter_doc.value = property_setter_doc.value + "\n" + self.document_type + property_setter_doc.save() + + frappe.clear_cache(doctype='Budget') else: frappe.get_doc({ "doctype": "Property Setter", + "doctype_or_field": "DocField", "doc_type": "Budget", - "fieldname": "budget_against" - }) + "field_name": "budget_against", + "property": "options", + "property_type": "Text", + "value": "\nCost Center\nProject\n" + self.document_type + }).insert(ignore_permissions=True) else: create_custom_field(doctype, df) @@ -76,10 +92,15 @@ class AccountingDimension(Document): AND doc_type IN (%s)""" % ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) + # budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") + # value_list = budget_against_property.value.split('\n')[3:] + # value_list.remove(self.document_type) + + # budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) + for doc in doclist: frappe.clear_cache(doctype=doc) - def get_last_created_accounting_dimension(): last_created_accounting_dimension = frappe.db.sql("select fieldname, max(creation) from `tabAccounting Dimension`", as_dict=1) From 705c03c0934598f0c9bb515814408a6cdfe5f475 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 17 May 2019 10:23:00 +0530 Subject: [PATCH 05/96] fix: Added dashboard and module links for accounting dimensions --- .../accounting_dimension.js | 6 ++- .../accounting_dimension.json | 10 +++- .../accounting_dimension.py | 51 ++++++++++--------- .../accounting_dimension_dashboard.py | 12 +++++ erpnext/config/accounting.py | 5 ++ 5 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 0676731840e..9a6ea73d4a4 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -2,7 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Accounting Dimension', { - // refresh: function(frm) { - // } + document_type: function(frm){ + frm.set_value('label', frm.doc.document_type); + frm.set_value('fieldname', frappe.scrub(frm.doc.document_type)) + } }); diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index 13902d6335f..0e9ab9e5929 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -6,7 +6,8 @@ "field_order": [ "document_type", "label", - "fieldname" + "fieldname", + "is_mandatory" ], "fields": [ { @@ -27,9 +28,14 @@ "label": "Document Type", "options": "DocType", "reqd": 1 + }, + { + "fieldname": "is_mandatory", + "fieldtype": "Check", + "label": "Is Mandatory" } ], - "modified": "2019-05-09 15:30:55.339917", + "modified": "2019-05-16 13:37:04.240148", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index ab66fe3b3d3..147b8085a13 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -13,8 +13,6 @@ class AccountingDimension(Document): def before_insert(self): self.set_fieldname_and_label() - - def after_insert(self): self.make_accounting_dimension_in_accounting_doctypes() def on_trash(self): @@ -28,21 +26,27 @@ class AccountingDimension(Document): self.fieldname = scrub(self.label) def make_accounting_dimension_in_accounting_doctypes(self): - last_created_accounting_dimension = get_last_created_accounting_dimension() - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", - "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] + doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Asset", + "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Sales Order Item", + "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", + "Purchase Order Item"] - df = { - "fieldname": self.fieldname, - "label": self.label, - "fieldtype": "Link", - "options": self.document_type, - "insert_after": last_created_accounting_dimension if last_created_accounting_dimension else "project" - } + if self.is_mandatory: + df.update({ + "reqd": 1 + }) for doctype in doclist: + df = { + "fieldname": self.fieldname, + "label": self.label, + "fieldtype": "Link", + "options": self.document_type, + "insert_after": "cost_center" + } + if doctype == "Budget": df.update({ "depends_on": "eval:doc.budget_against == '{0}'".format(self.document_type) @@ -73,12 +77,16 @@ class AccountingDimension(Document): "property_type": "Text", "value": "\nCost Center\nProject\n" + self.document_type }).insert(ignore_permissions=True) + frappe.clear_cache(doctype=doctype) else: create_custom_field(doctype, df) + frappe.clear_cache(doctype=doctype) def delete_accounting_dimension(self): - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", - "Stock Entry", "Budget", "Payroll Entry", "Delivery Note"] + doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Asset", + "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Sales Order Item", + "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", + "Purchase Order Item"] frappe.db.sql(""" DELETE FROM `tabCustom Field` @@ -92,21 +100,16 @@ class AccountingDimension(Document): AND doc_type IN (%s)""" % ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) - # budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") - # value_list = budget_against_property.value.split('\n')[3:] - # value_list.remove(self.document_type) + budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") + value_list = budget_against_property.value.split('\n')[3:] + value_list.remove(self.document_type) - # budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) + budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) + budget_against_property.save() for doc in doclist: frappe.clear_cache(doctype=doc) -def get_last_created_accounting_dimension(): - last_created_accounting_dimension = frappe.db.sql("select fieldname, max(creation) from `tabAccounting Dimension`", as_dict=1) - - if last_created_accounting_dimension[0]: - return last_created_accounting_dimension[0].fieldname - def get_accounting_dimensions(): accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["fieldname"]) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py new file mode 100644 index 00000000000..62a12914973 --- /dev/null +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals + +from frappe import _ + +def get_data(): + return { + 'transactions': [ + { + 'label': _('Accounting Doctypes') + } + ] + } \ No newline at end of file diff --git a/erpnext/config/accounting.py b/erpnext/config/accounting.py index 6664c4d4afd..0ab1f52e297 100644 --- a/erpnext/config/accounting.py +++ b/erpnext/config/accounting.py @@ -174,6 +174,11 @@ def get_data(): "name": "Cheque Print Template", "description": _("Setup cheque dimensions for printing") }, + { + "type": "doctype", + "name": "Accounting Dimension", + "description": _("Setup custom dimensions for accounting") + }, ] }, { From d0a1ed901734b265beba066ff0024d5cf54440c2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 18 May 2019 23:47:42 +0530 Subject: [PATCH 06/96] fix: Ability to disable dimensions --- .../accounting_dimension.js | 8 +- .../accounting_dimension.json | 12 +- .../accounting_dimension.py | 165 ++++++++++-------- .../accounting_dimension_dashboard.py | 12 -- 4 files changed, 111 insertions(+), 86 deletions(-) delete mode 100644 erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 9a6ea73d4a4..f928b0a2395 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -6,5 +6,11 @@ frappe.ui.form.on('Accounting Dimension', { document_type: function(frm){ frm.set_value('label', frm.doc.document_type); frm.set_value('fieldname', frappe.scrub(frm.doc.document_type)) - } + + frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { + if (r.document_type) { + frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); + } + }); + }, }); diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json index 0e9ab9e5929..daa100422b4 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json @@ -7,7 +7,8 @@ "document_type", "label", "fieldname", - "is_mandatory" + "is_mandatory", + "disable" ], "fields": [ { @@ -30,12 +31,19 @@ "reqd": 1 }, { + "default": "0", "fieldname": "is_mandatory", "fieldtype": "Check", "label": "Is Mandatory" + }, + { + "default": "0", + "fieldname": "disable", + "fieldtype": "Check", + "label": "Disable" } ], - "modified": "2019-05-16 13:37:04.240148", + "modified": "2019-05-17 20:35:31.014495", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension", diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 147b8085a13..51388ec87ef 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -4,19 +4,23 @@ from __future__ import unicode_literals import frappe +import json from frappe.model.document import Document from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe import scrub from frappe.utils import cstr +from frappe.utils.background_jobs import enqueue class AccountingDimension(Document): + def on_update(self): + frappe.enqueue(disable_dimension, doc=self) def before_insert(self): self.set_fieldname_and_label() - self.make_accounting_dimension_in_accounting_doctypes() + frappe.enqueue(make_accounting_dimension_in_accounting_doctypes, doc=self) def on_trash(self): - self.delete_accounting_dimension() + frappe.enqueue(delete_accounting_dimension, doc=self) def set_fieldname_and_label(self): if not self.label: @@ -25,90 +29,109 @@ class AccountingDimension(Document): if not self.fieldname: self.fieldname = scrub(self.label) - def make_accounting_dimension_in_accounting_doctypes(self): +def make_accounting_dimension_in_accounting_doctypes(doc): + doclist = get_doclist() - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Asset", - "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Sales Order Item", - "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", - "Purchase Order Item"] + if doc.is_mandatory: + df.update({ + "reqd": 1 + }) - if self.is_mandatory: + for doctype in doclist: + + df = { + "fieldname": doc.fieldname, + "label": doc.label, + "fieldtype": "Link", + "options": doc.document_type, + "insert_after": "cost_center" + } + + if doctype == "Budget": df.update({ - "reqd": 1 + "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type) }) - for doctype in doclist: + create_custom_field(doctype, df) - df = { - "fieldname": self.fieldname, - "label": self.label, - "fieldtype": "Link", - "options": self.document_type, - "insert_after": "cost_center" - } + property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") - if doctype == "Budget": - df.update({ - "depends_on": "eval:doc.budget_against == '{0}'".format(self.document_type) - }) + if property_setter: + property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") + property_setter_doc.doc_type = 'Budget' + property_setter_doc.doctype_or_field = "DocField" + property_setter_doc.fiel_dname = "budget_against" + property_setter_doc.property = "options" + property_setter_doc.property_type = "Text" + property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type + property_setter_doc.save() - create_custom_field(doctype, df) - - property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") - - if property_setter: - property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") - property_setter_doc.doc_type = 'Budget' - property_setter_doc.doctype_or_field = "DocField" - property_setter_doc.fiel_dname = "budget_against" - property_setter_doc.property = "options" - property_setter_doc.property_type = "Text" - property_setter_doc.value = property_setter_doc.value + "\n" + self.document_type - property_setter_doc.save() - - frappe.clear_cache(doctype='Budget') - else: - frappe.get_doc({ - "doctype": "Property Setter", - "doctype_or_field": "DocField", - "doc_type": "Budget", - "field_name": "budget_against", - "property": "options", - "property_type": "Text", - "value": "\nCost Center\nProject\n" + self.document_type - }).insert(ignore_permissions=True) - frappe.clear_cache(doctype=doctype) + frappe.clear_cache(doctype='Budget') else: - create_custom_field(doctype, df) - frappe.clear_cache(doctype=doctype) + frappe.get_doc({ + "doctype": "Property Setter", + "doctype_or_field": "DocField", + "doc_type": "Budget", + "field_name": "budget_against", + "property": "options", + "property_type": "Text", + "value": "\nCost Center\nProject\n" + doc.document_type + }).insert(ignore_permissions=True) + frappe.clear_cache(doctype=doctype) + else: + create_custom_field(doctype, df) + frappe.clear_cache(doctype=doctype) - def delete_accounting_dimension(self): - doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "BOM", "Sales Order", "Purchase Order", "Asset", - "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Sales Order Item", - "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", - "Purchase Order Item"] +def delete_accounting_dimension(doc): + doclist = get_doclist() - frappe.db.sql(""" - DELETE FROM `tabCustom Field` - WHERE fieldname = %s - AND dt IN (%s)""" % - ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) + frappe.db.sql(""" + DELETE FROM `tabCustom Field` + WHERE fieldname = %s + AND dt IN (%s)""" % + ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist)) - frappe.db.sql(""" - DELETE FROM `tabProperty Setter` - WHERE field_name = %s - AND doc_type IN (%s)""" % - ('%s', ', '.join(['%s']* len(doclist))), tuple([self.fieldname] + doclist)) + frappe.db.sql(""" + DELETE FROM `tabProperty Setter` + WHERE field_name = %s + AND doc_type IN (%s)""" % + ('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist)) - budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") - value_list = budget_against_property.value.split('\n')[3:] - value_list.remove(self.document_type) + budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") + value_list = budget_against_property.value.split('\n')[3:] + value_list.remove(doc.document_type) - budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) - budget_against_property.save() + budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) + budget_against_property.save() + + for doctype in doclist: + frappe.clear_cache(doctype=doctype) + +def disable_dimension(doc): + if doc.disable: + df = {"read_only": 1} + else: + df = {"read_only": 0} + + doclist = get_doclist() + + for doctype in doclist: + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.fieldname}) + if field: + custom_field = frappe.get_doc("Custom Field", field) + custom_field.update(df) + custom_field.save() + + frappe.clear_cache(doctype=doctype) + +def get_doclist(): + doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", + "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", + "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", + "Stock Entry Detail", "Payment Entry Deduction"] + + return doclist - for doc in doclist: - frappe.clear_cache(doctype=doc) def get_accounting_dimensions(): accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["fieldname"]) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py deleted file mode 100644 index 62a12914973..00000000000 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension_dashboard.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - -def get_data(): - return { - 'transactions': [ - { - 'label': _('Accounting Doctypes') - } - ] - } \ No newline at end of file From 3d11ac0e75d0240c57edd101092ddb685529acf4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 19 May 2019 00:02:01 +0530 Subject: [PATCH 07/96] fix: Accounting dimensions for child tables --- .../doctype/journal_entry/journal_entry.py | 2 +- .../doctype/payment_entry/payment_entry.py | 2 +- .../purchase_invoice/purchase_invoice.py | 20 ++++++------- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/accounts/general_ledger.py | 30 ++++++++++++------- erpnext/controllers/accounts_controller.py | 4 ++- erpnext/controllers/stock_controller.py | 4 +-- .../purchase_receipt/purchase_receipt.py | 14 ++++----- .../stock/doctype/stock_entry/stock_entry.py | 4 +-- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 63f180dccea..26fbc23b4cd 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -509,7 +509,7 @@ class JournalEntry(AccountsController): "cost_center": d.cost_center, "project": d.project, "finance_book": self.finance_book - }) + }, item=d) ) if gl_map: diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9033f659e7c..0161b2e703e 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -507,7 +507,7 @@ class PaymentEntry(AccountsController): "debit_in_account_currency": d.amount, "debit": d.amount, "cost_center": d.cost_center - }) + }, item=d) ) def update_advance_paid(self): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 95d49a4421a..4acf1692d31 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -454,7 +454,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "cost_center": item.cost_center, "project": item.project - }, account_currency) + }, account_currency, item=item) ) # Amount added through landed-cost-voucher @@ -466,7 +466,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.landed_cost_voucher_amount), "project": item.project - })) + }), item=item) # sub-contracting warehouse if flt(item.rm_supp_cost): @@ -480,7 +480,7 @@ class PurchaseInvoice(BuyingController): "cost_center": item.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.rm_supp_cost) - }, warehouse_account[self.supplier_warehouse]["account_currency"])) + }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()): gl_entries.append( self.get_gl_dict({ @@ -492,7 +492,7 @@ class PurchaseInvoice(BuyingController): else flt(item.net_amount, item.precision("net_amount"))), "cost_center": item.cost_center, "project": item.project - }, account_currency) + }, account_currency, item=item) ) if self.auto_accounting_for_stock and self.is_opening == "No" and \ @@ -511,7 +511,7 @@ class PurchaseInvoice(BuyingController): "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or "Accounting Entry for Stock", "cost_center": self.cost_center - }) + }, item=item) ) self.negative_expense_to_be_booked += flt(item.item_tax_amount, \ @@ -540,7 +540,7 @@ class PurchaseInvoice(BuyingController): "debit_in_account_currency": (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount), "cost_center": item.cost_center - })) + }, item=item)) if item.item_tax_amount: asset_eiiav_currency = get_account_currency(eiiav_account) @@ -553,7 +553,7 @@ class PurchaseInvoice(BuyingController): "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) - })) + }, item=item)) else: cwip_account = get_asset_account("capital_work_in_progress_account", item.asset, company = self.company) @@ -567,7 +567,7 @@ class PurchaseInvoice(BuyingController): "debit_in_account_currency": (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount), "cost_center": self.cost_center - })) + }, item=item)) if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): asset_eiiav_currency = get_account_currency(eiiav_account) @@ -580,7 +580,7 @@ class PurchaseInvoice(BuyingController): "credit_in_account_currency": (item.item_tax_amount if asset_eiiav_currency == self.company_currency else item.item_tax_amount / self.conversion_rate) - })) + }, item=item)) return gl_entries @@ -607,7 +607,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Stock Adjustment"), "cost_center": item.cost_center, "project": item.project - }, account_currency) + }, account_currency, item=item) ) warehouse_debit_amount = stock_amount diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 31a9c66f6f2..aec965c9f21 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -784,7 +784,7 @@ class SalesInvoice(SellingController): if account_currency==self.company_currency else flt(item.net_amount, item.precision("net_amount"))), "cost_center": item.cost_center - }, account_currency) + }, account_currency, item=item) ) # expense account gl entries diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a661c03b35b..e15dd2a7a63 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -7,6 +7,7 @@ from frappe.utils import flt, cstr, cint from frappe import _ from frappe.model.meta import get_field_precision from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions class StockAccountInvalidTransaction(frappe.ValidationError): pass @@ -49,10 +50,11 @@ def process_gl_map(gl_map, merge_entries=True): def merge_similar_entries(gl_map): merged_gl_map = [] + accounting_dimensions = get_accounting_dimensions() for entry in gl_map: # if there is already an entry in this account then just add it # to that entry - same_head = check_if_in_list(entry, merged_gl_map) + same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions) if same_head: same_head.debit = flt(same_head.debit) + flt(entry.debit) same_head.debit_in_account_currency = \ @@ -69,16 +71,24 @@ def merge_similar_entries(gl_map): return merged_gl_map -def check_if_in_list(gle, gl_map): +def check_if_in_list(gle, gl_map, dimensions=None): + account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', + 'cost_center', 'project'] + + if dimensions: + account_head_fieldnames = account_head_fieldnames + dimensions + for e in gl_map: - if e.account == gle.account \ - and cstr(e.get('party_type'))==cstr(gle.get('party_type')) \ - and cstr(e.get('party'))==cstr(gle.get('party')) \ - and cstr(e.get('against_voucher'))==cstr(gle.get('against_voucher')) \ - and cstr(e.get('against_voucher_type')) == cstr(gle.get('against_voucher_type')) \ - and cstr(e.get('cost_center')) == cstr(gle.get('cost_center')) \ - and cstr(e.get('project')) == cstr(gle.get('project')): - return e + same_head = True + if e.account != gle.account: + same_head = False + + for fieldname in account_head_fieldnames: + if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): + same_head = False + + if same_head: + return e def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): if not from_repost: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index fda1a406473..11c70b3dc78 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -339,7 +339,7 @@ class AccountsController(TransactionBase): frappe.throw(_("Row #{0}: Account {1} does not belong to company {2}") .format(d.idx, d.account_head, self.company)) - def get_gl_dict(self, args, account_currency=None): + def get_gl_dict(self, args, account_currency=None, item=None): """this method populates the common properties of a gl entry record""" posting_date = args.get('posting_date') or self.get('posting_date') @@ -372,6 +372,8 @@ class AccountsController(TransactionBase): for dimension in accounting_dimensions: dimension_dict[dimension] = self.get(dimension) + if item and item.get(dimension): + dimension_dict[dimension] = item.get(dimension) gl_dict.update(dimension_dict) gl_dict.update(args) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d11c644817e..9e791bbd6ea 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -80,7 +80,7 @@ class StockController(AccountsController): "cost_center": item_row.cost_center, "remarks": self.get("remarks") or "Accounting Entry for Stock", "debit": flt(sle.stock_value_difference, 2), - }, warehouse_account[sle.warehouse]["account_currency"])) + }, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) # to target warehouse / expense account gl_list.append(self.get_gl_dict({ @@ -90,7 +90,7 @@ class StockController(AccountsController): "remarks": self.get("remarks") or "Accounting Entry for Stock", "credit": flt(sle.stock_value_difference, 2), "project": item_row.get("project") or self.get("project") - })) + }, item=item_row)) elif sle.warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(sle.warehouse) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 1bd55f812f1..a3436412155 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -206,7 +206,7 @@ class PurchaseReceipt(BuyingController): "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": stock_value_diff - }, warehouse_account[d.warehouse]["account_currency"])) + }, warehouse_account[d.warehouse]["account_currency"], item=d)) # stock received but not billed stock_rbnb_currency = get_account_currency(stock_rbnb) @@ -218,7 +218,7 @@ class PurchaseReceipt(BuyingController): "credit": flt(d.base_net_amount, d.precision("base_net_amount")), "credit_in_account_currency": flt(d.base_net_amount, d.precision("base_net_amount")) \ if stock_rbnb_currency==self.company_currency else flt(d.net_amount, d.precision("net_amount")) - }, stock_rbnb_currency)) + }, stock_rbnb_currency, item=d)) negative_expense_to_be_booked += flt(d.item_tax_amount) @@ -231,7 +231,7 @@ class PurchaseReceipt(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(d.landed_cost_voucher_amount), "project": d.project - })) + }, item=d)) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): @@ -241,7 +241,7 @@ class PurchaseReceipt(BuyingController): "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(d.rm_supp_cost) - }, warehouse_account[self.supplier_warehouse]["account_currency"])) + }, warehouse_account[self.supplier_warehouse]["account_currency"], item=d)) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -263,7 +263,7 @@ class PurchaseReceipt(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": divisional_loss, "project": d.project - }, stock_rbnb_currency)) + }, stock_rbnb_currency, item=d)) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: @@ -345,7 +345,7 @@ class PurchaseReceipt(BuyingController): "debit": base_asset_amount, "debit_in_account_currency": (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount) - })) + }, item=d)) # Asset received but not billed asset_rbnb_currency = get_account_currency(arbnb_account) @@ -357,7 +357,7 @@ class PurchaseReceipt(BuyingController): "credit": base_asset_amount, "credit_in_account_currency": (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount) - })) + }, item=d)) return gl_entries diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index d106ed126f6..c881d837ff4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -611,7 +611,7 @@ class StockEntry(StockController): "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": additional_cost - })) + }, item=d)) gl_entries.append(self.get_gl_dict({ "account": d.expense_account, @@ -619,7 +619,7 @@ class StockEntry(StockController): "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": -1 * additional_cost # put it as negative credit instead of debit purposefully - })) + }, item=d)) return gl_entries From a285e4c73e21c6e158f3d744c9940dbe5c77ddee Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 19 May 2019 11:13:21 +0530 Subject: [PATCH 08/96] fix: Rename function in accounting dimension --- .../doctype/accounting_dimension/accounting_dimension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 51388ec87ef..92b845e6617 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -17,7 +17,7 @@ class AccountingDimension(Document): def before_insert(self): self.set_fieldname_and_label() - frappe.enqueue(make_accounting_dimension_in_accounting_doctypes, doc=self) + frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self) def on_trash(self): frappe.enqueue(delete_accounting_dimension, doc=self) @@ -29,7 +29,7 @@ class AccountingDimension(Document): if not self.fieldname: self.fieldname = scrub(self.label) -def make_accounting_dimension_in_accounting_doctypes(doc): +def make_dimension_in_accounting_doctypes(doc): doclist = get_doclist() if doc.is_mandatory: From 4541605e27c9fef6cc23b245de50867ff22ea6aa Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 19 May 2019 11:13:42 +0530 Subject: [PATCH 09/96] fix: Test Case for accounting dimension --- .../test_accounting_dimension.py | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py index b4368c4e61e..68ac7075c0e 100644 --- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py @@ -3,8 +3,52 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry class TestAccountingDimension(unittest.TestCase): - pass + def setUp(self): + frappe.set_user("Administrator") + + if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}): + dimension = frappe.get_doc({ + "doctype": "Accounting Dimension", + "document_type": "Department", + }).insert() + + def test_dimension_against_sales_invoice(self): + si = create_sales_invoice(do_not_save=1) + si.append("items", { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "qty": 1, + "rate": 100, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + "department": "_Test Department - _TC" + }) + + si.save() + si.submit() + + gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"}) + + self.assertEqual(gle.department, "_Test Department - _TC") + + def test_dimension_against_journal_entry(self): + je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False) + je.accounts[0].update({"department": "_Test Department - _TC"}) + je.accounts[1].update({"department": "_Test Department - _TC"}) + + je.save() + je.submit() + + gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"}) + gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"}) + self.assertEqual(gle.department, "_Test Department - _TC") + self.assertEqual(gle1.department, "_Test Department - _TC") + + From 823c79588b30ed11c51f2d728089b85c48659972 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sun, 19 May 2019 16:01:45 +0530 Subject: [PATCH 10/96] feat: Moving to Jinja --- erpnext/public/js/education/lms/call.js | 2 +- erpnext/www/lms/__init__.py | 0 erpnext/www/lms/all-programs.html | 76 +++++++++++++++ erpnext/www/lms/all_programs.py | 15 +++ erpnext/www/lms/index.html | 118 ++++++++++++++++++++++++ erpnext/www/lms/index.py | 24 +++++ erpnext/www/lms/program.html | 74 +++++++++++++++ erpnext/www/lms/program.py | 15 +++ erpnext/www/{lms.py => lms_legacy.py} | 0 erpnext/www/{lms.html => lms_old.html} | 0 10 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 erpnext/www/lms/__init__.py create mode 100644 erpnext/www/lms/all-programs.html create mode 100644 erpnext/www/lms/all_programs.py create mode 100644 erpnext/www/lms/index.html create mode 100644 erpnext/www/lms/index.py create mode 100644 erpnext/www/lms/program.html create mode 100644 erpnext/www/lms/program.py rename erpnext/www/{lms.py => lms_legacy.py} (100%) rename erpnext/www/{lms.html => lms_old.html} (100%) diff --git a/erpnext/public/js/education/lms/call.js b/erpnext/public/js/education/lms/call.js index e35acbdd752..4edcaaa6d6a 100644 --- a/erpnext/public/js/education/lms/call.js +++ b/erpnext/public/js/education/lms/call.js @@ -2,7 +2,7 @@ frappe.ready(() => { frappe.provide('lms'); lms.call = (method, args) => { - const method_path = 'erpnext.www.lms.' + method; + const method_path = 'erpnext.www.lms_legacy.' + method; return new Promise((resolve, reject) => { return frappe.call({ method: method_path, diff --git a/erpnext/www/lms/__init__.py b/erpnext/www/lms/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html new file mode 100644 index 00000000000..1003e5e0876 --- /dev/null +++ b/erpnext/www/lms/all-programs.html @@ -0,0 +1,76 @@ +{% extends "templates/base.html" %} +{% block title %}All Programs{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(program, is_enrolled) %} + +{% endmacro %} + +{% block content %} +
+
+

All Programs

+

+ Start Learning +

+
+
+
+ {% for program in all_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+

+ View All Programs +

+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/all_programs.py b/erpnext/www/lms/all_programs.py new file mode 100644 index 00000000000..aa10e2b039a --- /dev/null +++ b/erpnext/www/lms/all_programs.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.all_programs = get_all_programs() + +def get_all_programs(): + program_names = frappe.get_all("Program", filters={"is_published": True}) + if program_names: + program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] + return program_list diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html new file mode 100644 index 00000000000..f4756993cf1 --- /dev/null +++ b/erpnext/www/lms/index.html @@ -0,0 +1,118 @@ +{% extends "templates/base.html" %} +{% block title %}{{ education_settings.portal_title }}{% endblock %} + +{% block head_include %} + + + +{% endblock %} + + +{% macro card(program, is_enrolled) %} + +{% endmacro %} + +{% block content %} +
+
+

{{ education_settings.portal_title }}

+

{{ education_settings.description }}

+

+ Start Learning +

+ Go to erpnext.com +
+
+
+ {% for program in featured_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+

+ View All Programs +

+
+
+
+
+
+
+ +
+
Curated Courses
+
Start with a 14 day trial to get instant access to your own ERPNext Instance. Get expert support and world class hosting too.
+
+
+ +
+ +
+
Built by Experts
+
For self hosted users, ERPNext Support provides you the priority support and bug fix guarantee, so you can stop worrying.
+
+
+ +
+ +
+
Learn from the OEMs
+
ERPNext is open source and infinitely extensible. Customize it, build upon it, add your own apps built with Frappe Framework.
+
+
+
+
+
+
+
+

About ERPNext

+

ERPNext is the world's best 100% open source ERP used by over 5000 companies worldwide.

+ +
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py new file mode 100644 index 00000000000..99dcb651aa1 --- /dev/null +++ b/erpnext/www/lms/index.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.featured_programs = get_featured_programs() + + +def get_featured_programs(): + featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True}) + if featured_program_names: + featured_list = [utils.get_program_and_enrollment_status(program['name']) for program in featured_program_names] + return featured_list + else: + return get_all_programs()[:2] + +def get_all_programs(): + program_names = frappe.get_all("Program", filters={"is_published": True}) + if program_names: + program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] + return program_list diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html new file mode 100644 index 00000000000..60104e72d1f --- /dev/null +++ b/erpnext/www/lms/program.html @@ -0,0 +1,74 @@ +{% extends "templates/base.html" %} +{% block title %}{{ program.program_name }}{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(program, is_enrolled) %} + +{% endmacro %} + +{% block content %} +
+
+

{{ program.program_name }}

+

{{ program.description }}

+

+ Sign Up +

+
+
+
+ {% for program in all_programs %} + {{ card(program.program, program.is_enrolled) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py new file mode 100644 index 00000000000..f38e65246ba --- /dev/null +++ b/erpnext/www/lms/program.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + context.program = get_program(frappe.form_dict['name']) + +def get_program(program_name): + try: + return frappe.get_doc('Program', program_name) + except frappe.DoesNotExistError: + frappe.throw(_("Program {0} does not exist.".format(program_name))) \ No newline at end of file diff --git a/erpnext/www/lms.py b/erpnext/www/lms_legacy.py similarity index 100% rename from erpnext/www/lms.py rename to erpnext/www/lms_legacy.py diff --git a/erpnext/www/lms.html b/erpnext/www/lms_old.html similarity index 100% rename from erpnext/www/lms.html rename to erpnext/www/lms_old.html From d7e8298a5f58e200a393a541d1210ff474f00632 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 20 May 2019 11:55:17 +0530 Subject: [PATCH 11/96] feat: added program and course pages --- erpnext/www/lms/course.html | 77 ++++++++++++++++++++++++++++++++++++ erpnext/www/lms/course.py | 11 ++++++ erpnext/www/lms/program.html | 25 ++++++------ 3 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 erpnext/www/lms/course.html create mode 100644 erpnext/www/lms/course.py diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html new file mode 100644 index 00000000000..2e414a03695 --- /dev/null +++ b/erpnext/www/lms/course.html @@ -0,0 +1,77 @@ +{% extends "templates/base.html" %} +{% block title %}{{ course.course_name }}{% endblock %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(topic, index, length) %} + +{% endmacro %} + +{% block content %} +
+
+

{{ course.course_name }}

+

{{ course.course_intro }}

+

+ Sign Up +

+
+
+
+ {% for topic in topics %} + {{ card(topic.as_dict(), loop.index, topics|length) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py new file mode 100644 index 00000000000..d8670e69776 --- /dev/null +++ b/erpnext/www/lms/course.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + course = frappe.get_doc('Course', frappe.form_dict['name']) + context.course = course + context.topics = course.get_topics() \ No newline at end of file diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 60104e72d1f..35f7f8a69c0 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -31,23 +31,20 @@ {% endblock %} -{% macro card(program, is_enrolled) %} -
-
- - {% if program.hero_image %} -
+{% macro card(course, index, length) %} +
- {% for program in all_programs %} - {{ card(program.program, program.is_enrolled) }} + {% for course in program.courses %} + {{ card(frappe.get_doc("Course", course.course), loop.index, program.courses|length) }} {% endfor %}
From ff717a7c16ee3fad1dbadef08a8e8c7e1700e485 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 20 May 2019 18:35:38 +0530 Subject: [PATCH 12/96] fix: List view button for dimensions --- .../doctype/accounting_dimension/accounting_dimension.js | 8 +++++++- .../doctype/accounting_dimension/accounting_dimension.py | 6 ------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index f928b0a2395..34d17ec92a9 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -3,7 +3,13 @@ frappe.ui.form.on('Accounting Dimension', { - document_type: function(frm){ + refresh: function(frm) { + frm.add_custom_button(__('Show {0} List', [frm.doc.document_type]), function () { + frappe.set_route("List", frm.doc.document_type); + }); + }, + + document_type: function(frm) { frm.set_value('label', frm.doc.document_type); frm.set_value('fieldname', frappe.scrub(frm.doc.document_type)) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 92b845e6617..565d2439dad 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -58,11 +58,6 @@ def make_dimension_in_accounting_doctypes(doc): if property_setter: property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") - property_setter_doc.doc_type = 'Budget' - property_setter_doc.doctype_or_field = "DocField" - property_setter_doc.fiel_dname = "budget_against" - property_setter_doc.property = "options" - property_setter_doc.property_type = "Text" property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type property_setter_doc.save() @@ -132,7 +127,6 @@ def get_doclist(): return doclist - def get_accounting_dimensions(): accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["fieldname"]) From c8c790a097e7b65176dcf0682c49998318886e8e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 21 May 2019 12:04:50 +0530 Subject: [PATCH 13/96] feat: Added content page and minor doctype changes --- erpnext/education/doctype/course/course.json | 601 ++++--------------- erpnext/education/doctype/video/video.json | 214 +------ erpnext/www/lms/content.html | 136 +++++ erpnext/www/lms/content.py | 39 ++ 4 files changed, 313 insertions(+), 677 deletions(-) create mode 100644 erpnext/www/lms/content.html create mode 100644 erpnext/www/lms/content.py diff --git a/erpnext/education/doctype/course/course.json b/erpnext/education/doctype/course/course.json index 072e8b4afba..234b29d4218 100644 --- a/erpnext/education/doctype/course/course.json +++ b/erpnext/education/doctype/course/course.json @@ -1,514 +1,135 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:course_code", - "beta": 0, - "creation": "2015-09-07 12:39:55.181893", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:course_code", + "creation": "2015-09-07 12:39:55.181893", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "course_name", + "department", + "parent_course", + "column_break_3", + "course_code", + "course_abbreviation", + "section_break_6", + "topics", + "course_intro", + "hero_image", + "assessment", + "default_grading_scale", + "assessment_criteria" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_name", - "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": "Course Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "parent_course", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Parent Course (Leave blank, if this isn't part of Parent Course)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "parent_course", + "fieldtype": "Data", + "label": "Parent Course (Leave blank, if this isn't part of Parent Course)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Course Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "course_code", + "fieldtype": "Data", + "label": "Course Code", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_abbreviation", - "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": "Course Abbreviation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_abbreviation", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Abbreviation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "topics", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Topics", - "length": 0, - "no_copy": 0, - "options": "Course Topic", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "topics", + "fieldtype": "Table", + "label": "Topics", + "options": "Course Topic" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "course_intro", - "fieldtype": "Small Text", - "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": "Course Intro", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "course_intro", + "fieldtype": "Small Text", + "label": "Course Intro" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "hero_image", - "fieldtype": "Attach Image", - "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": "Hero Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "label": "Hero Image" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "assessment", + "fieldtype": "Section Break", + "label": "Assessment" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_grading_scale", - "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": "Default Grading Scale", - "length": 0, - "no_copy": 0, - "options": "Grading Scale", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_grading_scale", + "fieldtype": "Link", + "label": "Default Grading Scale", + "options": "Grading Scale" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "assessment_criteria", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment Criteria", - "length": 0, - "no_copy": 0, - "options": "Course Assessment Criteria", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "assessment_criteria", + "fieldtype": "Table", + "label": "Assessment Criteria", + "options": "Course Assessment Criteria" } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-09 11:35:27.354877", - "modified_by": "Administrator", - "module": "Education", - "name": "Course", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-20 19:27:09.381482", + "modified_by": "Administrator", + "module": "Education", + "name": "Course", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "restrict_to_domain": "Education", - "search_fields": "course_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "restrict_to_domain": "Education", + "search_fields": "course_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/video/video.json b/erpnext/education/doctype/video/video.json index cc8f718ba4e..3d11bd256fa 100644 --- a/erpnext/education/doctype/video/video.json +++ b/erpnext/education/doctype/video/video.json @@ -1,262 +1,102 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:47:13.087395", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "description", + "duration", + "provider", + "url", + "publish_date" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "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, + "reqd": 1, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Text Editor", - "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, + "in_list_view": 1, "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "duration", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Duration", - "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 + "label": "Duration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "url", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "in_list_view": 1, "label": "URL", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "publish_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Publish 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 + "label": "Publish Date" + }, + { + "fieldname": "provider", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Provider", + "options": "YouTube\nVimeo", + "reqd": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:17.134288", + "modified": "2019-05-20 15:11:53.075093", "modified_by": "Administrator", "module": "Education", "name": "Video", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html new file mode 100644 index 00000000000..3e00c79f9c3 --- /dev/null +++ b/erpnext/www/lms/content.html @@ -0,0 +1,136 @@ +{% extends "templates/base.html" %} +{% block title %}{% endblock %} + +{% block head_include %} + + +{% endblock %} + +{% macro navigation() %} +
+
+

{{ content.name }}

+
+
+ {% if previous %} + Previous + {% else %} + Back to Course + {% endif %} + + {% if next %} + + {% else %} + + {% endif %} +
+
+{% endmacro %} + +{% macro video() %} +
+ {{ navigation() }} +
+ {% if content.duration %} + {{ content.duration }} Mins + {% endif %} + + {% if content.publish_date and content.duration%} + - + {% endif %} + + {% if content.publish_date %} + Published on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+
+ {{ content.description }} +
+ +{% block script %} + + +{% endblock %} +{% endmacro %} + +{% macro article() %} +
+ {{ navigation() }} +
+ {% if content.author or content.publish_date %} + Published + {% endif %} + {% if content.author %} + by {{ content.author }} + {% endif %} + {% if content.publish_date %} + on {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+ {{ content.content }} +
+{% endmacro %} + +{% block content %} +
+
+
+ {% if content_type=='Video' %} + {{ video() }} + {% elif content_type=='Article'%} + {{ article() }} + {% elif content_type=='Quiz' %} +

Quiz: {{ content.name }}

+ {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py new file mode 100644 index 00000000000..9d26a326d99 --- /dev/null +++ b/erpnext/www/lms/content.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.course = frappe.form_dict['course'] + context.topic = frappe.form_dict['topic'] + content = frappe.form_dict['content'] + context.content_type = frappe.form_dict['type'] + + context.content = frappe.get_doc(context.content_type, content).as_dict() + + context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) + + context.next = get_next_content(context.topic, context.course, context.content, context.content_type) + +def get_next_content(topic, course, content, content_type): + if frappe.session.user == "Guest": + return None + topic = frappe.get_doc("Topic", topic) + content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] + current_index = content_list.index({'content': content.name, 'content_type': content_type}) + try: + return content_list[current_index + 1] + except IndexError: + return None + +def get_previous_content(topic, course, content, content_type): + if frappe.session.user == "Guest": + return None + topic = frappe.get_doc("Topic", topic) + content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] + current_index = content_list.index({'content': content.name, 'content_type': content_type}) + if current_index == 0: + return None + else: + return content_list[current_index - 1] \ No newline at end of file From 87a7438d3821351a15ddee97a51d8a677e9f2d0a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 21 May 2019 12:05:19 +0530 Subject: [PATCH 14/96] chore: added macro for page hero --- erpnext/www/lms/course.html | 24 ++++++++++++++---------- erpnext/www/lms/macros/hero.html | 20 ++++++++++++++++++++ erpnext/www/lms/program.html | 13 ++++--------- 3 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 erpnext/www/lms/macros/hero.html diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 2e414a03695..68128dca1d9 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}{{ course.course_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} {% block head_include %} {% endblock %} - {% macro card(program, is_enrolled) %} -
-
- - {% if program.hero_image %} -
- {% else %} -
-
{{ program.program_name }}
-
- {% endif %} -
-
{{ program.program_name }}
-
{{ program.description }}
-
-
- + {% endmacro %} From 08425d46fa114c0cb1787c9d558faa923c2f736d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 15:42:57 +0530 Subject: [PATCH 32/96] refactor: set Student as default portal role for Education --- erpnext/domains/education.py | 2 +- erpnext/education/doctype/student/student.py | 2 +- erpnext/education/setup.py | 22 +++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/erpnext/domains/education.py b/erpnext/domains/education.py index 55e4eed801a..bbaa6e55d99 100644 --- a/erpnext/domains/education.py +++ b/erpnext/domains/education.py @@ -14,7 +14,7 @@ data = { 'Student Attendance Tool', 'Student Applicant' ], - 'default_portal_role': 'LMS User', + 'default_portal_role': 'Student', 'restricted_roles': [ 'Student', 'Instructor', diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 529f78d4f53..da25880c81b 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -54,7 +54,7 @@ class Student(Document): 'send_welcome_email': 1, 'user_type': 'Website User' }) - student_user.add_roles("Student", "LMS User") + student_user.add_roles("Student") student_user.save() update_password_link = student_user.reset_password() diff --git a/erpnext/education/setup.py b/erpnext/education/setup.py index ed1d69e80de..9d16ebeea85 100644 --- a/erpnext/education/setup.py +++ b/erpnext/education/setup.py @@ -9,7 +9,8 @@ from erpnext.setup.utils import insert_record def setup_education(): - if frappe.db.exists('Academic Year', '2015-16'): + disable_desk_access_for_student_role() + if frappe.db.exists("Academic Year", "2015-16"): # already setup return create_academic_sessions() @@ -26,3 +27,22 @@ def create_academic_sessions(): {"doctype": "Academic Term", "academic_year": "2017-18", "term_name": "Semester 2"} ] insert_record(data) + +def disable_desk_access_for_student_role(): + try: + student_role = frappe.get_doc("Role", "Student") + except frappe.DoesNotExistError: + create_student_role() + return + + student_role.desk_access = 0 + student_role.save() + +def create_student_role(): + student_role = frappe.get_doc({ + "doctype": "Role", + "role_name": "Student", + "desk_access": 0, + "restrict_to_domain": "Education" + }) + student.insert() From 5aa8df840e4a9f324a14077583ef9f0d10b718dd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 18:38:09 +0530 Subject: [PATCH 33/96] refactor: cards and hero markup --- erpnext/www/lms/all-programs.html | 39 ++++++++++++++----------------- erpnext/www/lms/index.html | 6 ++--- erpnext/www/lms/macros/hero.html | 15 ++++++------ 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index 1003e5e0876..f5b7595ec84 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -32,25 +32,25 @@ {% macro card(program, is_enrolled) %} - {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index 54eb7c48fb5..ffa46e123ca 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -32,8 +32,8 @@ {% endblock %} -{% macro card(program, is_enrolled) %} -
+{% macro card(program) %} +
{% if program.hero_image %} @@ -68,7 +68,7 @@
{% for program in featured_programs %} - {{ card(program.program, program.is_enrolled) }} + {{ card(program.program) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index f837b5c447c..8c09580b8ff 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,22 +1,21 @@ -{% macro hero(title, description) %} +{% macro hero(title, description, is_enrolled) %}

{% block script %} {% endblock %} {% endmacro %} \ No newline at end of file From f927502cc5f460fb716f4e861d908c307f3e3881 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 29 May 2019 18:39:52 +0530 Subject: [PATCH 34/96] refactor: added lms utilities for enrollment and program list --- erpnext/education/utils.py | 125 ++++++++++++++++++++++++-------- erpnext/www/lms/all_programs.py | 8 +- erpnext/www/lms/index.py | 13 +--- erpnext/www/lms/program.py | 1 + 4 files changed, 96 insertions(+), 51 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index bf766adc099..398a2a5ea72 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -57,9 +57,10 @@ def validate_duplicate_student(students): # LMS Utils def get_current_student(): - """ - Returns student user name, example EDU-STU-2018-00001 (Based on the naming series). - Takes email from from frappe.session.user + """Returns current student from frappe.session.user + + Returns: + object: Student Document """ email = frappe.session.user if email in ('Administrator', 'Guest'): @@ -70,41 +71,101 @@ def get_current_student(): except (IndexError, frappe.DoesNotExistError): return None -def check_super_access(): - current_user = frappe.get_doc('User', frappe.session.user) - roles = set([role.role for role in current_user.roles]) - return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) +def get_portal_programs(): + """Returns a list of all program to be displayed on the portal + Programs are returned based on the following logic + is_published and (student_is_enrolled or student_can_self_enroll) -def get_program_enrollment(program_name): + Returns: + list of dict: List of all programs and to be displayed on the portal along with enrollment status """ - Function to get program enrollments for a particular student for a program - """ - student = get_current_student() - if not student: + published_programs = frappe.get_all("Program", filters={"is_published": True}) + if not published_programs: return None + + program_list = [frappe.get_doc("Program", program) for program in published_programs] + portal_programs = [] + + for program in program_list: + enrollment_status = get_enrollment_status(program.name) + if enrollment_status or program.allow_self_enroll: + portal_programs.append({'program': program, 'is_enrolled': enrollment_status}) + + return portal_programs + +def get_enrollment_status(program, student=None): + """Returns enrollment status for current student + + Args: + program (string): Name of the program + student (object): instance of Student document + + Returns: + bool: Is current user enrolled or not + """ + if not student: + student = get_current_student() + if student and get_enrollment('program', program, student.name): + return True else: - enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) - if enrollment: - return enrollment[0].name - else: - return None + return False -def get_program_and_enrollment_status(program_name): - program = frappe.get_doc('Program', program_name) - is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() - return {'program': program, 'is_enrolled': is_enrolled} +def get_enrollment(master, document, student): + """Gets enrollment for course or program -def get_course_enrollment(course_name): - student = get_current_student() - if not student: - return None - enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) - try: - name = enrollment_name[0].name - enrollment = frappe.get_doc("Course Enrollment", name) - return enrollment - except: - return None + Args: + master (string): can either be program or course + document (string): program or course name + student (string): Student ID + + Returns: + string: Enrollment Name if exists else returns empty string + """ + if master == 'program': + enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document}) + if master == 'course': + enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document}) + + if enrollments: + return enrollment[0].name + else: + return '' + +# def check_super_access(): +# current_user = frappe.get_doc('User', frappe.session.user) +# roles = set([role.role for role in current_user.roles]) +# return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) + +# def get_program_enrollment(program_name): +# """ +# Function to get program enrollments for a particular student for a program +# """ +# student = get_current_student() +# if not student: +# return None +# else: +# enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) +# if enrollment: +# return enrollment[0].name +# else: +# return None + +# def get_program_and_enrollment_status(program_name): +# program = frappe.get_doc('Program', program_name) +# is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() +# return {'program': program, 'is_enrolled': is_enrolled} + +# def get_course_enrollment(course_name): +# student = get_current_student() +# if not student: +# return None +# enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) +# try: +# name = enrollment_name[0].name +# enrollment = frappe.get_doc("Course Enrollment", name) +# return enrollment +# except: +# return None def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/all_programs.py b/erpnext/www/lms/all_programs.py index aa10e2b039a..c0c18c35341 100644 --- a/erpnext/www/lms/all_programs.py +++ b/erpnext/www/lms/all_programs.py @@ -6,10 +6,4 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") - context.all_programs = get_all_programs() - -def get_all_programs(): - program_names = frappe.get_all("Program", filters={"is_published": True}) - if program_names: - program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] - return program_list + context.all_programs = utils.get_portal_programs() \ No newline at end of file diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py index 15ca0b46287..00f66e72c3e 100644 --- a/erpnext/www/lms/index.py +++ b/erpnext/www/lms/index.py @@ -13,15 +13,4 @@ def get_context(context): def get_featured_programs(): - featured_program_names = frappe.get_all("Program", filters={"is_published": True, "is_featured": True}) - if featured_program_names: - featured_list = [utils.get_program_and_enrollment_status(program['name']) for program in featured_program_names] - return featured_list - else: - return get_all_programs()[:2] - -def get_all_programs(): - program_names = frappe.get_all("Program", filters={"is_published": True}) - if program_names: - program_list = [utils.get_program_and_enrollment_status(program['name']) for program in program_names] - return program_list + return utils.get_portal_programs() \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index f38e65246ba..827b11a4ab7 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -7,6 +7,7 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") context.program = get_program(frappe.form_dict['name']) + context.is_enrolled = utils.get_enrollment_status(program) def get_program(program_name): try: From 70a2734aabdb71723644cb119d14db1c39ea6daa Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 30 May 2019 01:40:31 +0530 Subject: [PATCH 35/96] fix: config file for qms --- erpnext/config/quality_management.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/config/quality_management.py b/erpnext/config/quality_management.py index 1256b2d9572..35acdfab24f 100644 --- a/erpnext/config/quality_management.py +++ b/erpnext/config/quality_management.py @@ -59,14 +59,14 @@ def get_data(): "items": [ { "type": "doctype", - "name": "Customer Feedback", - "description":_("Customer Feedback"), + "name": "Quality Feedback", + "description":_("Quality Feedback"), "onboard": 1, }, { "type": "doctype", - "name": "Customer Feedback Template", - "description":_("Customer Feedback Template"), + "name": "Quality Feedback Template", + "description":_("Quality Feedback Template"), } ] }, From 4d587344929c053fcaf1971e52ee410308bdecdd Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 30 May 2019 15:50:46 +0530 Subject: [PATCH 36/96] fix: Rounding adjustment while additional discount amount is aplied on grand total --- erpnext/controllers/taxes_and_totals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index b75b8b825c0..d5f86a15862 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -397,7 +397,7 @@ class calculate_taxes_and_totals(object): net_total += item.net_amount # discount amount rounding loss adjustment if no taxes - if (not taxes or self.doc.apply_discount_on == "Net Total") \ + if (self.doc.apply_discount_on == "Net Total" or not taxes or total_for_discount_amount==self.doc.net_total) \ and i == len(self.doc.get("items")) - 1: discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")) From d060890817f52b404cdf2ad8855e216510e7402e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 30 May 2019 15:50:46 +0530 Subject: [PATCH 37/96] fix: Rounding adjustment while additional discount amount is aplied on grand total --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b3c853815fb..91800cd9a90 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -515,7 +515,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ net_total += item.net_amount; // discount amount rounding loss adjustment if no taxes - if ((!(me.frm.doc.taxes || []).length || (me.frm.doc.apply_discount_on == "Net Total")) + if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total")) && i == (me.frm.doc.items || []).length - 1) { var discount_amount_loss = flt(me.frm.doc.net_total - net_total - me.frm.doc.discount_amount, precision("net_total")); From f22793f91b31ad1e7c3e038a7001f5c57bc2c1cd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:34:53 +0530 Subject: [PATCH 38/96] refactor: added program card macro --- erpnext/www/lms/all-programs.html | 29 +++-------------------------- erpnext/www/lms/index.html | 28 +++------------------------- erpnext/www/lms/macros/card.html | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 51 deletions(-) create mode 100644 erpnext/www/lms/macros/card.html diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index f5b7595ec84..088498b4a98 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}All Programs{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} {% block head_include %} {% endblock %} - -{% macro card(program, is_enrolled) %} - -{% endmacro %} - {% block content %}
{% for program in all_programs %} - {{ card(program.program, program.is_enrolled) }} + {{ program_card(program) }} {% endfor %}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index ffa46e123ca..ba3034c79e0 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -1,5 +1,6 @@ {% extends "templates/base.html" %} {% block title %}{{ education_settings.portal_title }}{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} {% block head_include %} @@ -32,43 +33,20 @@ {% endblock %} -{% macro card(program) %} - -{% endmacro %} - {% block content %}

{{ education_settings.portal_title }}

{{ education_settings.description }}

- Start Learning + {{ 'Start Learning' if frappe.session.user == 'Guest' else 'Explore Programs'}}

Go to erpnext.com
{% for program in featured_programs %} - {{ card(program.program) }} + {{ program_card(program) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html new file mode 100644 index 00000000000..8cf8a78e565 --- /dev/null +++ b/erpnext/www/lms/macros/card.html @@ -0,0 +1,22 @@ +{% macro program_card(program) %} +

+{% endmacro %} \ No newline at end of file From dfdb92f4b1e086d76d02a0151d51e28358742224 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:35:15 +0530 Subject: [PATCH 39/96] refactor: added enrollment api --- erpnext/education/utils.py | 88 +++++++++++++++++++++++--------- erpnext/www/lms/macros/hero.html | 40 +++++++++++++-- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 398a2a5ea72..09ac22ca276 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -60,7 +60,7 @@ def get_current_student(): """Returns current student from frappe.session.user Returns: - object: Student Document + object: Student Document """ email = frappe.session.user if email in ('Administrator', 'Guest'): @@ -77,32 +77,29 @@ def get_portal_programs(): is_published and (student_is_enrolled or student_can_self_enroll) Returns: - list of dict: List of all programs and to be displayed on the portal along with enrollment status + list of objects: List of all programs and to be displayed on the portal along with enrollment status """ published_programs = frappe.get_all("Program", filters={"is_published": True}) if not published_programs: return None program_list = [frappe.get_doc("Program", program) for program in published_programs] - portal_programs = [] - - for program in program_list: - enrollment_status = get_enrollment_status(program.name) - if enrollment_status or program.allow_self_enroll: - portal_programs.append({'program': program, 'is_enrolled': enrollment_status}) + portal_programs = [program for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] return portal_programs -def get_enrollment_status(program, student=None): +def allowed_program_access(program, student=None): """Returns enrollment status for current student Args: - program (string): Name of the program - student (object): instance of Student document + program (string): Name of the program + student (object): instance of Student document Returns: - bool: Is current user enrolled or not + bool: Is current user enrolled or not """ + if has_super_access(): + return True if not student: student = get_current_student() if student and get_enrollment('program', program, student.name): @@ -114,33 +111,74 @@ def get_enrollment(master, document, student): """Gets enrollment for course or program Args: - master (string): can either be program or course - document (string): program or course name - student (string): Student ID + master (string): can either be program or course + document (string): program or course name + student (string): Student ID Returns: - string: Enrollment Name if exists else returns empty string + string: Enrollment Name if exists else returns empty string """ if master == 'program': - enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document}) + enrollments = frappe.get_all("Program Enrollment", filters={'student':student, 'program': document, 'docstatus': 1}) if master == 'course': enrollments = frappe.get_all("Course Enrollment", filters={'student':student, 'course': document}) if enrollments: - return enrollment[0].name + return enrollments[0].name else: - return '' + return None -# def check_super_access(): -# current_user = frappe.get_doc('User', frappe.session.user) -# roles = set([role.role for role in current_user.roles]) -# return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) +@frappe.whitelist() +def enroll_in_program(program_name, student=None): + """Enroll student in program + + Args: + program_name (string): Name of the program to be enrolled into + student (string, optional): name of student who has to be enrolled, if not + provided, a student will be created from the current user + + Returns: + string: name of the program enrollment document + """ + if has_super_access(): + return + + if not student == None: + student = frappe.get_doc("Student", student) + else: + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + student = get_current_student() + if not student: + student = create_student_from_current_user() + + # Check if student is already enrolled in program + enrollment = get_enrollment('program', program_name, student.name) + if enrollment: + return enrollment + + # Check if self enrollment in allowed + program = frappe.get_doc('Program', program_name) + if not program.allow_self_enroll: + return frappe.throw("You are not allowed to enroll for this course") + + # Enroll in program + program_enrollment = student.enroll_in_program(program_name) + return program_enrollment.name + +def has_super_access(): + current_user = frappe.get_doc('User', frappe.session.user) + roles = set([role.role for role in current_user.roles]) + return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) # def get_program_enrollment(program_name): # """ # Function to get program enrollments for a particular student for a program # """ -# student = get_current_student() +# student get_current_student() # if not student: # return None # else: @@ -169,6 +207,7 @@ def get_enrollment(master, document, student): def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) + student = frappe.get_doc({ "doctype": "Student", "first_name": user.first_name, @@ -176,6 +215,7 @@ def create_student_from_current_user(): "student_email_id": user.email, "user": frappe.session.user }) + student.save(ignore_permissions=True) return student diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 8c09580b8ff..89011b28919 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,20 +1,50 @@ -{% macro hero(title, description, is_enrolled) %} +{% macro hero(title, description, has_access) %}
-

{{ title}}

+

{{ title }}

{{ description }}

{% if frappe.session.user == 'Guest' %} Sign Up - {% elif not is_enrolled %} - + {% elif not has_access %} + {% endif %}

{% block script %} {% endblock %} From 24bd07d000d1e747a453bfd6ba5588fc6086922b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:36:58 +0530 Subject: [PATCH 40/96] refactor: style changes and template refactor --- erpnext/www/lms/content.html | 2 +- erpnext/www/lms/course.html | 24 ++++++++++++------------ erpnext/www/lms/program.html | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index a908d85da3b..68fb9f32fee 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -90,7 +90,7 @@ {% endif %}
-
+
{{ content.content }}
{% endmacro %} diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 30b594d9035..ee3b9758cb6 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -35,16 +35,16 @@ {% macro card(topic, index, length) %}
- {% if frappe.session.user == 'Guest' %} - - {% else %} + {% if has_access %} + {% else %} +
{% endif %}
@@ -81,7 +81,7 @@ {% block content %}
- {{ hero(course.course_name, course.course_intro) }} + {{ hero(course.course_name, course.course_intro, has_access) }}
{% for topic in topics %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 65c883fb4c6..d364e5e1d9a 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -40,7 +40,7 @@
{% else %}
-
{{ course.course_name }}
+
{% endif %}
@@ -54,7 +54,7 @@ {% block content %}
- {{ hero(program.program_name, program.description) }} + {{ hero(program.program_name, program.description, has_access) }}
{% for course in program.courses %} From 4991fca5ccc81f130b93bf6669e41ccd50313e4a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 16:37:15 +0530 Subject: [PATCH 41/96] feat: enabled per student access --- erpnext/www/lms/content.py | 36 +++++++++++++++++++++++++++++++----- erpnext/www/lms/course.py | 4 +++- erpnext/www/lms/program.py | 4 ++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 78b02e27786..67e3508df59 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -5,19 +5,27 @@ import frappe no_cache = 1 def get_context(context): - if frappe.session.user == "Guest": + program = frappe.form_dict['program'] + content = frappe.form_dict['content'] + content_type = frappe.form_dict['type'] + + has_program_access = utils.allowed_program_access(program) + has_content_access = allowed_content_access(program, content, content_type) + + if frappe.session.user == "Guest" or not has_program_access or not has_content_access: frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect + context.content = frappe.get_doc(content_type, content).as_dict() + context.content_type = content_type + context.course = frappe.form_dict['course'] context.topic = frappe.form_dict['topic'] - content = frappe.form_dict['content'] - context.content_type = frappe.form_dict['type'] - context.content = frappe.get_doc(context.content_type, content).as_dict() context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) context.next = get_next_content(context.topic, context.course, context.content, context.content_type) + def get_next_content(topic, course, content, content_type): if frappe.session.user == "Guest": return None @@ -38,4 +46,22 @@ def get_previous_content(topic, course, content, content_type): if current_index == 0: return None else: - return content_list[current_index - 1] \ No newline at end of file + return content_list[current_index - 1] + +def allowed_content_access(program, content, content_type): + # Get all content in program + + # Using ORM + # course_in_program = [course.course for course in frappe.get_all('Program Course', fields=['course'], filters={'parent': program})] + # topics_in_course = [topic.topic for topic in frappe.get_all("Course Topic", fields=['topic'], filters=[['parent','in', course_in_program]])] + # contents_of_program = [[c.content, c.content_type] for c in frappe.get_all('Topic Content', fields=['content', 'content_type'], filters=[['parent','in', topics_in_course]])] + + contents_of_program = frappe.db.sql("""select `tabtopic content`.content, `tabtopic content`.content_type + from `tabcourse topic`, + `tabprogram course`, + `tabtopic content` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabtopic content`.parent = `tabcourse topic`.topic + and `tabprogram course`.parent = '{0}'""".format(program)) + + return (content, content_type) in contents_of_program \ No newline at end of file diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index d8670e69776..b9aff5c5bd4 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -7,5 +7,7 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") course = frappe.get_doc('Course', frappe.form_dict['name']) + context.program = frappe.form_dict['program'] context.course = course - context.topics = course.get_topics() \ No newline at end of file + context.topics = course.get_topics() + context.has_access = utils.allowed_program_access(context.program) \ No newline at end of file diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 827b11a4ab7..4c3a3fdf66b 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -6,8 +6,8 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") - context.program = get_program(frappe.form_dict['name']) - context.is_enrolled = utils.get_enrollment_status(program) + context.program = get_program(frappe.form_dict['program']) + context.has_access = utils.allowed_program_access(frappe.form_dict['program']) def get_program(program_name): try: From 12579617f3079e925ad0d1dca73ea580adbfbea1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 17:19:11 +0530 Subject: [PATCH 42/96] refactor: added enrolled status to program card --- erpnext/education/utils.py | 2 +- erpnext/www/lms/all-programs.html | 2 +- erpnext/www/lms/content.py | 2 +- erpnext/www/lms/index.html | 2 +- erpnext/www/lms/macros/card.html | 6 ++++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 09ac22ca276..c19bd19879a 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -84,7 +84,7 @@ def get_portal_programs(): return None program_list = [frappe.get_doc("Program", program) for program in published_programs] - portal_programs = [program for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] + portal_programs = [{'program': program, 'has_access': allowed_program_access(program.name)} for program in program_list if allowed_program_access(program.name) or program.allow_self_enroll] return portal_programs diff --git a/erpnext/www/lms/all-programs.html b/erpnext/www/lms/all-programs.html index 088498b4a98..02df51acdf2 100644 --- a/erpnext/www/lms/all-programs.html +++ b/erpnext/www/lms/all-programs.html @@ -42,7 +42,7 @@
{% for program in all_programs %} - {{ program_card(program) }} + {{ program_card(program.program, program.has_access) }} {% endfor %}
diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 67e3508df59..25d5bc7e7f3 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -16,7 +16,7 @@ def get_context(context): frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect - context.content = frappe.get_doc(content_type, content).as_dict() + context.content = frappe.get_doc(content_type, content) context.content_type = content_type context.course = frappe.form_dict['course'] diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index ba3034c79e0..6661e22ce33 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -46,7 +46,7 @@
{% for program in featured_programs %} - {{ program_card(program) }} + {{ program_card(program.program, program.has_access) }} {% endfor %}

diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html index 8cf8a78e565..a0e4dab3495 100644 --- a/erpnext/www/lms/macros/card.html +++ b/erpnext/www/lms/macros/card.html @@ -1,4 +1,4 @@ -{% macro program_card(program) %} +{% macro program_card(program, has_access) %}

From 3336fb595c05b62525a1dcb4b627f0514e74311a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:04:36 +0530 Subject: [PATCH 43/96] style: removed console.logs --- erpnext/www/lms/macros/hero.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 89011b28919..20e9d05c600 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -20,7 +20,7 @@ function enroll() { let params = frappe.utils.get_query_params() - console.log(params.program) + let btn = document.getElementById('enroll'); btn.disbaled = true; btn.innerText = 'Enrolling...' @@ -33,7 +33,6 @@ } frappe.call(opts).then(res => { - console.log(res) let success_dialog = new frappe.ui.Dialog({ title: __('Success'), secondary_action: function() { From e94e9d2b06a15a5e1b017714dd3bdb6ec1a2c0a6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:05:00 +0530 Subject: [PATCH 44/96] feat: content navigation and activity tracking working --- .../course_enrollment/course_enrollment.py | 16 ++++-- erpnext/education/utils.py | 52 ++++++++---------- erpnext/www/lms/content.html | 23 +++++--- erpnext/www/lms/content.py | 55 ++++++++++--------- 4 files changed, 76 insertions(+), 70 deletions(-) diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 6f2bb0db1f1..064b0757093 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -62,8 +62,9 @@ class CourseEnrollment(Document): }).insert() def add_activity(self, content_type, content): - if check_activity_exists(self.name, content_type, content): - pass + activity = check_activity_exists(self.name, content_type, content) + if activity: + return activity else: activity = frappe.get_doc({ "doctype": "Course Activity", @@ -71,9 +72,14 @@ class CourseEnrollment(Document): "content_type": content_type, "content": content, "activity_date": frappe.utils.datetime.datetime.now() - }) - activity.insert() + }) + + activity.insert(ignore_permissions=True) + return activity.name def check_activity_exists(enrollment, content_type, content): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment, 'content_type': content_type, 'content': content}) - return bool(activity) \ No newline at end of file + if activity: + return activity[0].name + else: + return None \ No newline at end of file diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index c19bd19879a..a4b71e310ea 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -77,7 +77,7 @@ def get_portal_programs(): is_published and (student_is_enrolled or student_can_self_enroll) Returns: - list of objects: List of all programs and to be displayed on the portal along with enrollment status + list of dictionary: List of all programs and to be displayed on the portal along with access rights """ published_programs = frappe.get_all("Program", filters={"is_published": True}) if not published_programs: @@ -170,40 +170,34 @@ def enroll_in_program(program_name, student=None): return program_enrollment.name def has_super_access(): + """Check if user has a role that allows full access to LMS + + Returns: + bool: true if user has access to all lms content + """ current_user = frappe.get_doc('User', frappe.session.user) roles = set([role.role for role in current_user.roles]) return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) -# def get_program_enrollment(program_name): -# """ -# Function to get program enrollments for a particular student for a program -# """ -# student get_current_student() -# if not student: -# return None -# else: -# enrollment = frappe.get_all("Program Enrollment", filters={'student':student.name, 'program': program_name}) -# if enrollment: -# return enrollment[0].name -# else: -# return None +@frappe.whitelist() +def add_activity(course, content_type, content): + if has_super_access(): + return None -# def get_program_and_enrollment_status(program_name): -# program = frappe.get_doc('Program', program_name) -# is_enrolled = bool(get_program_enrollment(program_name)) or check_super_access() -# return {'program': program, 'is_enrolled': is_enrolled} + student = get_current_student() + if not student: + return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) -# def get_course_enrollment(course_name): -# student = get_current_student() -# if not student: -# return None -# enrollment_name = frappe.get_all("Course Enrollment", filters={'student': student.name, 'course':course_name}) -# try: -# name = enrollment_name[0].name -# enrollment = frappe.get_doc("Course Enrollment", name) -# return enrollment -# except: -# return None + course_enrollment = get_enrollment("course", course, student.name) + print(course_enrollment) + if not course_enrollment: + return None + + enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + if content_type == 'Quiz': + return + else: + return enrollment.add_activity(content_type, content) def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 68fb9f32fee..a02b2c75005 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -34,19 +34,19 @@ {% macro navigation() %}
-

{{ content.name }}

+

{{ content.name }} ({{ position + 1 }}/{{length}})

{% if previous %} - Previous + Previous {% else %} - Back to Course + Back to Course {% endif %} {% if next %} - + {% else %} - + {% endif %}
@@ -126,13 +126,18 @@ }) function handle(url) { - frappe.call("add_activity", - { + + opts = { + method: "erpnext.education.utils.add_activity", + args: { course: "{{ course }}", content_type: "{{ content_type }}", - content: "{{ content.name }}", + content: "{{ content.name }}" } - ) + } + frappe.call(opts).then(res => { + window.location.href = url; + }) } {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 25d5bc7e7f3..51a8e32badf 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -5,10 +5,19 @@ import frappe no_cache = 1 def get_context(context): - program = frappe.form_dict['program'] - content = frappe.form_dict['content'] - content_type = frappe.form_dict['type'] + # Load Query Parameters + try: + program = frappe.form_dict['program'] + content = frappe.form_dict['content'] + content_type = frappe.form_dict['type'] + course = frappe.form_dict['course'] + topic = frappe.form_dict['topic'] + except KeyError: + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + + # Check if user has access to the content has_program_access = utils.allowed_program_access(program) has_content_access = allowed_content_access(program, content, content_type) @@ -16,46 +25,38 @@ def get_context(context): frappe.local.flags.redirect_location = '/lms' raise frappe.Redirect + + # Set context for content to be displayer context.content = frappe.get_doc(content_type, content) context.content_type = content_type + context.program = program + context.course = course + context.topic = topic - context.course = frappe.form_dict['course'] - context.topic = frappe.form_dict['topic'] - - context.previous = get_previous_content(context.topic, context.course, context.content, context.content_type) - context.next = get_next_content(context.topic, context.course, context.content, context.content_type) - - -def get_next_content(topic, course, content, content_type): - if frappe.session.user == "Guest": - return None topic = frappe.get_doc("Topic", topic) content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] - current_index = content_list.index({'content': content.name, 'content_type': content_type}) + + # Set context for progress numbers + context.position = content_list.index({'content': content, 'content_type': content_type}) + context.length = len(content_list) + + # Set context for navigation + context.previous = get_previous_content(content_list, context.position) + context.next = get_next_content(content_list, context.position) + +def get_next_content(content_list, current_index): try: return content_list[current_index + 1] except IndexError: return None -def get_previous_content(topic, course, content, content_type): - if frappe.session.user == "Guest": - return None - topic = frappe.get_doc("Topic", topic) - content_list = [{'content_type':item.doctype, 'content':item.name} for item in topic.get_contents()] - current_index = content_list.index({'content': content.name, 'content_type': content_type}) +def get_previous_content(content_list, current_index): if current_index == 0: return None else: return content_list[current_index - 1] def allowed_content_access(program, content, content_type): - # Get all content in program - - # Using ORM - # course_in_program = [course.course for course in frappe.get_all('Program Course', fields=['course'], filters={'parent': program})] - # topics_in_course = [topic.topic for topic in frappe.get_all("Course Topic", fields=['topic'], filters=[['parent','in', course_in_program]])] - # contents_of_program = [[c.content, c.content_type] for c in frappe.get_all('Topic Content', fields=['content', 'content_type'], filters=[['parent','in', topics_in_course]])] - contents_of_program = frappe.db.sql("""select `tabtopic content`.content, `tabtopic content`.content_type from `tabcourse topic`, `tabprogram course`, From 253a2bd26042eea776a39cbcd72d9fd67030f2d0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:29:46 +0530 Subject: [PATCH 45/96] =?UTF-8?q?refactor:=20goodbye=20Vue=20=F0=9F=91=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- erpnext/public/build.json | 3 - erpnext/public/js/education/lms/call.js | 15 --- .../js/education/lms/components/Article.vue | 44 ------- .../education/lms/components/Breadcrumb.vue | 56 --------- .../js/education/lms/components/Button.vue | 25 ---- .../js/education/lms/components/CardList.vue | 28 ----- .../lms/components/ContentNavigation.vue | 40 ------ .../education/lms/components/ContentTitle.vue | 29 ----- .../education/lms/components/CourseCard.vue | 87 ------------- .../js/education/lms/components/Navbar.vue | 85 ------------- .../education/lms/components/ProfileInfo.vue | 83 ------------ .../education/lms/components/ProgramCard.vue | 82 ------------ .../education/lms/components/ProgressCard.vue | 89 ------------- .../js/education/lms/components/Quiz.vue | 119 ------------------ .../components/Quiz/QuizMultipleChoice.vue | 34 ----- .../lms/components/Quiz/QuizSingleChoice.vue | 28 ----- .../js/education/lms/components/ScoreCard.vue | 60 --------- .../education/lms/components/TopSection.vue | 27 ---- .../lms/components/TopSectionButton.vue | 49 -------- .../js/education/lms/components/TopicCard.vue | 112 ----------------- .../js/education/lms/components/Video.vue | 63 ---------- .../education/lms/components/VideoModal.vue | 35 ------ .../lms/components/YoutubePlayer.vue | 36 ------ erpnext/public/js/education/lms/lms.js | 81 ------------ erpnext/public/js/education/lms/lmsRoot.vue | 45 ------- .../js/education/lms/pages/ContentPage.vue | 84 ------------- .../js/education/lms/pages/CoursePage.vue | 49 -------- .../public/js/education/lms/pages/Home.vue | 48 ------- .../js/education/lms/pages/ListPage.vue | 53 -------- .../js/education/lms/pages/ProfilePage.vue | 50 -------- .../js/education/lms/pages/ProgramPage.vue | 49 -------- erpnext/public/js/education/lms/routes.js | 92 -------------- 32 files changed, 1780 deletions(-) delete mode 100644 erpnext/public/js/education/lms/call.js delete mode 100644 erpnext/public/js/education/lms/components/Article.vue delete mode 100644 erpnext/public/js/education/lms/components/Breadcrumb.vue delete mode 100644 erpnext/public/js/education/lms/components/Button.vue delete mode 100644 erpnext/public/js/education/lms/components/CardList.vue delete mode 100644 erpnext/public/js/education/lms/components/ContentNavigation.vue delete mode 100644 erpnext/public/js/education/lms/components/ContentTitle.vue delete mode 100644 erpnext/public/js/education/lms/components/CourseCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Navbar.vue delete mode 100644 erpnext/public/js/education/lms/components/ProfileInfo.vue delete mode 100644 erpnext/public/js/education/lms/components/ProgramCard.vue delete mode 100644 erpnext/public/js/education/lms/components/ProgressCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue delete mode 100644 erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue delete mode 100644 erpnext/public/js/education/lms/components/ScoreCard.vue delete mode 100644 erpnext/public/js/education/lms/components/TopSection.vue delete mode 100644 erpnext/public/js/education/lms/components/TopSectionButton.vue delete mode 100644 erpnext/public/js/education/lms/components/TopicCard.vue delete mode 100644 erpnext/public/js/education/lms/components/Video.vue delete mode 100644 erpnext/public/js/education/lms/components/VideoModal.vue delete mode 100644 erpnext/public/js/education/lms/components/YoutubePlayer.vue delete mode 100644 erpnext/public/js/education/lms/lms.js delete mode 100644 erpnext/public/js/education/lms/lmsRoot.vue delete mode 100644 erpnext/public/js/education/lms/pages/ContentPage.vue delete mode 100644 erpnext/public/js/education/lms/pages/CoursePage.vue delete mode 100644 erpnext/public/js/education/lms/pages/Home.vue delete mode 100644 erpnext/public/js/education/lms/pages/ListPage.vue delete mode 100644 erpnext/public/js/education/lms/pages/ProfilePage.vue delete mode 100644 erpnext/public/js/education/lms/pages/ProgramPage.vue delete mode 100644 erpnext/public/js/education/lms/routes.js diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 45de6eb294f..60e72dad715 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -54,8 +54,5 @@ "stock/dashboard/item_dashboard.html", "stock/dashboard/item_dashboard_list.html", "stock/dashboard/item_dashboard.js" - ], - "js/lms.min.js": [ - "public/js/education/lms/lms.js" ] } diff --git a/erpnext/public/js/education/lms/call.js b/erpnext/public/js/education/lms/call.js deleted file mode 100644 index 4edcaaa6d6a..00000000000 --- a/erpnext/public/js/education/lms/call.js +++ /dev/null @@ -1,15 +0,0 @@ -frappe.ready(() => { - frappe.provide('lms'); - - lms.call = (method, args) => { - const method_path = 'erpnext.www.lms_legacy.' + method; - return new Promise((resolve, reject) => { - return frappe.call({ - method: method_path, - args, - }) - .then(r => resolve(r.message)) - .fail(reject); - }); - }; -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Article.vue b/erpnext/public/js/education/lms/components/Article.vue deleted file mode 100644 index eab1424455c..00000000000 --- a/erpnext/public/js/education/lms/components/Article.vue +++ /dev/null @@ -1,44 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/Breadcrumb.vue b/erpnext/public/js/education/lms/components/Breadcrumb.vue deleted file mode 100644 index 1b617a3751c..00000000000 --- a/erpnext/public/js/education/lms/components/Breadcrumb.vue +++ /dev/null @@ -1,56 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Button.vue b/erpnext/public/js/education/lms/components/Button.vue deleted file mode 100644 index 4d8df4b3140..00000000000 --- a/erpnext/public/js/education/lms/components/Button.vue +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/erpnext/public/js/education/lms/components/CardList.vue b/erpnext/public/js/education/lms/components/CardList.vue deleted file mode 100644 index 10f6af096c0..00000000000 --- a/erpnext/public/js/education/lms/components/CardList.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/ContentNavigation.vue b/erpnext/public/js/education/lms/components/ContentNavigation.vue deleted file mode 100644 index a07c0f85f40..00000000000 --- a/erpnext/public/js/education/lms/components/ContentNavigation.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ContentTitle.vue b/erpnext/public/js/education/lms/components/ContentTitle.vue deleted file mode 100644 index a488ab85c37..00000000000 --- a/erpnext/public/js/education/lms/components/ContentTitle.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/CourseCard.vue b/erpnext/public/js/education/lms/components/CourseCard.vue deleted file mode 100644 index 48a9f591c73..00000000000 --- a/erpnext/public/js/education/lms/components/CourseCard.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Navbar.vue b/erpnext/public/js/education/lms/components/Navbar.vue deleted file mode 100644 index f3f3ce4cbb0..00000000000 --- a/erpnext/public/js/education/lms/components/Navbar.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProfileInfo.vue b/erpnext/public/js/education/lms/components/ProfileInfo.vue deleted file mode 100644 index 5bad713997f..00000000000 --- a/erpnext/public/js/education/lms/components/ProfileInfo.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgramCard.vue b/erpnext/public/js/education/lms/components/ProgramCard.vue deleted file mode 100644 index 15a9fcdcd2e..00000000000 --- a/erpnext/public/js/education/lms/components/ProgramCard.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/ProgressCard.vue b/erpnext/public/js/education/lms/components/ProgressCard.vue deleted file mode 100644 index 66b61f694e4..00000000000 --- a/erpnext/public/js/education/lms/components/ProgressCard.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/Quiz.vue b/erpnext/public/js/education/lms/components/Quiz.vue deleted file mode 100644 index 0a6199a7568..00000000000 --- a/erpnext/public/js/education/lms/components/Quiz.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue deleted file mode 100644 index 338b1ac0c51..00000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizMultipleChoice.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue b/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue deleted file mode 100644 index 235cbce4ae3..00000000000 --- a/erpnext/public/js/education/lms/components/Quiz/QuizSingleChoice.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/erpnext/public/js/education/lms/components/ScoreCard.vue b/erpnext/public/js/education/lms/components/ScoreCard.vue deleted file mode 100644 index 80b12cb6f63..00000000000 --- a/erpnext/public/js/education/lms/components/ScoreCard.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSection.vue b/erpnext/public/js/education/lms/components/TopSection.vue deleted file mode 100644 index c27d0031efb..00000000000 --- a/erpnext/public/js/education/lms/components/TopSection.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/components/TopSectionButton.vue b/erpnext/public/js/education/lms/components/TopSectionButton.vue deleted file mode 100644 index 0fa49d4da53..00000000000 --- a/erpnext/public/js/education/lms/components/TopSectionButton.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/TopicCard.vue b/erpnext/public/js/education/lms/components/TopicCard.vue deleted file mode 100644 index 4cb8e85c3bf..00000000000 --- a/erpnext/public/js/education/lms/components/TopicCard.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/Video.vue b/erpnext/public/js/education/lms/components/Video.vue deleted file mode 100644 index 50b4dd460d4..00000000000 --- a/erpnext/public/js/education/lms/components/Video.vue +++ /dev/null @@ -1,63 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/VideoModal.vue b/erpnext/public/js/education/lms/components/VideoModal.vue deleted file mode 100644 index 71227ade2c3..00000000000 --- a/erpnext/public/js/education/lms/components/VideoModal.vue +++ /dev/null @@ -1,35 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/components/YoutubePlayer.vue b/erpnext/public/js/education/lms/components/YoutubePlayer.vue deleted file mode 100644 index 9377b57d3b8..00000000000 --- a/erpnext/public/js/education/lms/components/YoutubePlayer.vue +++ /dev/null @@ -1,36 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lms.js b/erpnext/public/js/education/lms/lms.js deleted file mode 100644 index 4665b144c2e..00000000000 --- a/erpnext/public/js/education/lms/lms.js +++ /dev/null @@ -1,81 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import VueRouter from 'vue-router/dist/vue-router.js'; -import moment from 'moment/min/moment.min.js'; - -import lmsRoot from "./lmsRoot.vue"; -import routes from './routes'; -import './call'; - -Vue.use(VueRouter); - -var store = { - enrolledPrograms: [], - enrolledCourses: [] -}; - -// let profile_page = ` LMS Profile ` -// document.querySelector('#website-post-login > ul').innerHTML += profile_page - -frappe.ready(() => { - frappe.provide('lms'); - - lms.moment = moment; - - lms.store = new Vue({ - data: store, - methods: { - updateEnrolledPrograms() { - if(this.checkLogin()) { - lms.call("get_program_enrollments").then(data => { - this.enrolledPrograms = data; - }); - } - }, - updateEnrolledCourses() { - if(this.checkLogin()) { - lms.call("get_all_course_enrollments").then(data => { - this.enrolledCourses = data; - }); - } - }, - checkLogin() { - return frappe.is_user_logged_in(); - }, - updateState() { - this.checkLogin(); - this.updateEnrolledPrograms(); - this.updateEnrolledCourses(); - }, - checkProgramEnrollment(programName) { - if(this.checkLogin()){ - if(this.enrolledPrograms) { - if(this.enrolledPrograms.includes(programName)) { - return true; - } - else { - return false; - } - } - else { - return false; - } - } - else { - return false; - } - } - } - }); - lms.view = new Vue({ - el: "#lms-app", - router: new VueRouter({ routes }), - template: "", - components: { lmsRoot }, - mounted() { - lms.store.updateState(); - } - }); - lms.view.$router.afterEach((to, from) => { - window.scrollTo(0,0); - }); -}); \ No newline at end of file diff --git a/erpnext/public/js/education/lms/lmsRoot.vue b/erpnext/public/js/education/lms/lmsRoot.vue deleted file mode 100644 index d359265c587..00000000000 --- a/erpnext/public/js/education/lms/lmsRoot.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/erpnext/public/js/education/lms/pages/ContentPage.vue b/erpnext/public/js/education/lms/pages/ContentPage.vue deleted file mode 100644 index 224ee03a4ad..00000000000 --- a/erpnext/public/js/education/lms/pages/ContentPage.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/CoursePage.vue b/erpnext/public/js/education/lms/pages/CoursePage.vue deleted file mode 100644 index dc3d13052b0..00000000000 --- a/erpnext/public/js/education/lms/pages/CoursePage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/Home.vue b/erpnext/public/js/education/lms/pages/Home.vue deleted file mode 100644 index 6554a765871..00000000000 --- a/erpnext/public/js/education/lms/pages/Home.vue +++ /dev/null @@ -1,48 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ListPage.vue b/erpnext/public/js/education/lms/pages/ListPage.vue deleted file mode 100644 index cf5cecce9c1..00000000000 --- a/erpnext/public/js/education/lms/pages/ListPage.vue +++ /dev/null @@ -1,53 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProfilePage.vue b/erpnext/public/js/education/lms/pages/ProfilePage.vue deleted file mode 100644 index beff5eb34eb..00000000000 --- a/erpnext/public/js/education/lms/pages/ProfilePage.vue +++ /dev/null @@ -1,50 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/pages/ProgramPage.vue b/erpnext/public/js/education/lms/pages/ProgramPage.vue deleted file mode 100644 index 415c861e812..00000000000 --- a/erpnext/public/js/education/lms/pages/ProgramPage.vue +++ /dev/null @@ -1,49 +0,0 @@ - - \ No newline at end of file diff --git a/erpnext/public/js/education/lms/routes.js b/erpnext/public/js/education/lms/routes.js deleted file mode 100644 index 483f2220c3f..00000000000 --- a/erpnext/public/js/education/lms/routes.js +++ /dev/null @@ -1,92 +0,0 @@ -import Home from "./pages/Home.vue"; -import ProgramPage from "./pages/ProgramPage.vue"; -import CoursePage from "./pages/CoursePage.vue"; -import ContentPage from "./pages/ContentPage.vue"; -import ListPage from "./pages/ListPage.vue"; -import ProfilePage from "./pages/ProfilePage.vue"; - -const routes = [{ - name: 'home', - path: '', - component: Home -}, -{ - name: 'program', - path: '/Program/:program_name', - component: ProgramPage, - props: true -}, -{ - name: 'course', - path: '/Program/:program_name/:course_name/', - component: CoursePage, - props: true, -}, -{ - name: 'content', - path: '/Program/:program_name/:course_name/:topic/:type/:content', - component: ContentPage, - props: true, - beforeRouteUpdate (to, from, next) { - if (lms.store.checkProgramEnrollment(to.params.program_name)) { - next(); - } else { - next({ - name: 'program', - params: { - program_name: to.params.program_name - } - }); - } - } -}, -{ - name: 'list', - path: '/List/:master', - component: ListPage, - props: true -}, -{ - name: 'signup', - path: '/Signup', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#signup'; - }, - component: Home, - props: true -}, -{ - name: 'login', - path: '/Login', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/login#login'; - }, - component: Home, - props: true -}, -{ - name: 'logout', - path: '/Logout', - beforeEnter(to, from, next) { - window.location = window.location.origin.toString() + '/?cmd=web_logout'; - }, - component: Home, - props: true -}, -{ - name: 'profile', - path: '/Profile', - component: ProfilePage, - props: true, - beforeEnter: (to, from, next) => { - if (!lms.store.checkLogin()) { - next({ - name: 'home' - }); - } else { - next(); - } - } -}]; - -export default routes; \ No newline at end of file From 7c7053fcf59f7f22b0fb3e16ace6d7719dda08df Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:54:58 +0530 Subject: [PATCH 46/96] refactor: added question type field --- .../education/doctype/question/question.json | 221 ++++++------------ .../education/doctype/question/question.py | 8 + 2 files changed, 75 insertions(+), 154 deletions(-) diff --git a/erpnext/education/doctype/question/question.json b/erpnext/education/doctype/question/question.json index 14a9f3ce922..b3a161daa07 100644 --- a/erpnext/education/doctype/question/question.json +++ b/erpnext/education/doctype/question/question.json @@ -1,167 +1,80 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "format:QUESTION-{#####}", - "beta": 0, - "creation": "2018-10-01 15:58:00.696815", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "format:QUESTION-{#####}", + "creation": "2018-10-01 15:58:00.696815", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "options", + "question_type" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Small Text", - "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": "Question", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Question", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "options", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Options", - "length": 0, - "no_copy": 0, - "options": "Options", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "options", + "fieldtype": "Table", + "label": "Options", + "options": "Options", + "reqd": 1 + }, + { + "fieldname": "question_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "\nSingle Correct Answer\nMultiple Correct Answer", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-22 14:02:08.140652", - "modified_by": "Administrator", - "module": "Education", - "name": "Question", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-30 18:39:21.880974", + "modified_by": "Administrator", + "module": "Education", + "name": "Question", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Academics User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Instructor", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/education/doctype/question/question.py b/erpnext/education/doctype/question/question.py index 8cd23983ce9..b8221081fab 100644 --- a/erpnext/education/doctype/question/question.py +++ b/erpnext/education/doctype/question/question.py @@ -12,6 +12,7 @@ class Question(Document): def validate(self): self.check_at_least_one_option() self.check_minimum_one_correct_answer() + self.set_question_type() def check_at_least_one_option(self): if len(self.options) <= 1: @@ -26,6 +27,13 @@ class Question(Document): else: frappe.throw(_("A qustion must have at least one correct options")) + def set_question_type(self): + correct_options = [option for option in self.options if option.is_correct] + if len(correct_options) > 1: + self.question_type = "Multiple Correct Answer" + else: + self.question_type = "Single Correct Answer" + def get_answer(self): options = self.options answers = [item.name for item in options if item.is_correct == True] From 46b3446da050d1822b2b8fd6e607f969195601fd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 30 May 2019 18:55:16 +0530 Subject: [PATCH 47/96] refactor: add check for max passing score --- erpnext/education/doctype/quiz/quiz.json | 232 ++--------------------- erpnext/education/doctype/quiz/quiz.py | 3 + 2 files changed, 22 insertions(+), 213 deletions(-) diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json index f91bc0f0219..b4903fc285d 100644 --- a/erpnext/education/doctype/quiz/quiz.json +++ b/erpnext/education/doctype/quiz/quiz.json @@ -1,299 +1,105 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "field:title", - "beta": 0, "creation": "2018-10-17 05:52:50.149904", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "title", + "question", + "quiz_configuration_section", + "passing_score", + "max_attempts", + "grading_basis" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "question", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Question", - "length": 0, - "no_copy": 0, "options": "Quiz Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "quiz_configuration_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quiz Configuration", - "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 + "label": "Quiz Configuration" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "75", + "description": "Score out of 100", "fieldname": "passing_score", "fieldtype": "Float", - "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": "Passing Score", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "description": "Enter 0 to waive limit", "fieldname": "max_attempts", "fieldtype": "Int", - "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": "Max Attempts", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Last Highest Score", + "default": "Latest Highest Score", "fieldname": "grading_basis", "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": "Grading Basis", - "length": 0, - "no_copy": 0, - "options": "\nLast Attempt\nLast Highest Score", - "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 + "options": "Latest Highest Score\nLatest Attempt" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-25 19:07:36.190116", + "modified": "2019-05-30 18:50:54.218571", "modified_by": "Administrator", "module": "Education", "name": "Quiz", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Academics User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "LMS User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Instructor", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index 6da50a6e258..6d00d333723 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -7,6 +7,9 @@ import frappe from frappe.model.document import Document class Quiz(Document): + def validate(self): + if self.passing_score > 100: + frappe.throw("Passing Score value should be between 0 and 100") def validate_quiz_attempts(self, enrollment, quiz_name): if self.max_attempts > 0: From 52ea2874ec6f29044abd4133eb1019900a4fe2cf Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 30 May 2019 22:09:36 +0530 Subject: [PATCH 48/96] fix: Multiple fixes in accounting dimensions --- .../accounting_dimension.js | 3 - .../accounting_dimension.py | 67 +++++++++++-------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 6 +- .../public/js/utils/dimension_tree_filter.js | 4 +- 4 files changed, 44 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 1f83d9a052b..fcbd10f606f 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -27,9 +27,6 @@ frappe.ui.form.on('Accounting Dimension', { method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension", args: { doc: frm.doc - }, - callback: function() { - frappe.msgprint(__("{0} dimension disabled", [frm.doc.label])); } }); } diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index a14f71407d0..1d9700561a1 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ import json from frappe.model.document import Document from frappe.custom.doctype.custom_field.custom_field import create_custom_field @@ -33,8 +34,9 @@ class AccountingDimension(Document): self.fieldname = scrub(self.label) def make_dimension_in_accounting_doctypes(doc): - doclist = get_doclist() + doclist = get_doctypes_with_dimensions() doc_count = len(get_accounting_dimensions()) + count = 0 for doctype in doclist: @@ -52,38 +54,45 @@ def make_dimension_in_accounting_doctypes(doc): } if doctype == "Budget": - df.update({ - "insert_after": "cost_center", - "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type) - }) - - create_custom_field(doctype, df) - - property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") - - if property_setter: - property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") - property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type - property_setter_doc.save() - - frappe.clear_cache(doctype='Budget') - else: - frappe.get_doc({ - "doctype": "Property Setter", - "doctype_or_field": "DocField", - "doc_type": "Budget", - "field_name": "budget_against", - "property": "options", - "property_type": "Text", - "value": "\nCost Center\nProject\n" + doc.document_type - }).insert(ignore_permissions=True) + add_dimensions_to_budget_doctype(df, doc) else: create_custom_field(doctype, df) + count += 1 + + frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions...")) frappe.clear_cache(doctype=doctype) +def add_dimension_to_budget_doctype(df, doc): + df.update({ + "insert_after": "cost_center", + "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type) + }) + + create_custom_field("Budget", df) + + property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options") + + if property_setter: + property_setter_doc = frappe.get_doc("Property Setter", "Budget-budget_against-options") + property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type + property_setter_doc.save() + + frappe.clear_cache(doctype='Budget') + else: + frappe.get_doc({ + "doctype": "Property Setter", + "doctype_or_field": "DocField", + "doc_type": "Budget", + "field_name": "budget_against", + "property": "options", + "property_type": "Text", + "value": "\nCost Center\nProject\n" + doc.document_type + }).insert(ignore_permissions=True) + + def delete_accounting_dimension(doc): - doclist = get_doclist() + doclist = get_doctypes_with_dimensions() frappe.db.sql(""" DELETE FROM `tabCustom Field` @@ -122,7 +131,7 @@ def start_dimension_disabling(doc): else: df = {"read_only": 0} - doclist = get_doclist() + doclist = get_doctypes_with_dimensions() for doctype in doclist: field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')}) @@ -133,7 +142,7 @@ def start_dimension_disabling(doc): frappe.clear_cache(doctype=doctype) -def get_doclist(): +def get_doctypes_with_dimensions(): doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index cd4f794b6cb..4c617c727e8 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -84,15 +84,17 @@ class GLEntry(Document): def validate_dimensions_for_pl_and_bs(self): + account_type = frappe.db.get_value("Account", self.account, "report_type") + for dimension in get_accounting_dimensions(as_list=False): - if frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss" \ + if account_type == "Profit and Loss" \ and dimension.mandatory_for_pl and not dimension.disabled: if not self.get(dimension.fieldname): frappe.throw(_("{0} is required for 'Profit and Loss' account {1}.") .format(dimension.label, self.account)) - if frappe.db.get_value("Account", self.account, "report_type") == "Balance Sheet" \ + if account_type == "Balance Sheet" \ and dimension.mandatory_for_bs and not dimension.disabled: if not self.get(dimension.fieldname): frappe.throw(_("{0} is required for 'Balance Sheet' account {1}.") diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 827095ae558..16c1d4d2485 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -1,6 +1,6 @@ frappe.provide('frappe.ui.form'); -let doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", +erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", @@ -9,7 +9,7 @@ let doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", let dimension_filters = erpnext.get_dimension_filters(); -doclist.forEach((doctype) => { +erpnext.doctypes_with_dimensions.forEach((doctype) => { frappe.ui.form.on(doctype, { onload: function(frm) { dimension_filters.then((dimensions) => { From b8eaa3d3777a30373abb1dfe78ec0de4d1ecf89f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 1 Jun 2019 20:56:37 +0530 Subject: [PATCH 49/96] fix: Issue list portal styling --- erpnext/templates/includes/issue_row.html | 59 +++++++++++------------ erpnext/templates/includes/macros.html | 2 +- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/erpnext/templates/includes/issue_row.html b/erpnext/templates/includes/issue_row.html index 637fde26c5f..ff868fabb51 100644 --- a/erpnext/templates/includes/issue_row.html +++ b/erpnext/templates/includes/issue_row.html @@ -1,33 +1,30 @@ - \ No newline at end of file diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index 2d27915aa8d..3c82e90cc0d 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -45,7 +45,7 @@
{{ card.title }}

{{ card.subtitle or '' }}

-

{{ card.content | truncate(140, True) }}

+

{{ card.content or '' | truncate(140, True) }}

{{ _('More details') }} From 223225e785bc458eee2042278cb07432ae507c0a Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 1 Jun 2019 23:09:04 +0530 Subject: [PATCH 50/96] fix: Update function name --- .../doctype/accounting_dimension/accounting_dimension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 1d9700561a1..931b479720e 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -54,7 +54,7 @@ def make_dimension_in_accounting_doctypes(doc): } if doctype == "Budget": - add_dimensions_to_budget_doctype(df, doc) + add_dimension_to_budget_doctype(df, doc) else: create_custom_field(doctype, df) From e19abb162a67656cba0cd0788e45ae3ee2ed85ae Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 1 Jun 2019 23:26:09 +0530 Subject: [PATCH 51/96] fix: Resolve merge conflicts in purchase_receipt_item --- .../purchase_receipt_item.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index ad5ee7b24de..66df86bcf41 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -79,10 +79,7 @@ "batch_no", "column_break_48", "rejected_serial_no", - "section_break_50", - "project", "expense_account", - "cost_center", "col_break5", "allow_zero_valuation_rate", "bom", @@ -782,11 +779,20 @@ "label": "Expense Account", "options": "Account", "read_only": 1 + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, - "modified": "2019-05-30 18:09:21.648599", + "modified": "2019-06-01 23:25:20.732134", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 6d313c72e1793c1863be18570c2ae9108b455b32 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 1 Jun 2019 23:26:42 +0530 Subject: [PATCH 52/96] fix: Remove diemnsion from value list if present --- .../doctype/accounting_dimension/accounting_dimension.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 931b479720e..91d03be493f 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -108,7 +108,9 @@ def delete_accounting_dimension(doc): budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") value_list = budget_against_property.value.split('\n')[3:] - value_list.remove(doc.document_type) + + if doc.document_type in value_list: + value_list.remove(doc.document_type) budget_against_property.value = "\nCost Center\nProject\n" + "\n".join(value_list) budget_against_property.save() From 4a7a380f0cf249bddb5f21d6ee04babc8c82e6f5 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 2 Jun 2019 19:46:44 +0530 Subject: [PATCH 53/96] fix: codacy --- .../doctype/quality_action/quality_action.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_action/quality_action.js b/erpnext/quality_management/doctype/quality_action/quality_action.js index d279a7390c4..af051d971e0 100644 --- a/erpnext/quality_management/doctype/quality_action/quality_action.js +++ b/erpnext/quality_management/doctype/quality_action/quality_action.js @@ -16,10 +16,11 @@ frappe.ui.form.on('Quality Action', { callback: function(data){ frm.fields_dict.resolutions.grid.remove_all(); let objectives = []; + let i = 0; if(frm.doc.document_type === "Quality Review"){ - for(var i in data.message.reviews) objectives.push(data.message.reviews[i].review); + for(i in data.message.reviews) objectives.push(data.message.reviews[i].review); } else { - for(var i in data.message.parameters) objectives.push(data.message.parameters[i].feedback); + for(i in data.message.parameters) objectives.push(data.message.parameters[i].feedback); } for (var objective in objectives){ frm.add_child("resolutions"); From 9f458e3413e5ef29b2f18bd588c86d1096b29912 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 2 Jun 2019 20:47:23 +0530 Subject: [PATCH 54/96] fix: codacy --- .../doctype/quality_action/quality_action.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_action/quality_action.js b/erpnext/quality_management/doctype/quality_action/quality_action.js index af051d971e0..70782477f06 100644 --- a/erpnext/quality_management/doctype/quality_action/quality_action.js +++ b/erpnext/quality_management/doctype/quality_action/quality_action.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Quality Action', { callback: function(data){ frm.fields_dict.resolutions.grid.remove_all(); let objectives = []; - let i = 0; + if(frm.doc.document_type === "Quality Review"){ - for(i in data.message.reviews) objectives.push(data.message.reviews[i].review); + for(let i in data.message.reviews) objectives.push(data.message.reviews[i].review); } else { - for(i in data.message.parameters) objectives.push(data.message.parameters[i].feedback); + for(let j in data.message.parameters) objectives.push(data.message.parameters[j].feedback); } for (var objective in objectives){ frm.add_child("resolutions"); From 853a561f116ec8513bd270cf0f5fdd0505985949 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 3 Jun 2019 11:57:00 +0530 Subject: [PATCH 55/96] fix: Serial no filtering issue for delivered items --- erpnext/public/js/utils/serial_no_batch_selector.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index b22d5ca4c4e..7b04efce282 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -373,13 +373,24 @@ erpnext.SerialNoBatchSelector = Class.extend({ get_serial_no_fields: function() { var me = this; this.serial_list = []; + + let serial_no_filters = { + item_code: me.item_code, + delivery_document_no: ["in", ""] + } + + if (me.warehouse_details.name) { + serial_no_filters['warehouse'] = me.warehouse_details.name; + } return [ {fieldtype: 'Section Break', label: __('Serial Numbers')}, { fieldtype: 'Link', fieldname: 'serial_no_select', options: 'Serial No', label: __('Select to add Serial Number.'), get_query: function() { - return { filters: {item_code: me.item_code, warehouse: me.warehouse_details.name}}; + return { + filters: serial_no_filters + }; }, onchange: function(e) { if(this.in_local_change) return; From d1a252190b657f4ea64819760b5f653b7a00428e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 12:57:38 +0530 Subject: [PATCH 56/96] refactor: refactored quiz api and added quiz.js --- .../course_enrollment/course_enrollment.py | 8 +- erpnext/education/doctype/quiz/quiz.py | 49 ++--- .../doctype/quiz_result/quiz_result.json | 183 +++++------------ erpnext/education/utils.py | 56 +++++- erpnext/public/js/education/lms/quiz.js | 185 ++++++++++++++++++ erpnext/www/lms/content.html | 47 ++++- erpnext/www/lms/content.py | 2 +- 7 files changed, 350 insertions(+), 180 deletions(-) create mode 100644 erpnext/public/js/education/lms/quiz.js diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py index 064b0757093..b082be2aa21 100644 --- a/erpnext/education/doctype/course_enrollment/course_enrollment.py +++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py @@ -35,7 +35,7 @@ class CourseEnrollment(Document): if enrollment: frappe.throw(_("Student is already enrolled.")) - def add_quiz_activity(self, quiz_name, quiz_response,answers, score, status): + def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result_data = [] for key in answers: @@ -43,7 +43,9 @@ class CourseEnrollment(Document): item['question'] = key item['quiz_result'] = result[key] try: - if isinstance(quiz_response[key], list): + if not quiz_response[key]: + item['selected_option'] = "Unattempted" + elif isinstance(quiz_response[key], list): item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key]) else: item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option') @@ -59,7 +61,7 @@ class CourseEnrollment(Document): "result": result_data, "score": score, "status": status - }).insert() + }).insert(ignore_permissions = True) def add_activity(self, content_type, content): activity = check_activity_exists(self.name, content_type, content) diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py index 6d00d333723..8e54745464b 100644 --- a/erpnext/education/doctype/quiz/quiz.py +++ b/erpnext/education/doctype/quiz/quiz.py @@ -11,50 +11,43 @@ class Quiz(Document): if self.passing_score > 100: frappe.throw("Passing Score value should be between 0 and 100") - def validate_quiz_attempts(self, enrollment, quiz_name): - if self.max_attempts > 0: - try: - if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: - frappe.throw('Maximum attempts reached!') - except Exception as e: - pass + def allowed_attempt(self, enrollment, quiz_name): + if self.max_attempts == 0: + return True + + try: + if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts: + frappe.msgprint("Maximum attempts for this quiz reached!") + return False + else: + return True + except Exception as e: + return False def evaluate(self, response_dict, quiz_name): - # self.validate_quiz_attempts(enrollment, quiz_name) questions = [frappe.get_doc('Question', question.question_link) for question in self.question] answers = {q.name:q.get_answer() for q in questions} - correct_answers = {} + result = {} for key in answers: try: if isinstance(response_dict[key], list): - result = compare_list_elementwise(response_dict[key], answers[key]) + is_correct = compare_list_elementwise(response_dict[key], answers[key]) else: - result = (response_dict[key] == answers[key]) - except: - result = False - correct_answers[key] = result - score = (sum(correct_answers.values()) * 100 ) / len(answers) + is_correct = (response_dict[key] == answers[key]) + except Exception as e: + is_correct = False + result[key] = is_correct + score = (sum(result.values()) * 100 ) / len(answers) if score >= self.passing_score: status = "Pass" else: status = "Fail" - return correct_answers, score, status + return result, score, status def get_questions(self): - quiz_question = self.get_all_children() - if quiz_question: - questions = [frappe.get_doc('Question', question.question_link).as_dict() for question in quiz_question] - for question in questions: - correct_options = [option.is_correct for option in question.options] - if sum(correct_options) > 1: - question['type'] = "MultipleChoice" - else: - question['type'] = "SingleChoice" - return questions - else: - return None + return [frappe.get_doc('Question', question.question_link) for question in self.question] def compare_list_elementwise(*args): try: diff --git a/erpnext/education/doctype/quiz_result/quiz_result.json b/erpnext/education/doctype/quiz_result/quiz_result.json index 86505ac756a..67c7e2d4492 100644 --- a/erpnext/education/doctype/quiz_result/quiz_result.json +++ b/erpnext/education/doctype/quiz_result/quiz_result.json @@ -1,145 +1,52 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-10-15 15:52:25.766374", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2018-10-15 15:52:25.766374", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "selected_option", + "quiz_result" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "question", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Question", - "length": 0, - "no_copy": 0, - "options": "Question", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "question", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Question", + "options": "Question", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "selected_option", - "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": "Selected Option", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "selected_option", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Selected Option", + "read_only": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "quiz_result", - "fieldtype": "Select", - "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": "Result", - "length": 0, - "no_copy": 0, - "options": "\nCorrect\nWrong", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "fieldname": "quiz_result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCorrect\nWrong", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 } - ], - "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, - "max_attachments": 0, - "modified": "2019-03-27 17:58:54.388848", - "modified_by": "Administrator", - "module": "Education", - "name": "Quiz Result", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "modified": "2019-06-03 12:52:32.267392", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Result", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index a4b71e310ea..53f02f5f2f7 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For lice from __future__ import unicode_literals, division import frappe @@ -173,7 +172,7 @@ def has_super_access(): """Check if user has a role that allows full access to LMS Returns: - bool: true if user has access to all lms content + bool: true if user has access to all lms content """ current_user = frappe.get_doc('User', frappe.session.user) roles = set([role.role for role in current_user.roles]) @@ -189,7 +188,6 @@ def add_activity(course, content_type, content): return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) course_enrollment = get_enrollment("course", course, student.name) - print(course_enrollment) if not course_enrollment: return None @@ -199,6 +197,56 @@ def add_activity(course, content_type, content): else: return enrollment.add_activity(content_type, content) +@frappe.whitelist() +def evaluate_quiz(quiz_response, quiz_name, course): + import json + + student = get_current_student() + + quiz_response = json.loads(quiz_response) + quiz = frappe.get_doc("Quiz", quiz_name) + result, score, status = quiz.evaluate(quiz_response, quiz_name) + + if has_super_access(): + return {'result': result, 'score': score, 'status': status} + + if student: + course_enrollment = get_enrollment("course", course, student.name) + if course_enrollment: + enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + return {'result': result, 'score': score, 'status': status} + else: + return None + else: + frappe.throw("Something went wrong. Pleae contact the administrator.") + +@frappe.whitelist() +def get_quiz(quiz_name, course): + try: + quiz = frappe.get_doc("Quiz", quiz_name) + questions = quiz.get_questions() + except: + frappe.throw("Quiz {0} does not exist".format(quiz_name)) + return None + + questions = [{ + 'name': question.name, + 'question': question.question, + 'type': question.question_type, + 'options': [{'name': option.name, 'option': option.option} + for option in question.options], + } for question in questions] + + if has_super_access(): + return {'questions': questions, 'activity': None} + + student = get_current_student() + course_enrollment = get_enrollment("course", course, student.name) + status, score, result = check_quiz_completion(quiz, course_enrollment) + return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) @@ -226,7 +274,7 @@ def check_content_completion(content_name, content_type, enrollment_name): def check_quiz_completion(quiz, enrollment_name): attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) - status = False if quiz.max_attempts == 0 else bool(len(attempts) == quiz.max_attempts) + status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) score = None result = None if attempts: diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js new file mode 100644 index 00000000000..f6dc4d08d54 --- /dev/null +++ b/erpnext/public/js/education/lms/quiz.js @@ -0,0 +1,185 @@ +class Quiz { + constructor(wrapper, options) { + this.wrapper = wrapper; + Object.assign(this, options); + this.questions = [] + this.refresh(); + } + + refresh() { + this.get_quiz(); + } + + get_quiz() { + frappe.call('erpnext.education.utils.get_quiz', { + quiz_name: this.name, + course: this.course + }).then(res => { + this.make(res.message) + }); + } + + make(data) { + data.questions.forEach(question_data => { + let question_wrapper = document.createElement('div'); + let question = new Question({ + wrapper: question_wrapper, + ...question_data + }); + this.questions.push(question) + this.wrapper.appendChild(question_wrapper); + }) + if (data.activity.is_complete) { + this.disable() + let indicator = 'red' + let message = 'Your are not allowed to attempt the quiz again.' + if (data.activity.result == 'Pass') { + indicator = 'green' + message = 'You have already cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, data.activity.score) + } + else { + this.make_actions(); + } + } + + make_actions() { + const button = document.createElement("button"); + button.classList.add("btn", "btn-primary", "mt-5", "mr-2"); + + button.id = 'submit-button'; + button.innerText = 'Submit'; + button.onclick = () => this.submit(); + this.submit_btn = button + this.wrapper.appendChild(button); + } + + submit() { + this.submit_btn.innerText = 'Evaluating..' + this.submit_btn.disabled = true + this.disable() + frappe.call('erpnext.education.utils.evaluate_quiz', { + quiz_name: this.name, + quiz_response: this.get_selected(), + course: this.course + }).then(res => { + this.submit_btn.remove() + if (!res.message) { + frappe.throw("Something went wrong while evaluating the quiz.") + } + + let indicator = 'red' + let message = 'Fail' + if (res.message.status == 'Pass') { + indicator = 'green' + message = 'Congratulations, you cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, res.message.score) + }); + } + + set_quiz_footer(message, indicator, score) { + const div = document.createElement("div"); + div.classList.add("mt-5"); + div.innerHTML = `
+
+

${message}

+
Score: ${score}/100
+
+ +
` + + this.wrapper.appendChild(div) + } + + disable() { + this.questions.forEach(que => que.disable()) + } + + get_selected() { + let que = {} + this.questions.forEach(question => { + que[question.name] = question.get_selected() + }) + return que + } +} + +class Question { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.make_question() + this.make_options() + } + + get_selected() { + let selected = this.options.filter(opt => opt.input.checked) + if (this.type == 'Single Correct Answer') { + if (selected[0]) return selected[0].name + } + if (this.type == 'Multiple Correct Answer') { + return selected.map(opt => opt.name) + } + return null + } + + disable() { + let selected = this.options.forEach(opt => opt.input.disabled = true) + } + + make_question() { + let question_wrapper = document.createElement('h5'); + question_wrapper.classList.add('mt-3'); + question_wrapper.innerText = this.question; + this.wrapper.appendChild(question_wrapper); + } + + make_options() { + let make_input = (name, value) => { + let input = document.createElement('input'); + input.id = name; + input.name = this.name; + input.value = value; + input.type = 'radio'; + if (this.type == 'Multiple Correct Answer') + input.type = 'checkbox'; + input.classList.add('form-check-input'); + return input; + } + + let make_label = function(name, value) { + let label = document.createElement('label'); + label.classList.add('form-check-label'); + label.htmlFor = name; + label.innerText = value; + return label + } + + let make_option = function (wrapper, option) { + let option_div = document.createElement('div') + option_div.classList.add('form-check', 'pb-1') + let input = make_input(option.name, option.option); + let label = make_label(option.name, option.option); + option_div.appendChild(input) + option_div.appendChild(label) + wrapper.appendChild(option_div) + return {input: input, ...option} + } + + let options_wrapper = document.createElement('div') + options_wrapper.classList.add('ml-2') + let option_list = [] + this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt))) + this.options = option_list + this.wrapper.appendChild(options_wrapper) + } +} \ No newline at end of file diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index a02b2c75005..41f27f3bc1b 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -36,7 +36,7 @@

{{ content.name }} ({{ position + 1 }}/{{length}})

-
+
-
+
{{ content.description }}
{% endmacro %} @@ -95,6 +95,18 @@
{% endmacro %} +{% macro quiz() %} +
+
+
+

{{ content.name }} ({{ position + 1 }}/{{length}})

+
+
+
+
+
+{% endmacro %} + {% block content %}
@@ -104,7 +116,7 @@ {% elif content_type=='Article'%} {{ article() }} {% elif content_type=='Quiz' %} -

Quiz: {{ content.name }}

+ {{ quiz() }} {% endif %}
@@ -113,20 +125,41 @@ {% block script %} {% if content_type=='Video' %} - + + {% elif content_type == 'Quiz' %} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py index 51a8e32badf..f804cee3bd4 100644 --- a/erpnext/www/lms/content.py +++ b/erpnext/www/lms/content.py @@ -27,7 +27,7 @@ def get_context(context): # Set context for content to be displayer - context.content = frappe.get_doc(content_type, content) + context.content = frappe.get_doc(content_type, content).as_dict() context.content_type = content_type context.program = program context.course = course From 545fef9934ed1968d67665f2838a323dd8cd6078 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 3 Jun 2019 13:04:57 +0530 Subject: [PATCH 57/96] fix: Pass delivery document no as empty string --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 7b04efce282..a28d42b5673 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -376,7 +376,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ let serial_no_filters = { item_code: me.item_code, - delivery_document_no: ["in", ""] + delivery_document_no: "" } if (me.warehouse_details.name) { From 8ddb63adae6bbe3835d46019a4522885b4689b9d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 14:40:52 +0530 Subject: [PATCH 58/96] feat: added auto course enrollment for enrolled programs --- erpnext/education/utils.py | 36 ++++++++++++------------- erpnext/public/js/education/lms/quiz.js | 3 ++- erpnext/www/lms/content.html | 4 ++- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 53f02f5f2f7..96fb4eed8b8 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -179,7 +179,7 @@ def has_super_access(): return bool(roles & {'Administrator', 'Instructor', 'Education Manager', 'System Manager', 'Academic User'}) @frappe.whitelist() -def add_activity(course, content_type, content): +def add_activity(course, content_type, content, program): if has_super_access(): return None @@ -187,18 +187,14 @@ def add_activity(course, content_type, content): if not student: return frappe.throw("Student with email {0} does not exist".format(frappe.session.user), frappe.DoesNotExistError) - course_enrollment = get_enrollment("course", course, student.name) - if not course_enrollment: - return None - - enrollment = frappe.get_doc('Course Enrollment', course_enrollment) + enrollment = get_or_create_course_enrollment(course, program) if content_type == 'Quiz': return else: return enrollment.add_activity(content_type, content) @frappe.whitelist() -def evaluate_quiz(quiz_response, quiz_name, course): +def evaluate_quiz(quiz_response, quiz_name, course, program): import json student = get_current_student() @@ -211,16 +207,12 @@ def evaluate_quiz(quiz_response, quiz_name, course): return {'result': result, 'score': score, 'status': status} if student: - course_enrollment = get_enrollment("course", course, student.name) - if course_enrollment: - enrollment = frappe.get_doc('Course Enrollment', course_enrollment) - if quiz.allowed_attempt(enrollment, quiz_name): - enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) - return {'result': result, 'score': score, 'status': status} - else: - return None + enrollment = get_or_create_course_enrollment(course, program) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) + return {'result': result, 'score': score, 'status': status} else: - frappe.throw("Something went wrong. Pleae contact the administrator.") + return None @frappe.whitelist() def get_quiz(quiz_name, course): @@ -261,9 +253,17 @@ def create_student_from_current_user(): student.save(ignore_permissions=True) return student -def enroll_in_course(course_name, program_name): +def get_or_create_course_enrollment(course, program): student = get_current_student() - return student.enroll_in_course(course_name=course_name, program_enrollment=get_program_enrollment(program_name)) + course_enrollment = get_enrollment("course", course, student.name) + if not course_enrollment: + program_enrollment = get_enrollment('program', program, student.name) + if not program_enrollment: + frappe.throw("You are not enrolled in program {0}".format(program)) + return + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + else: + return frappe.get_doc('Course Enrollment', course_enrollment) def check_content_completion(content_name, content_type, enrollment_name): activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment_name, 'content_type': content_type, 'content': content_name}) diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js index f6dc4d08d54..1b520eb9f54 100644 --- a/erpnext/public/js/education/lms/quiz.js +++ b/erpnext/public/js/education/lms/quiz.js @@ -63,7 +63,8 @@ class Quiz { frappe.call('erpnext.education.utils.evaluate_quiz', { quiz_name: this.name, quiz_response: this.get_selected(), - course: this.course + course: this.course, + program: this.program }).then(res => { this.submit_btn.remove() if (!res.message) { diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index 41f27f3bc1b..acdc54d6069 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -144,6 +144,7 @@ const quiz = new Quiz(document.getElementById('quiz-wrapper'), { name: '{{ content.name }}', course: '{{ course }}', + program: '{{ program }}', quiz_exit_button: quiz_exit_button, next_url: next_url }) @@ -165,7 +166,8 @@ args: { course: "{{ course }}", content_type: "{{ content_type }}", - content: "{{ content.name }}" + content: "{{ content.name }}", + program: "{{ program }}" } } frappe.call(opts).then(res => { From 6d4c66647685aa8f0997286669fa80af5769504e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 3 Jun 2019 14:41:05 +0530 Subject: [PATCH 59/96] feat: added topic progress api --- erpnext/education/utils.py | 19 +++++++++++++++++++ erpnext/www/lms/course.html | 4 +--- erpnext/www/lms/course.py | 8 +++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 96fb4eed8b8..3abb8409147 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -239,6 +239,25 @@ def get_quiz(quiz_name, course): status, score, result = check_quiz_completion(quiz, course_enrollment) return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} +def get_student_topic_details(topic, course_name, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + student = get_current_student() + course_enrollment = get_or_create_course_enrollment(course_name, program) + progress = student.get_topic_progress(course_enrollment.name, topic) + if not progress: + return {'label':'Open', 'indicator': 'blue'} + count = sum([activity['is_complete'] for activity in progress]) + if count == 0: + return {'label':'Open', 'indicator': 'blue'} + elif count == len(progress): + return {'label':'Completed', 'indicator': 'green'} + elif count < len(progress): + return {'label':'In Progress', 'indicator': 'orange'} + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index ee3b9758cb6..199fc169e5a 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -67,9 +67,7 @@
{% if has_access %} {% else %} diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index b9aff5c5bd4..f59c28cf7e5 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -9,5 +9,11 @@ def get_context(context): course = frappe.get_doc('Course', frappe.form_dict['name']) context.program = frappe.form_dict['program'] context.course = course + context.topics = course.get_topics() - context.has_access = utils.allowed_program_access(context.program) \ No newline at end of file + context.has_access = utils.allowed_program_access(context.program) + context.progress = get_topic_progress(context.topics, course, context.program) + +def get_topic_progress(topics, course, program): + progress = {topic.name: utils.get_student_topic_details(topic, course.name, program) for topic in topics} + return progress From 8309582cca5c513349a27882e1628483602f9023 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 Jun 2019 18:20:46 +0530 Subject: [PATCH 60/96] fix: report Payment Period Based On Invoice Date not working --- .../payment_period_based_on_invoice_date.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index a7d1820850c..89e0113fc8d 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -18,22 +18,22 @@ def execute(filters=None): data = [] for d in entries: invoice = invoice_details.get(d.against_voucher) or frappe._dict() - + if d.reference_type=="Purchase Invoice": payment_amount = flt(d.debit) or -1 * flt(d.credit) else: payment_amount = flt(d.credit) or -1 * flt(d.debit) - row = [d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher, + row = [d.voucher_type, d.voucher_no, d.party_type, d.party, d.posting_date, d.against_voucher, invoice.posting_date, invoice.due_date, d.debit, d.credit, d.remarks] if d.against_voucher: - row += get_ageing_data(30, 60, 90, d.posting_date, invoice.posting_date, payment_amount) + row += get_ageing_data(30, 60, 90, 120, d.posting_date, invoice.posting_date, payment_amount) else: row += ["", "", "", "", ""] if invoice.due_date: row.append((getdate(d.posting_date) - getdate(invoice.due_date)).days or 0) - + data.append(row) return columns, data @@ -48,19 +48,19 @@ def get_columns(filters): return [ _("Payment Document") + ":: 100", _("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140", - _("Party Type") + "::100", + _("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140", _("Posting Date") + ":Date:100", _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"), - _("Invoice Posting Date") + ":Date:130", - _("Payment Due Date") + ":Date:130", - _("Debit") + ":Currency:120", + _("Invoice Posting Date") + ":Date:130", + _("Payment Due Date") + ":Date:130", + _("Debit") + ":Currency:120", _("Credit") + ":Currency:120", - _("Remarks") + "::150", + _("Remarks") + "::150", _("Age") +":Int:40", - "0-30:Currency:100", - "30-60:Currency:100", - "60-90:Currency:100", + "0-30:Currency:100", + "30-60:Currency:100", + "60-90:Currency:100", _("90-Above") + ":Currency:100", _("Delay in payment (Days)") + "::150" ] @@ -79,21 +79,21 @@ def get_conditions(filters): if filters.party: conditions.append("party=%(party)s") - + if filters.party_type: conditions.append("against_voucher_type=%(reference_type)s") filters["reference_type"] = "Sales Invoice" if filters.party_type=="Customer" else "Purchase Invoice" if filters.get("from_date"): conditions.append("posting_date >= %(from_date)s") - + if filters.get("to_date"): conditions.append("posting_date <= %(to_date)s") return "and " + " and ".join(conditions) if conditions else "" def get_entries(filters): - return frappe.db.sql("""select + return frappe.db.sql("""select voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher from `tabGL Entry` where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') {0} From a87c5459072f50a6b18b63db28417c94d3566359 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 5 Jun 2019 09:07:42 +0530 Subject: [PATCH 61/96] fix: Remove support analytics report --- erpnext/patches.txt | 3 +- .../support/page/support_analytics/README.md | 1 - .../page/support_analytics/__init__.py | 0 .../support_analytics/support_analytics.js | 106 ------------------ .../support_analytics/support_analytics.json | 20 ---- 5 files changed, 2 insertions(+), 128 deletions(-) delete mode 100644 erpnext/support/page/support_analytics/README.md delete mode 100644 erpnext/support/page/support_analytics/__init__.py delete mode 100644 erpnext/support/page/support_analytics/support_analytics.js delete mode 100644 erpnext/support/page/support_analytics/support_analytics.json diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fb1e4fc9f4a..a8fb5d6f005 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -602,4 +602,5 @@ erpnext.patches.v11_1.set_salary_details_submittable erpnext.patches.v11_1.rename_depends_on_lwp execute:frappe.delete_doc("Report", "Inactive Items") erpnext.patches.v11_1.delete_scheduling_tool -erpnext.patches.v12_0.make_custom_fields_for_bank_remittance \ No newline at end of file +erpnext.patches.v12_0.make_custom_fields_for_bank_remittance +execute:frappe.delete_doc_if_exists("Page", "support-analytics") \ No newline at end of file diff --git a/erpnext/support/page/support_analytics/README.md b/erpnext/support/page/support_analytics/README.md deleted file mode 100644 index 2cb8acd7130..00000000000 --- a/erpnext/support/page/support_analytics/README.md +++ /dev/null @@ -1 +0,0 @@ -Issue volume, performance over time. \ No newline at end of file diff --git a/erpnext/support/page/support_analytics/__init__.py b/erpnext/support/page/support_analytics/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/support/page/support_analytics/support_analytics.js b/erpnext/support/page/support_analytics/support_analytics.js deleted file mode 100644 index 4db5c73b901..00000000000 --- a/erpnext/support/page/support_analytics/support_analytics.js +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.pages['support-analytics'].on_page_load = function(wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - title: __('Support Analytics'), - single_column: true - }); - - new erpnext.SupportAnalytics(wrapper); - - - frappe.breadcrumbs.add("Support") - -} - -erpnext.SupportAnalytics = frappe.views.GridReportWithPlot.extend({ - init: function(wrapper) { - this._super({ - title: __("Support Analtyics"), - parent: $(wrapper).find('.layout-main'), - page: wrapper.page, - doctypes: ["Issue", "Fiscal Year"], - }); - }, - - filters: [ - {fieldname: "fiscal_year", fieldtype:"Select", label: __("Fiscal Year"), link:"Fiscal Year", - default_value: __("Select Fiscal Year") + "..."}, - {fieldname: "from_date", fieldtype:"Date", label: __("From Date")}, - {fieldname: "to_date", fieldtype:"Date", label: __("To Date")}, - {fieldname: "range", fieldtype:"Select", label: __("Range"), - options:["Daily", "Weekly", "Monthly", "Quarterly", "Yearly"], default_value: "Monthly"} - ], - - init_filter_values: function() { - this._super(); - this.filter_inputs.range.val('Monthly'); - }, - - setup_columns: function() { - var std_columns = [ - {id: "name", name: __("Status"), field: "name", width: 100}, - ]; - this.make_date_range_columns(); - this.columns = std_columns.concat(this.columns); - }, - - prepare_data: function() { - // add Opening, Closing, Totals rows - // if filtered by account and / or voucher - var me = this; - var total_tickets = {name:"All Tickets", "id": "all-tickets", - checked:true}; - var days_to_close = {name:"Days to Close", "id":"days-to-close", - checked:false}; - var total_closed = {}; - var hours_to_close = {name:"Hours to Close", "id":"hours-to-close", - checked:false}; - var hours_to_respond = {name:"Hours to Respond", "id":"hours-to-respond", - checked:false}; - var total_responded = {}; - - - $.each(frappe.report_dump.data["Issue"], function(i, d) { - var dateobj = frappe.datetime.str_to_obj(d.creation); - var date = d.creation.split(" ")[0]; - var col = me.column_map[date]; - if(col) { - total_tickets[col.field] = flt(total_tickets[col.field]) + 1; - if(d.status=="Closed") { - // just count - total_closed[col.field] = flt(total_closed[col.field]) + 1; - - days_to_close[col.field] = flt(days_to_close[col.field]) - + frappe.datetime.get_diff(d.resolution_date, d.creation); - - hours_to_close[col.field] = flt(hours_to_close[col.field]) - + frappe.datetime.get_hour_diff(d.resolution_date, d.creation); - - } - if (d.first_responded_on) { - total_responded[col.field] = flt(total_responded[col.field]) + 1; - - hours_to_respond[col.field] = flt(hours_to_respond[col.field]) - + frappe.datetime.get_hour_diff(d.first_responded_on, d.creation); - } - } - }); - - // make averages - $.each(this.columns, function(i, col) { - if(col.formatter==me.currency_formatter && total_tickets[col.field]) { - days_to_close[col.field] = flt(days_to_close[col.field]) / - flt(total_closed[col.field]); - hours_to_close[col.field] = flt(hours_to_close[col.field]) / - flt(total_closed[col.field]); - hours_to_respond[col.field] = flt(hours_to_respond[col.field]) / - flt(total_responded[col.field]); - } - }) - - this.data = [total_tickets, days_to_close, hours_to_close, hours_to_respond]; - } -}); diff --git a/erpnext/support/page/support_analytics/support_analytics.json b/erpnext/support/page/support_analytics/support_analytics.json deleted file mode 100644 index 93eb2d289e5..00000000000 --- a/erpnext/support/page/support_analytics/support_analytics.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "creation": "2013-01-04 15:31:45.000000", - "docstatus": 0, - "doctype": "Page", - "icon": "fa fa-bar-chart", - "idx": 1, - "modified": "2013-07-11 14:44:24.000000", - "modified_by": "Administrator", - "module": "Support", - "name": "support-analytics", - "owner": "Administrator", - "page_name": "support-analytics", - "roles": [ - { - "role": "Support Team" - } - ], - "standard": "Yes", - "title": "Support Analytics" -} \ No newline at end of file From da2e009990766e54c7dcb5b42dd7db6a5450e4f1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 5 Jun 2019 10:22:05 +0530 Subject: [PATCH 62/96] fix(payment-order): filter already created payment entries (#17843) --- erpnext/accounts/doctype/payment_order/payment_order.js | 2 +- erpnext/accounts/doctype/payment_order/regional/india.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index b1b1139637c..f2f00cebcba 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -68,7 +68,7 @@ frappe.ui.form.on('Payment Order', { docstatus: 1, bank_account: frm.doc.company_bank_account, paid_from: frm.doc.account, - status: ["=", "Initiated"], + payment_order_status: ["=", "Initiated"], } }); }, diff --git a/erpnext/accounts/doctype/payment_order/regional/india.js b/erpnext/accounts/doctype/payment_order/regional/india.js index 8300f74a04e..66d0f60c618 100644 --- a/erpnext/accounts/doctype/payment_order/regional/india.js +++ b/erpnext/accounts/doctype/payment_order/regional/india.js @@ -8,7 +8,7 @@ frappe.ui.form.on('Payment Order', { }, generate_text_and_download_file: (frm) => { return frappe.call({ - method: "erpnext.regional.india.bank_remittance_txt.generate_report", + method: "erpnext.regional.india.bank_remittance.generate_report", args: { name: frm.doc.name }, From 4cb1a1e2e373e62c24776a6158c9c59d42a94a30 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 5 Jun 2019 10:26:01 +0530 Subject: [PATCH 63/96] fix: set task weight from onboarding template (#17840) --- .../employee_boarding_activity.json | 304 ++---------------- .../employee_onboarding.js | 7 +- erpnext/hr/utils.py | 2 +- 3 files changed, 27 insertions(+), 286 deletions(-) diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json index 5ad9d060f8c..65792b42fb8 100644 --- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json @@ -1,341 +1,83 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2018-05-09 05:37:18.439763", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "activity_name", + "user", + "role", + "column_break_3", + "task", + "task_weight", + "required_for_employee_creation", + "section_break_6", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "activity_name", "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": "Activity Name", - "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 + "label": "Activity Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "user", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "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 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "role", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Role", - "length": 0, - "no_copy": 0, - "options": "Role", - "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 + "options": "Role" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_3", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "task", "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": "Task", - "length": 0, "no_copy": 1, "options": "Task", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, "fieldname": "task_weight", "fieldtype": "Float", - "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": "Task Weight", - "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 + "label": "Task Weight" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Applicable in the case of Employee Onboarding", - "fetch_if_empty": 0, "fieldname": "required_for_employee_creation", "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": "Required for Employee Creation", - "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 + "label": "Required for Employee Creation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_6", - "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 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", - "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": "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 + "label": "Description" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, "istable": 1, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-12 11:31:27.080747", + "modified": "2019-06-03 19:22:42.965762", "modified_by": "Administrator", "module": "HR", "name": "Employee Boarding Activity", - "name_case": "", "owner": "Administrator", "permissions": [], "quick_entry": 1, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js index ce235756e10..996dcdf8484 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -60,12 +60,11 @@ frappe.ui.form.on('Employee Onboarding', { }, callback: function(r) { if (r.message) { - $.each(r.message, function(i, d) { - var row = frappe.model.add_child(frm.doc, "Employee Boarding Activity", "activities"); - $.extend(row, d); + r.message.forEach((d) => { + frm.add_child("activities", d); }); + refresh_field("activities"); } - refresh_field("activities"); } }); } diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 7357fd0cc10..7c3c2cf1d49 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -85,7 +85,7 @@ class EmployeeBoardingController(Document): @frappe.whitelist() def get_onboarding_details(parent, parenttype): return frappe.get_all("Employee Boarding Activity", - fields=["activity_name", "role", "user", "required_for_employee_creation", "description"], + fields=["activity_name", "role", "user", "required_for_employee_creation", "description", "task_weight"], filters={"parent": parent, "parenttype": parenttype}, order_by= "idx") From 811bed32c1f550b8c4e8ad4934b1839950c3b340 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 5 Jun 2019 10:26:26 +0530 Subject: [PATCH 64/96] fix(naming): Limit number of docs to be renamed to 50000 per doctype (#17827) If total docs to be renamed goes over 100000 then the total number of writes exceed 200000 and check_transaction_status (database.py) raises a Validation Error This causes failure in the job being executed currently and every single job triggered afterwards. --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 4c617c727e8..c5432254445 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -281,7 +281,7 @@ def rename_gle_sle_docs(): def rename_temporarily_named_docs(doctype): """Rename temporarily named docs using autoname options""" - docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation") + docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation", limit=50000) for doc in docs_to_rename: oldname = doc.name set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc) From 570161b9789792631fc19f9312769b5bbe777725 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 13:08:53 +0530 Subject: [PATCH 65/96] feat: added course progress apis --- erpnext/education/utils.py | 36 +++++++++++++++++++++++++++++++---- erpnext/www/lms/course.html | 8 +++++++- erpnext/www/lms/program.html | 37 +++++++++++++++++++++++------------- erpnext/www/lms/program.py | 8 +++++++- 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 3abb8409147..a35cedc8b04 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -249,14 +249,42 @@ def get_student_topic_details(topic, course_name, program): course_enrollment = get_or_create_course_enrollment(course_name, program) progress = student.get_topic_progress(course_enrollment.name, topic) if not progress: - return {'label':'Open', 'indicator': 'blue'} + return None count = sum([activity['is_complete'] for activity in progress]) if count == 0: - return {'label':'Open', 'indicator': 'blue'} + return {'completed': False, 'started': False} elif count == len(progress): - return {'label':'Completed', 'indicator': 'green'} + return {'completed': True, 'started': True} elif count < len(progress): - return {'label':'In Progress', 'indicator': 'orange'} + return {'completed': False, 'started': True} + +def get_student_course_details(course, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + course_progress = [] + for course_topic in course.topics: + topic = frappe.get_doc("Topic", course_topic.topic) + progress = get_student_topic_details(topic, course.name, program) + if progress: + course_progress.append(progress) + + if course_progress: + number_of_completed_topics = sum([activity['completed'] for activity in course_progress]) + total_topics = len(course_progress) + print("course_progress", course_progress) + print("number_of_completed_topics", number_of_completed_topics) + print("total_topics", total_topics) + if number_of_completed_topics == 0: + return {'completed': False, 'started': False} + if number_of_completed_topics == total_topics: + return {'completed': True, 'started': True} + if number_of_completed_topics < total_topics: + return {'completed': False, 'started': True} + + return None def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 199fc169e5a..34158c80fe1 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -67,7 +67,13 @@
{% if has_access %} {% else %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index d364e5e1d9a..0ea2dbb4626 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -34,21 +34,32 @@ {% macro card(course, index, length) %} {% endmacro %} @@ -57,8 +68,8 @@ {{ hero(program.program_name, program.description, has_access) }}
- {% for course in program.courses %} - {{ card(frappe.get_doc("Course", course.course), loop.index, program.courses|length) }} + {% for course in courses %} + {{ card(course, loop.index, courses|length) }} {% endfor %}
diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 4c3a3fdf66b..1242336688c 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -7,10 +7,16 @@ no_cache = 1 def get_context(context): context.education_settings = frappe.get_single("Education Settings") context.program = get_program(frappe.form_dict['program']) + context.courses = [frappe.get_doc("Course", course.course) for course in context.program.courses] context.has_access = utils.allowed_program_access(frappe.form_dict['program']) + context.progress = get_course_progress(context.courses, context.program) def get_program(program_name): try: return frappe.get_doc('Program', program_name) except frappe.DoesNotExistError: - frappe.throw(_("Program {0} does not exist.".format(program_name))) \ No newline at end of file + frappe.throw(_("Program {0} does not exist.".format(program_name))) + +def get_course_progress(courses, program): + progress = {course.name: utils.get_student_course_details(course, program) for course in courses} + return progress \ No newline at end of file From 3f97fac8e10b71e06a4d835220469f5e948ac063 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 5 Jun 2019 13:20:08 +0530 Subject: [PATCH 66/96] feat: Salary rounding based on settings on salary component (#17852) --- .../salary_component/salary_component.json | 1274 +++-------------- erpnext/hr/doctype/salary_slip/salary_slip.py | 4 + .../doctype/salary_slip/test_salary_slip.py | 12 +- 3 files changed, 234 insertions(+), 1056 deletions(-) diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json index ca49ceac006..e117a20fcc2 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.json +++ b/erpnext/hr/doctype/salary_component/salary_component.json @@ -1,1092 +1,264 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:salary_component", - "beta": 0, - "creation": "2016-06-30 15:42:43.631931", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:salary_component", + "creation": "2016-06-30 15:42:43.631931", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "salary_component", + "salary_component_abbr", + "type", + "description", + "column_break_4", + "is_payable", + "depends_on_payment_days", + "is_tax_applicable", + "deduct_full_tax_on_selected_payroll_date", + "round_to_the_nearest_integer", + "statistical_component", + "do_not_include_in_total", + "disabled", + "flexible_benefits", + "is_flexible_benefit", + "max_benefit_amount", + "column_break_9", + "pay_against_benefit_claim", + "only_tax_impact", + "create_separate_payment_entry_against_benefit_claim", + "section_break_11", + "variable_based_on_taxable_salary", + "section_break_5", + "accounts", + "condition_and_formula", + "condition", + "amount", + "amount_based_on_formula", + "formula", + "column_break_28", + "help" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "salary_component", - "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": "Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "salary_component", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "salary_component_abbr", - "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": "Abbr", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "salary_component_abbr", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Abbr", + "print_width": "120px", + "reqd": 1, "width": "120px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "type", - "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": 1, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "Earning\nDeduction", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "Earning\nDeduction", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "depends_on": "eval:doc.type == \"Earning\"", - "fetch_if_empty": 0, - "fieldname": "is_tax_applicable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Tax Applicable", - "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 - }, + "default": "1", + "depends_on": "eval:doc.type == \"Earning\"", + "fieldname": "is_tax_applicable", + "fieldtype": "Check", + "label": "Is Tax Applicable" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "is_payable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Payable", - "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 - }, + "default": "1", + "fieldname": "is_payable", + "fieldtype": "Check", + "label": "Is Payable" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "depends_on_payment_days", - "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": "Depends on Payment Days", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "depends_on_payment_days", + "fieldtype": "Check", + "label": "Depends on Payment Days", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "do_not_include_in_total", - "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": "Do not include in total", - "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 - }, + "default": "0", + "fieldname": "do_not_include_in_total", + "fieldtype": "Check", + "label": "Do Not Include in Total" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "deduct_full_tax_on_selected_payroll_date", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Deduct Full Tax on Selected Payroll Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "is_tax_applicable", + "fieldname": "deduct_full_tax_on_selected_payroll_date", + "fieldtype": "Check", + "label": "Deduct Full Tax on Selected Payroll Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "disabled", - "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": "Disabled", - "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 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "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": "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 - }, + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", - "fetch_if_empty": 0, - "fieldname": "statistical_component", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Statistical Component", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", + "fieldname": "statistical_component", + "fieldtype": "Check", + "label": "Statistical Component" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1", - "fetch_if_empty": 0, - "fieldname": "flexible_benefits", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Flexible Benefits", - "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 - }, + "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1", + "fieldname": "flexible_benefits", + "fieldtype": "Section Break", + "label": "Flexible Benefits" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "is_flexible_benefit", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Flexible Benefit", - "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 - }, + "default": "0", + "fieldname": "is_flexible_benefit", + "fieldtype": "Check", + "label": "Is Flexible Benefit" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "is_flexible_benefit", - "fetch_if_empty": 0, - "fieldname": "max_benefit_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Max Benefit Amount (Yearly)", - "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 - }, + "depends_on": "is_flexible_benefit", + "fieldname": "max_benefit_amount", + "fieldtype": "Currency", + "label": "Max Benefit Amount (Yearly)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "is_flexible_benefit", - "fetch_if_empty": 0, - "fieldname": "pay_against_benefit_claim", - "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": "Pay Against Benefit Claim", - "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 - }, + "default": "0", + "depends_on": "is_flexible_benefit", + "fieldname": "pay_against_benefit_claim", + "fieldtype": "Check", + "label": "Pay Against Benefit Claim" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1", - "fetch_if_empty": 0, - "fieldname": "only_tax_impact", - "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": "Only Tax Impact (Cannot Claim But Part of Taxable Income)", - "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 - }, + "default": "0", + "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.create_separate_payment_entry_against_benefit_claim !=1", + "fieldname": "only_tax_impact", + "fieldtype": "Check", + "label": "Only Tax Impact (Cannot Claim But Part of Taxable Income)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1", - "fetch_if_empty": 0, - "fieldname": "create_separate_payment_entry_against_benefit_claim", - "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": "Create Separate Payment Entry Against Benefit Claim", - "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 - }, + "default": "0", + "depends_on": "eval:doc.is_flexible_benefit == 1 & doc.only_tax_impact !=1", + "fieldname": "create_separate_payment_entry_against_benefit_claim", + "fieldtype": "Check", + "label": "Create Separate Payment Entry Against Benefit Claim" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.type=='Deduction'", - "fetch_if_empty": 0, - "fieldname": "section_break_11", - "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 - }, + "depends_on": "eval:doc.type=='Deduction'", + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "variable_based_on_taxable_salary", - "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": "Variable Based On Taxable Salary", - "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 - }, + "default": "0", + "fieldname": "variable_based_on_taxable_salary", + "fieldtype": "Check", + "label": "Variable Based On Taxable Salary" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.statistical_component != 1", - "fetch_if_empty": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "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 - }, + "depends_on": "eval:doc.statistical_component != 1", + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Accounts" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Salary Component Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Salary Component Account" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1", - "fetch_if_empty": 0, - "fieldname": "condition_and_formula", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Condition and Formula", - "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 - }, + "collapsible": 1, + "depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1", + "fieldname": "condition_and_formula", + "fieldtype": "Section Break", + "label": "Condition and Formula" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "condition", - "fieldtype": "Code", - "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": "Condition", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "condition", + "fieldtype": "Code", + "label": "Condition" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, - "fieldname": "amount_based_on_formula", - "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": "Amount based on formula", - "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 - }, + "default": "0", + "fieldname": "amount_based_on_formula", + "fieldtype": "Check", + "label": "Amount based on formula" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.amount_based_on_formula!==0", - "fetch_if_empty": 0, - "fieldname": "formula", - "fieldtype": "Code", - "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": "Formula", - "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 - }, + "depends_on": "amount_based_on_formula", + "fieldname": "formula", + "fieldtype": "Code", + "label": "Formula" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.amount_based_on_formula!==1", - "fetch_if_empty": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.amount_based_on_formula!==1", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_28", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "help", - "fieldtype": "HTML", - "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": "Help", - "length": 0, - "no_copy": 0, - "options": "

Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condtion. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "help", + "fieldtype": "HTML", + "label": "Help", + "options": "

Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condtion. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
" + }, + { + "default": "0", + "fieldname": "round_to_the_nearest_integer", + "fieldtype": "Check", + "label": "Round to the Nearest Integer" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-flag", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-05-13 12:55:55.424370", - "modified_by": "Administrator", - "module": "HR", - "name": "Salary Component", - "name_case": "", - "owner": "Administrator", + ], + "icon": "fa fa-flag", + "modified": "2019-06-05 11:34:14.231228", + "modified_by": "Administrator", + "module": "HR", + "name": "Salary Component", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Employee", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Employee" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 6bba0c54d83..bdb2080e39b 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -619,6 +619,10 @@ class SalarySlip(TransactionBase): elif not row.amount: amount = row.default_amount + row.additional_amount + # apply rounding + if frappe.get_cached_value("Salary Component", row.salary_component, "round_to_the_nearest_integer"): + amount, additional_amount = rounded(amount), rounded(additional_amount) + return amount, additional_amount def calculate_unclaimed_taxable_benefits(self, payroll_period): diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index b333f4f49e4..16a75f473f4 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -211,7 +211,7 @@ class TestSalarySlip(unittest.TestCase): tax_paid = get_tax_paid_in_period(employee) # total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200 - annual_tax = 113567.79 + annual_tax = 113568 try: self.assertEqual(tax_paid, annual_tax) except AssertionError: @@ -250,7 +250,7 @@ class TestSalarySlip(unittest.TestCase): # total taxable income 416000, 166000 @ 5% ie. 8300 try: - self.assertEqual(tax_paid, 88607.79) + self.assertEqual(tax_paid, 88608) except AssertionError: print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") raise @@ -265,7 +265,7 @@ class TestSalarySlip(unittest.TestCase): # total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200 tax_paid = get_tax_paid_in_period(employee) try: - self.assertEqual(tax_paid, 121211.48) + self.assertEqual(tax_paid, 121211) except AssertionError: print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") raise @@ -443,7 +443,8 @@ def make_deduction_salary_component(setup=False, test_tax=False): "type": "Deduction", "amount_based_on_formula": 1, "depends_on_payment_days": 0, - "variable_based_on_taxable_salary": 1 + "variable_based_on_taxable_salary": 1, + "round_to_the_nearest_integer": 1 } ] if not test_tax: @@ -453,7 +454,8 @@ def make_deduction_salary_component(setup=False, test_tax=False): "condition": 'employment_type=="Intern"', "formula": 'base*.1', "type": "Deduction", - "amount_based_on_formula": 1 + "amount_based_on_formula": 1, + "round_to_the_nearest_integer": 1 }) if setup or test_tax: make_salary_component(data, test_tax) From 6593263df8de6b0782ea8a9c80e0d1fa79cd139c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 13:29:51 +0530 Subject: [PATCH 67/96] feat: added navigation --- erpnext/education/utils.py | 2 ++ erpnext/www/lms/course.html | 2 +- erpnext/www/lms/macros/hero.html | 9 +++++++-- erpnext/www/lms/program.html | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index a35cedc8b04..3352b515f33 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -246,6 +246,8 @@ def get_student_topic_details(topic, course_name, program): :param course_name: """ student = get_current_student() + if not student: + return None course_enrollment = get_or_create_course_enrollment(course_name, program) progress = student.get_topic_progress(course_enrollment.name, topic) if not progress: diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index 34158c80fe1..1182eb99da3 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -85,7 +85,7 @@ {% block content %}
- {{ hero(course.course_name, course.course_intro, has_access) }} + {{ hero(course.course_name, course.course_intro, has_access, {'name': 'Program', 'url': '/lms/program?program=' + program }) }}
{% for topic in topics %} diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html index 20e9d05c600..dfee93fa995 100644 --- a/erpnext/www/lms/macros/hero.html +++ b/erpnext/www/lms/macros/hero.html @@ -1,6 +1,11 @@ -{% macro hero(title, description, has_access) %} +{% macro hero(title, description, has_access, back) %}
-

{{ title }}

+ +

{{ title }}

{{ description }}

{% if frappe.session.user == 'Guest' %} diff --git a/erpnext/www/lms/program.html b/erpnext/www/lms/program.html index 0ea2dbb4626..a0e45e31fe5 100644 --- a/erpnext/www/lms/program.html +++ b/erpnext/www/lms/program.html @@ -65,7 +65,7 @@ {% block content %}

- {{ hero(program.program_name, program.description, has_access) }} + {{ hero(program.program_name, program.description, has_access, {'name': 'LMS Home', 'url': '/lms'}) }}
{% for course in courses %} From bd9abc15b5cedbfe01e9b24aa78bd8aca271f551 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 5 Jun 2019 14:17:29 +0530 Subject: [PATCH 68/96] fix(projects): Remove hardcode of expected end date for new Tasks (#17717) --- erpnext/projects/doctype/task/task.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 1f609d7910c..298efbf2363 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -26,23 +26,17 @@ frappe.ui.form.on("Task", { } } } - if(!frm.is_group){ - var doc = frm.doc; - if(doc.__islocal) { - if(!frm.doc.exp_end_date) { - frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7)); - } - } - if(!doc.__islocal) { - if(frm.perm[0].write) { - if(frm.doc.status!=="Completed" && frm.doc.status!=="Cancelled") { - frm.add_custom_button(__("Completed"), function() { - frm.set_value("status", "Completed"); + if(!frm.doc.is_group){ + if (!frm.is_new()) { + if (frm.perm[0].write) { + if (!["Closed", "Cancelled"].includes(frm.doc.status)) { + frm.add_custom_button(__("Close"), () => { + frm.set_value("status", "Closed"); frm.save(); }); } else { - frm.add_custom_button(__("Reopen"), function() { + frm.add_custom_button(__("Reopen"), () => { frm.set_value("status", "Open"); frm.save(); }); From 16b4129ede4795d8f2af09ce08e60ddbdda97a8b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:29:48 +0530 Subject: [PATCH 69/96] feat: added student profile page --- .../program_enrollment/program_enrollment.py | 23 -------- erpnext/education/utils.py | 53 +++++++++++++++--- erpnext/www/lms/course.py | 2 +- erpnext/www/lms/profile.html | 56 +++++++++++++++++++ erpnext/www/lms/profile.py | 24 ++++++++ erpnext/www/lms/program.py | 2 +- 6 files changed, 128 insertions(+), 32 deletions(-) create mode 100644 erpnext/www/lms/profile.html create mode 100644 erpnext/www/lms/profile.py diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index 22cca86fcf5..d232e472450 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -96,29 +96,6 @@ class ProgramEnrollment(Document): quiz_progress.program = self.program return quiz_progress - def get_program_progress(self): - import math - program = frappe.get_doc("Program", self.program) - program_progress = {} - progress = [] - for course in program.get_all_children(): - course_progress = lms.get_student_course_details(course.course, self.program) - is_complete = False - if course_progress['flag'] == "Completed": - is_complete = True - progress.append({'course_name': course.course_name, 'name': course.course, 'is_complete': is_complete}) - - program_progress['progress'] = progress - program_progress['name'] = self.program - program_progress['program'] = frappe.get_value("Program", self.program, 'program_name') - - try: - program_progress['percentage'] = math.ceil((sum([item['is_complete'] for item in progress] * 100)/len(progress))) - except ZeroDivisionError: - program_progress['percentage'] = 0 - - return program_progress - @frappe.whitelist() def get_program_courses(doctype, txt, searchfield, start, page_len, filters): if filters.get('program'): diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 3352b515f33..ac7294badb2 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -239,7 +239,7 @@ def get_quiz(quiz_name, course): status, score, result = check_quiz_completion(quiz, course_enrollment) return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} -def get_student_topic_details(topic, course_name, program): +def get_topic_progress(topic, course_name, program): """ Return the porgress of a course in a program as well as the content to continue from. :param topic_name: @@ -260,7 +260,7 @@ def get_student_topic_details(topic, course_name, program): elif count < len(progress): return {'completed': False, 'started': True} -def get_student_course_details(course, program): +def get_course_progress(course, program): """ Return the porgress of a course in a program as well as the content to continue from. :param topic_name: @@ -269,16 +269,14 @@ def get_student_course_details(course, program): course_progress = [] for course_topic in course.topics: topic = frappe.get_doc("Topic", course_topic.topic) - progress = get_student_topic_details(topic, course.name, program) + progress = get_topic_progress(topic, course.name, program) if progress: course_progress.append(progress) - if course_progress: number_of_completed_topics = sum([activity['completed'] for activity in course_progress]) total_topics = len(course_progress) - print("course_progress", course_progress) - print("number_of_completed_topics", number_of_completed_topics) - print("total_topics", total_topics) + if total_topics == 1: + return course_progress[0] if number_of_completed_topics == 0: return {'completed': False, 'started': False} if number_of_completed_topics == total_topics: @@ -288,6 +286,47 @@ def get_student_course_details(course, program): return None +def get_program_progress(program): + program_progress = [] + if not program.courses: + return None + for program_course in program.courses: + course = frappe.get_doc("Course", program_course.course) + progress = get_course_progress(course, program.name) + if progress: + progress['name'] = course.name + progress['course'] = course.course_name + program_progress.append(progress) + + if program_progress: + return program_progress + + return None + +def get_program_completion(program): + topics = frappe.db.sql("""select `tabcourse topic`.topic, `tabcourse topic`.parent + from `tabcourse topic`, + `tabprogram course` + where `tabcourse topic`.parent = `tabprogram course`.course + and `tabprogram course`.parent = '{0}'""".format(program.name)) + + progress = [] + for topic in topics: + topic_doc = frappe.get_doc('Topic', topic[0]) + topic_progress = get_topic_progress(topic_doc, topic[1], program.name) + if topic_progress: + progress.append(topic_progress) + + if progress: + number_of_completed_topics = sum([activity['completed'] for activity in progress if activity]) + total_topics = len(progress) + try: + return int((float(number_of_completed_topics)/total_topics)*100) + except ZeroDivisionError: + return 0 + + return 0 + def create_student_from_current_user(): user = frappe.get_doc("User", frappe.session.user) diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py index f59c28cf7e5..e7ed2e3ed6f 100644 --- a/erpnext/www/lms/course.py +++ b/erpnext/www/lms/course.py @@ -15,5 +15,5 @@ def get_context(context): context.progress = get_topic_progress(context.topics, course, context.program) def get_topic_progress(topics, course, program): - progress = {topic.name: utils.get_student_topic_details(topic, course.name, program) for topic in topics} + progress = {topic.name: utils.get_topic_progress(topic, course.name, program) for topic in topics} return progress diff --git a/erpnext/www/lms/profile.html b/erpnext/www/lms/profile.html new file mode 100644 index 00000000000..c642265b63b --- /dev/null +++ b/erpnext/www/lms/profile.html @@ -0,0 +1,56 @@ +{% extends "templates/base.html" %} +{% block title %}Profile{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} + +{% macro card(program) %} + +{% endmacro %} + +{% block content %} +
+
+ +

{{ student.first_name }} {{ student.last_name or '' }}

+

{{ student.name }}

+
+
+
+ {% for program in progress %} + {{ card(program) }} + {% endfor %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py new file mode 100644 index 00000000000..1a0decce32e --- /dev/null +++ b/erpnext/www/lms/profile.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import erpnext.education.utils as utils +import frappe + +no_cache = 1 + +def get_context(context): + context.student = utils.get_current_student() + context.progress = get_program_progress(context.student.name) + +def get_program_progress(student): + enrolled_programs = frappe.get_all("Program Enrollment", filters={'student':student}, fields=['program']) + student_progress = [] + for list_item in enrolled_programs: + program = frappe.get_doc("Program", list_item.program) + progress = utils.get_program_progress(program) + completion = utils.get_program_completion(program) + student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) + + return student_progress + + + + diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py index 1242336688c..a92ec310828 100644 --- a/erpnext/www/lms/program.py +++ b/erpnext/www/lms/program.py @@ -18,5 +18,5 @@ def get_program(program_name): frappe.throw(_("Program {0} does not exist.".format(program_name))) def get_course_progress(courses, program): - progress = {course.name: utils.get_student_course_details(course, program) for course in courses} + progress = {course.name: utils.get_course_progress(course, program) for course in courses} return progress \ No newline at end of file From bd499fcf2730584ef5a0f4d3ee7740dd96738da7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:38:12 +0530 Subject: [PATCH 70/96] chore: added session check for profile page --- erpnext/www/lms/profile.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py index 1a0decce32e..4788ea6e70b 100644 --- a/erpnext/www/lms/profile.py +++ b/erpnext/www/lms/profile.py @@ -5,7 +5,13 @@ import frappe no_cache = 1 def get_context(context): + if frappe.session.user == "Guest": + frappe.local.flags.redirect_location = '/lms' + raise frappe.Redirect + context.student = utils.get_current_student() + if not context.student: + context.student = frappe.get_doc('User', frappe.session.user) context.progress = get_program_progress(context.student.name) def get_program_progress(student): @@ -17,8 +23,4 @@ def get_program_progress(student): completion = utils.get_program_completion(program) student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion}) - return student_progress - - - - + return student_progress \ No newline at end of file From 7597baab950183fdb3ac5c58eaa8b33b85353240 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jun 2019 17:49:42 +0530 Subject: [PATCH 71/96] refactor: minor fixes --- erpnext/education/doctype/program/program.json | 5 ++--- erpnext/www/lms/content.html | 4 ++-- erpnext/www/lms/macros/card.html | 2 +- erpnext/www/lms/macros/hero.html | 2 +- erpnext/www/lms/program.html | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/education/doctype/program/program.json b/erpnext/education/doctype/program/program.json index 17a7f9d3c70..a0a2aa2e2ba 100644 --- a/erpnext/education/doctype/program/program.json +++ b/erpnext/education/doctype/program/program.json @@ -74,8 +74,7 @@ { "fieldname": "description", "fieldtype": "Small Text", - "label": "Description", - "reqd": 1 + "label": "Description" }, { "fieldname": "intro_video", @@ -113,7 +112,7 @@ "label": "Allow Self Enroll" } ], - "modified": "2019-05-29 14:42:44.693874", + "modified": "2019-06-05 17:47:26.877296", "modified_by": "Administrator", "module": "Education", "name": "Program", diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index acdc54d6069..92cfec2c8c6 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -38,9 +38,9 @@
diff --git a/erpnext/www/lms/course.html b/erpnext/www/lms/course.html index d046ac85140..381af01a7a7 100644 --- a/erpnext/www/lms/course.html +++ b/erpnext/www/lms/course.html @@ -28,6 +28,9 @@ line-height: 1; padding: 20px; } + section { + padding: 5rem 0 5rem 0; + } {% endblock %} @@ -84,7 +87,7 @@ {% endmacro %} {% block content %} -
+
{{ hero(course.course_name, course.description, has_access, {'name': 'Program', 'url': '/lms/program?program=' + program }) }}
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html index 6661e22ce33..c33aeb59d50 100644 --- a/erpnext/www/lms/index.html +++ b/erpnext/www/lms/index.html @@ -30,11 +30,15 @@ line-height: 1; padding: 20px; } + + section { + padding: 5rem 0 5rem 0; + } {% endblock %} {% block content %} -
+

{{ education_settings.portal_title }}

{{ education_settings.description }}

@@ -54,42 +58,4 @@

-
-
-
-
- -
-
Curated Courses
-
Start with a 14 day trial to get instant access to your own ERPNext Instance. Get expert support and world class hosting too.
-
-
- -
- -
-
Built by Experts
-
For self hosted users, ERPNext Support provides you the priority support and bug fix guarantee, so you can stop worrying.
-
-
- -
- -
-
Learn from the OEMs
-
ERPNext is open source and infinitely extensible. Customize it, build upon it, add your own apps built with Frappe Framework.
-
-
-
-
-
-
-
-

About ERPNext

-

ERPNext is the world's best 100% open source ERP used by over 5000 companies worldwide.

- -
-
{% endblock %} \ No newline at end of file diff --git a/erpnext/www/lms/macros/card.html b/erpnext/www/lms/macros/card.html index ffed8e9ee0f..f77b5148010 100644 --- a/erpnext/www/lms/macros/card.html +++ b/erpnext/www/lms/macros/card.html @@ -11,7 +11,7 @@ {% endif %}
{{ program.program_name }}
-
{{ program.description or '' }}
+
{{ program.description or '' }}
{% if has_access or program.intro_video%}
{% endblock %} \ No newline at end of file From 2a0483dee3f8d4c5f510126c83416b14769479ea Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 6 Jun 2019 16:02:38 +0530 Subject: [PATCH 89/96] refactor: styling cards and article --- erpnext/www/lms/content.html | 24 ++++++++++++++++++++++-- erpnext/www/lms/course.html | 12 +++++++++--- erpnext/www/lms/index.html | 10 +++++++++- erpnext/www/lms/macros/card.html | 12 ++++++++++-- erpnext/www/lms/program.html | 14 ++++++++++---- erpnext/www/lms/topic.html | 6 ++++++ 6 files changed, 66 insertions(+), 12 deletions(-) diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html index bdbacc1f3d1..9b8c45cb9b0 100644 --- a/erpnext/www/lms/content.html +++ b/erpnext/www/lms/content.html @@ -3,6 +3,26 @@ {% block head_include %}