Merge pull request #22323 from deepeshgarg007/item_tax_map
fix: Do not copy Item Tax template from SO to PO
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index d36b115..8f67858 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -11,4 +11,4 @@
- name: curl
run: |
apk add curl bash
- curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.org/repo/frappe%2Ffrappe_docker/requests
+ curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index c9bbbab..e7fa954 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -146,7 +146,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-04-29 16:09:30.025214",
+ "modified": "2020-06-17 16:09:30.025214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 7145fea..13f5cb0 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1376,6 +1376,12 @@
"read": 1,
"role": "Purchase Manager",
"write": 1
+ },
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts User"
}
],
"search_fields": "status, transaction_date, supplier,grand_total",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 1712369..813286f 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -118,7 +118,7 @@
self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
-
+
def test_add_new_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -144,7 +144,7 @@
self.assertEquals(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
-
+
def test_remove_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -185,6 +185,23 @@
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
+ def test_update_child_qty_rate_perm(self):
+ po = create_purchase_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+ frappe.set_user("Administrator")
+
def test_update_qty(self):
po = create_purchase_order()
@@ -689,7 +706,7 @@
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
-
+
def test_po_optional_blanket_order(self):
"""
Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eecb143..f54b593 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1137,8 +1137,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@@ -1157,8 +1157,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
return child_item
@@ -1190,6 +1190,26 @@
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
+ def check_permissions(doc, perm_type='create'):
+ try:
+ doc.check_permission(perm_type)
+ except:
+ action = "add" if perm_type == 'create' else "update"
+ frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
+
+ def get_new_child_item(item_row):
+ if parent_doctype == "Sales Order":
+ return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+ if parent_doctype == "Purchase Order":
+ return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+
+ def validate_quantity(child_item, d):
+ if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
+ frappe.throw(_("Cannot set quantity less than delivered quantity"))
+
+ if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
+ frappe.throw(_("Cannot set quantity less than received quantity"))
+
data = json.loads(trans_items)
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
@@ -1201,20 +1221,29 @@
new_child_flag = False
if not d.get("docname"):
new_child_flag = True
- if parent_doctype == "Sales Order":
- child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
- if parent_doctype == "Purchase Order":
- child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
+ check_permissions(parent, 'create')
+ child_item = get_new_child_item(d)
else:
+ check_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
- if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
+
+ prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
+ prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
+ prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+
+ if parent_doctype == 'Sales Order':
+ prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
+ elif parent_doctype == 'Purchase Order':
+ prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
+
+ rate_unchanged = prev_rate == new_rate
+ qty_unchanged = prev_qty == new_qty
+ conversion_factor_unchanged = prev_con_fac == new_con_fac
+ date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
+ if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
continue
- if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
- frappe.throw(_("Cannot set quantity less than delivered quantity"))
-
- if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
- frappe.throw(_("Cannot set quantity less than received quantity"))
+ validate_quantity(child_item, d)
child_item.qty = flt(d.get("qty"))
precision = child_item.precision("rate") or 2
@@ -1225,6 +1254,18 @@
else:
child_item.rate = flt(d.get("rate"))
+ if d.get("conversion_factor"):
+ if child_item.stock_uom == child_item.uom:
+ child_item.conversion_factor = 1
+ else:
+ child_item.conversion_factor = flt(d.get('conversion_factor'))
+
+ if d.get("delivery_date") and parent_doctype == 'Sales Order':
+ child_item.delivery_date = d.get('delivery_date')
+
+ if d.get("schedule_date") and parent_doctype == 'Purchase Order':
+ child_item.schedule_date = d.get('schedule_date')
+
if flt(child_item.price_list_rate):
if flt(child_item.rate) > flt(child_item.price_list_rate):
# if rate is greater than price_list_rate, set margin
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 50b17ab..1f95e00 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -102,7 +102,7 @@
frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
else:
- msg = _("The value {0} is already assigned to an exisiting Item {1}.").format(
+ msg = _("The value {0} is already assigned to an existing Item {1}.").format(
frappe.bold(attribute_value), frappe.bold(item))
msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2888c76..759c6cd 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -227,7 +227,9 @@
def check_expense_account(self, item):
if not item.get("expense_account"):
- frappe.throw(_("Expense Account not set for Item {0}. Please set an Expense Account for the item in the Items table").format(item.item_code))
+ frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
+ Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
+ title=_("Expense Account Missing"))
else:
is_expense_account = frappe.db.get_value("Account",
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index 7606565..f09d2ef 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -9,6 +9,14 @@
},
onload: function(frm) {
+ frm.set_query("academic_term", function() {
+ return {
+ "filters": {
+ "academic_year": frm.doc.academic_year
+ }
+ };
+ });
+
frm.set_query("receivable_account", function(doc) {
return {
filters: {
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.json b/erpnext/education/doctype/fee_structure/fee_structure.json
index 8ff6851..67e4637 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.json
+++ b/erpnext/education/doctype/fee_structure/fee_structure.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -11,8 +12,8 @@
"program",
"student_category",
"column_break_2",
- "academic_term",
"academic_year",
+ "academic_term",
"section_break_4",
"components",
"section_break_6",
@@ -157,7 +158,8 @@
],
"icon": "fa fa-flag",
"is_submittable": 1,
- "modified": "2019-05-26 09:04:17.765758",
+ "links": [],
+ "modified": "2020-06-16 15:34:57.295010",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Structure",
diff --git a/erpnext/education/doctype/student_admission/student_admission.json b/erpnext/education/doctype/student_admission/student_admission.json
index b3c10d4..1096888 100644
--- a/erpnext/education/doctype/student_admission/student_admission.json
+++ b/erpnext/education/doctype/student_admission/student_admission.json
@@ -1,398 +1,119 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 1,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "",
- "beta": 0,
- "creation": "2016-09-13 03:05:27.154713",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
+ "actions": [],
+ "allow_guest_to_view": 1,
+ "allow_rename": 1,
+ "creation": "2016-09-13 03:05:27.154713",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "route",
+ "column_break_3",
+ "academic_year",
+ "admission_start_date",
+ "admission_end_date",
+ "published",
+ "enable_admission_application",
+ "section_break_5",
+ "program_details",
+ "introduction"
+ ],
"fields": [
{
- "allow_bulk_edit": 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": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "route",
- "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": "Route",
- "length": 0,
- "no_copy": 1,
- "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,
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route",
+ "no_copy": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_form_route",
- "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": "Application Form Route",
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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,
- "unique": 0
- },
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Academic Year",
+ "no_copy": 1,
+ "options": "Academic Year",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "academic_year",
- "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": "Academic Year",
- "length": 0,
- "no_copy": 1,
- "options": "Academic Year",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "admission_start_date",
+ "fieldtype": "Date",
+ "label": "Admission Start Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_start_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Admission Start Date",
- "length": 0,
- "no_copy": 1,
- "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,
- "unique": 0
- },
+ "fieldname": "admission_end_date",
+ "fieldtype": "Date",
+ "label": "Admission End Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_end_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Admission End Date",
- "length": 0,
- "no_copy": 1,
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "label": "Publish on website"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "published",
- "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": "Publish on website",
- "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,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break",
+ "label": "Eligibility and Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": "Eligibility and Details",
- "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,
- "unique": 0
- },
+ "fieldname": "program_details",
+ "fieldtype": "Table",
+ "label": "Eligibility and Details",
+ "options": "Student Admission Program"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program_details",
- "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": "Eligibility and Details",
- "length": 0,
- "no_copy": 0,
- "options": "Student Admission Program",
- "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,
- "unique": 0
- },
+ "fieldname": "introduction",
+ "fieldtype": "Text Editor",
+ "label": "Introduction"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "introduction",
- "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": "Introduction",
- "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,
- "unique": 0
+ "default": "0",
+ "fieldname": "enable_admission_application",
+ "fieldtype": "Check",
+ "label": "Enable Admission Application"
}
- ],
- "has_web_view": 1,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_published_field": "published",
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-11-10 18:57:34.570376",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "has_web_view": 1,
+ "is_published_field": "published",
+ "links": [],
+ "modified": "2020-06-15 20:18:38.591626",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 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
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "route": "admissions",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "restrict_to_domain": "Education",
+ "route": "admissions",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_admission/templates/student_admission.html b/erpnext/education/doctype/student_admission/templates/student_admission.html
index 25afaca..e5a9ead 100644
--- a/erpnext/education/doctype/student_admission/templates/student_admission.html
+++ b/erpnext/education/doctype/student_admission/templates/student_admission.html
@@ -43,8 +43,8 @@
<thead>
<tr class="active">
<th style="width: 90px">Program/Std.</th>
- <th style="width: 170px">Minumum Age(DOB)</th>
- <th style="width: 170px">Maximum Age(DOB)</th>
+ <th style="width: 170px">Minumum Age</th>
+ <th style="width: 170px">Maximum Age</th>
<th style="width: 100px">Application Fee</th>
</tr>
</thead>
@@ -52,8 +52,8 @@
{% for row in program_details %}
<tr>
<td>{{ row.program }}</td>
- <td>{{ row.minimum_age }}</td>
- <td>{{ row.maximum_age }}</td>
+ <td>{{ row.min_age }}</td>
+ <td>{{ row.max_age }}</td>
<td>{{ row.application_fee }}</td>
</tr>
{% endfor %}
@@ -61,12 +61,11 @@
</table>
</div>
{% endif %}
-
- {%- if application_form_route -%}
+ {%- if doc.enable_admission_application -%}
<br>
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}?new=1'>
+ href='/student-applicant?new=1&student_admission={{doc.name}}'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/education/doctype/student_admission/test_student_admission.js b/erpnext/education/doctype/student_admission/test_student_admission.js
index ed794b2..3a0bb0b 100644
--- a/erpnext/education/doctype/student_admission/test_student_admission.js
+++ b/erpnext/education/doctype/student_admission/test_student_admission.js
@@ -11,7 +11,7 @@
{admission_start_date: '2016-04-20'},
{admission_end_date: '2016-05-31'},
{title: '2016-17 Admissions'},
- {application_form_route: 'student-applicant'},
+ {enable_admission_application: 1},
{introduction: 'Test intro'},
{program_details: [
[
@@ -28,7 +28,7 @@
assert.ok(cur_frm.doc.admission_start_date == '2016-04-20');
assert.ok(cur_frm.doc.admission_end_date == '2016-05-31');
assert.ok(cur_frm.doc.title == '2016-17 Admissions');
- assert.ok(cur_frm.doc.application_form_route == 'student-applicant');
+ assert.ok(cur_frm.doc.enable_admission_application == 1);
assert.ok(cur_frm.doc.introduction == 'Test intro');
assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected');
assert.ok(cur_frm.doc.program_details[0].application_fee == 1000);
diff --git a/erpnext/education/doctype/student_admission_program/student_admission_program.json b/erpnext/education/doctype/student_admission_program/student_admission_program.json
index 97b1bba..e9f041e 100644
--- a/erpnext/education/doctype/student_admission_program/student_admission_program.json
+++ b/erpnext/education/doctype/student_admission_program/student_admission_program.json
@@ -1,237 +1,77 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-09-15 12:59:43.207923",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-09-15 12:59:43.207923",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "program",
+ "min_age",
+ "max_age",
+ "column_break_4",
+ "application_fee",
+ "applicant_naming_series"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program",
- "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": "Program",
- "length": 0,
- "no_copy": 0,
- "options": "Program",
- "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": "program",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Program",
+ "options": "Program",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "minimum_age",
- "fieldtype": "Date",
- "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": "Minimum Age",
- "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",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maximum_age",
- "fieldtype": "Date",
- "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": "Maximum Age",
- "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": "application_fee",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Application Fee",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": "applicant_naming_series",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Naming Series (for Student Applicant)",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_fee",
- "fieldtype": "Currency",
- "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": "Application Fee",
- "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": "min_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Minimum Age",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "applicant_naming_series",
- "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": "Naming Series (for Student Applicant)",
- "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": "max_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Maximum Age",
+ "show_days": 1,
+ "show_seconds": 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": "2018-11-04 03:37:17.408427",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission Program",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-10 23:06:30.037404",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission Program",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_applicant/student_applicant.py b/erpnext/education/doctype/student_applicant/student_applicant.py
index ab94780..2113482 100644
--- a/erpnext/education/doctype/student_applicant/student_applicant.py
+++ b/erpnext/education/doctype/student_applicant/student_applicant.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import getdate
+from frappe.utils import getdate, add_years, nowdate, date_diff
class StudentApplicant(Document):
def autoname(self):
@@ -31,6 +31,7 @@
def validate(self):
self.validate_dates()
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
+
if self.student_admission and self.program and self.date_of_birth:
self.validation_from_student_admission()
@@ -48,16 +49,16 @@
frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant"))
def validation_from_student_admission(self):
+
student_admission = get_student_admission_data(self.student_admission, self.program)
- # different validation for minimum and maximum age so that either min/max can also work independently.
- if student_admission and student_admission.minimum_age and \
- getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.min_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
- if student_admission and student_admission.maximum_age and \
- getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.max_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
def on_payment_authorized(self, *args, **kwargs):
@@ -65,10 +66,12 @@
def get_student_admission_data(student_admission, program):
+
student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date,
- sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series
+ sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series
from `tabStudent Admission` sa, `tabStudent Admission Program` sap
where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1)
+
if student_admission:
return student_admission[0]
else:
diff --git a/erpnext/education/web_form/student_applicant/student_applicant.json b/erpnext/education/web_form/student_applicant/student_applicant.json
index b1ad754..1810f07 100644
--- a/erpnext/education/web_form/student_applicant/student_applicant.json
+++ b/erpnext/education/web_form/student_applicant/student_applicant.json
@@ -1,200 +1,248 @@
{
- "accept_payment": 0,
- "allow_comments": 0,
- "allow_delete": 0,
- "allow_edit": 1,
- "allow_incomplete": 0,
- "allow_multiple": 1,
- "allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
- "creation": "2016-09-22 13:10:10.792735",
- "doc_type": "Student Applicant",
- "docstatus": 0,
- "doctype": "Web Form",
- "idx": 0,
- "is_standard": 1,
- "login_required": 1,
- "max_attachment_size": 0,
- "modified": "2017-02-21 05:44:46.022738",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "student-applicant",
- "owner": "Administrator",
- "payment_button_label": "Buy Now",
- "published": 1,
- "route": "student-applicant",
- "show_sidebar": 1,
- "sidebar_items": [],
- "success_url": "/student-applicant",
- "title": "Student Applicant",
+ "accept_payment": 0,
+ "allow_comments": 0,
+ "allow_delete": 0,
+ "allow_edit": 1,
+ "allow_incomplete": 0,
+ "allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
+ "amount_based_on_field": 0,
+ "creation": "2016-09-22 13:10:10.792735",
+ "doc_type": "Student Applicant",
+ "docstatus": 0,
+ "doctype": "Web Form",
+ "idx": 0,
+ "is_standard": 1,
+ "login_required": 1,
+ "max_attachment_size": 0,
+ "modified": "2020-06-11 22:53:45.875310",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "student-applicant",
+ "owner": "Administrator",
+ "payment_button_label": "Buy Now",
+ "published": 1,
+ "route": "student-applicant",
+ "route_to_success_link": 0,
+ "show_attachments": 0,
+ "show_in_grid": 0,
+ "show_sidebar": 1,
+ "sidebar_items": [],
+ "success_url": "/student-applicant",
+ "title": "Student Applicant",
"web_form_fields": [
{
- "fieldname": "first_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "First Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "First Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Middle Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Middle Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "last_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Last Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Last Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "image",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Image",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "image",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Image",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "program",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Program",
- "max_length": 0,
- "max_value": 0,
- "options": "Program",
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Program",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Program",
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "academic_year",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Academic Year",
- "max_length": 0,
- "max_value": 0,
- "options": "Academic Year",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Academic Year",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Academic Year",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "date_of_birth",
- "fieldtype": "Date",
- "hidden": 0,
- "label": "Date of Birth",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "date_of_birth",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "label": "Date of Birth",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "blood_group",
- "fieldtype": "Select",
- "hidden": 0,
- "label": "Blood Group",
- "max_length": 0,
- "max_value": 0,
- "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "blood_group",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "label": "Blood Group",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_email_id",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Email ID",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_email_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Email ID",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_mobile_number",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Mobile Number",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_mobile_number",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Mobile Number",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "default": "INDIAN",
- "fieldname": "nationality",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Nationality",
- "max_length": 0,
- "max_value": 0,
- "options": "",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "default": "INDIAN",
+ "fieldname": "nationality",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Nationality",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_1",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 1",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_1",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 1",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_2",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 2",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_2",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 2",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "pincode",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Pincode",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "pincode",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Pincode",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "guardians",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Guardians",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Guardian",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "guardians",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "label": "Guardians",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Guardian",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "siblings",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Siblings",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Sibling",
- "read_only": 0,
- "reqd": 0
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "siblings",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "label": "Siblings",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Sibling",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_admission",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Student Admission",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Admission",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
}
]
}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 1b0c9f6..6dedaa8 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -73,10 +73,16 @@
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"})
- shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"})
- rename_address(billing_address, customer)
- rename_address(shipping_address, customer)
+ for address_type in ("Billing", "Shipping",):
+ try:
+ address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
+ rename_address(address, customer)
+ except (
+ frappe.DoesNotExistError,
+ frappe.DuplicateEntryError,
+ frappe.ValidationError,
+ ):
+ pass
else:
create_address(raw_billing_data, customer, "Billing")
create_address(raw_shipping_data, customer, "Shipping")
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 64c3b2d..25ffd28 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -8,6 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_request_session
+from requests.exceptions import HTTPError
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.erpnext_integrations.utils import get_webhook_address
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
@@ -29,19 +30,24 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
- d = session.post(url, data=json.dumps({
+ res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
"format": "json"
}
}), headers=get_header(self))
- d.raise_for_status()
- self.update_webhook_table(method, d.json())
+ res.raise_for_status()
+ self.update_webhook_table(method, res.json())
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
make_shopify_log(status="Warning", exception=e, rollback=True)
@@ -50,13 +56,18 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
deleted_webhooks.append(d)
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
- frappe.log_error(message=frappe.get_traceback(), title=e)
+ frappe.log_error(message=e, title='Shopify Webhooks Issue')
for d in deleted_webhooks:
self.remove(d)
@@ -125,4 +136,3 @@
}
create_custom_fields(custom_fields)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index bde1011..f9f0bb3 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 0c13b6a..3dc7c1e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -70,6 +70,7 @@
if frappe.db.get_value('Item', item, 'is_stock_item'):
frappe.throw(_(msg))
+@frappe.whitelist()
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ['name', 'practitioner_name', 'mobile_phone']
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 9d7cdc2..742cc8e 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -238,6 +238,9 @@
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"on_trash": "erpnext.regional.check_deletion_permission"
},
+ "Purchase Invoice": {
+ "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
+ },
"Payment Entry": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
"on_trash": "erpnext.regional.check_deletion_permission"
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 2543eec..7d31a1c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -119,6 +119,7 @@
"description": d.description,
"time_in_mins": d.time_in_mins,
"batch_size": d.batch_size,
+ "operating_cost": d.operating_cost,
"idx": d.idx
})
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 3ca851d..0350e2c 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -78,6 +78,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_hour_rate",
"fieldtype": "Currency",
"label": "Base Hour Rate(Company Currency)",
@@ -87,6 +88,7 @@
},
{
"default": "5",
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"label": "Operating Cost(Company Currency)",
@@ -108,12 +110,12 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-16 22:35:55.374037",
- "modified_by": "govindsmenokee@gmail.com",
+ "modified": "2020-06-16 17:01:11.128420",
+ "modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 6cfd0ba..d7589fa 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -44,7 +44,6 @@
name: d.workstation
},
callback: function (data) {
- frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
frm.events.calculate_operating_cost(frm, d);
}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 279c453..d74dc24 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -699,3 +699,4 @@
erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
erpnext.patches.v13_0.update_sla_enhancements
erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v13_0.update_issue_metrics
diff --git a/erpnext/patches/v13_0/update_issue_metrics.py b/erpnext/patches/v13_0/update_issue_metrics.py
new file mode 100644
index 0000000..6d76235
--- /dev/null
+++ b/erpnext/patches/v13_0/update_issue_metrics.py
@@ -0,0 +1,33 @@
+from __future__ import unicode_literals
+import frappe
+
+from frappe.core.doctype.communication.communication import set_avg_response_time
+from erpnext.support.doctype.issue.issue import set_resolution_time, set_user_resolution_time
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doctype('Issue')
+
+ count = 0
+ for parent in frappe.get_all('Issue', order_by='creation desc'):
+ parent_doc = frappe.get_doc('Issue', parent.name)
+
+ communication = frappe.get_all('Communication', filters={
+ 'reference_doctype': 'Issue',
+ 'reference_name': parent.name,
+ 'communication_medium': 'Email',
+ 'sent_or_received': 'Sent'
+ }, order_by = 'creation asc', limit=1)
+
+ if communication:
+ communication_doc = frappe.get_doc('Communication', communication[0].name)
+ set_avg_response_time(parent_doc, communication_doc)
+
+ if parent_doc.status in ['Closed', 'Resolved']:
+ set_resolution_time(parent_doc)
+ set_user_resolution_time(parent_doc)
+
+ # commit after every 100 records
+ count += 1
+ if count % 100 == 0:
+ frappe.db.commit()
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 2cd79b5..bcab0d8 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -487,7 +487,14 @@
fieldtype: 'Date',
fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
in_list_view: 1,
- label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date")
+ label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
+ reqd: 1
+ })
+ fields.splice(3, 0, {
+ fieldtype: 'Float',
+ fieldname: "conversion_factor",
+ in_list_view: 1,
+ label: __("Conversion Factor")
})
}
@@ -536,6 +543,7 @@
"item_code": d.item_code,
"delivery_date": d.delivery_date,
"schedule_date": d.schedule_date,
+ "conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
});
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 735b417..617e916 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -81,4 +81,10 @@
.place-order-container {
text-align: right;
+}
+
+.kb-card {
+ .card-body > .card-title {
+ line-height: 1.3;
+ }
}
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
index ded3a51..cf2644e 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
@@ -2,4 +2,13 @@
// For license information, please see license.txt
frappe.ui.form.on('Quality Procedure', {
+ refresh: function(frm) {
+ frm.set_query("procedure","processes", (frm) =>{
+ return {
+ filters: {
+ name: ["not in", [frm.parent_quality_procedure, frm.name]]
+ }
+ };
+ });
+ }
});
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
index 6df116c..b3c0d94 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_rename": 1,
"autoname": "format:PRC-{quality_procedure_name}",
"creation": "2018-10-06 00:06:29.756804",
"doctype": "DocType",
@@ -72,7 +73,7 @@
],
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:09:29.371627",
+ "modified": "2020-06-17 17:25:03.434953",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure",
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
index d29710d..1952e57 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
@@ -11,18 +11,18 @@
nsm_parent_field = 'parent_quality_procedure'
def before_save(self):
- for process in self.processes:
- if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- if doc.parent_quality_procedure:
- frappe.throw(_("{0} already has a Parent Procedure {1}.").format(process.procedure, doc.parent_quality_procedure))
- self.is_group = 1
+ self.check_for_incorrect_child()
def on_update(self):
self.set_parent()
def after_insert(self):
self.set_parent()
+ #if Child is Added through Tree View.
+ if self.parent_quality_procedure:
+ parent_quality_procedure = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
+ parent_quality_procedure.append("processes", {"procedure": self.name})
+ parent_quality_procedure.save()
def on_trash(self):
if self.parent_quality_procedure:
@@ -43,10 +43,20 @@
def set_parent(self):
for process in self.processes:
+ # Set parent for only those children who don't have a parent
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if not parent_quality_procedure and process.procedure:
+ frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
+
+ def check_for_incorrect_child(self):
+ for process in self.processes:
if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- doc.parent_quality_procedure = self.name
- doc.save(ignore_permissions=True)
+ # Check if any child process belongs to another parent.
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if parent_quality_procedure and parent_quality_procedure != self.name:
+ frappe.throw(_("{0} already has a Parent Procedure {1}.".format(frappe.bold(process.procedure), frappe.bold(parent_quality_procedure))),
+ title=_("Invalid Child Procedure"))
+ self.is_group = 1
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
index 6df6f65..ef48ab6 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
@@ -16,6 +16,7 @@
},
],
breadcrumb: "Setup",
+ disable_add_node: true,
root_label: "All Quality Procedures",
get_tree_root: false,
menu_items: [
diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
index 0a67fa5..3925dbb 100644
--- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
+++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
@@ -1,6 +1,8 @@
{
+ "actions": [],
"creation": "2019-05-26 00:10:00.248885",
"doctype": "DocType",
+ "editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"process_description",
@@ -23,7 +25,8 @@
}
],
"istable": 1,
- "modified": "2019-05-26 22:05:49.007189",
+ "links": [],
+ "modified": "2020-06-17 15:44:38.937915",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure Process",
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 35f9cf6..888b2da 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -52,7 +52,7 @@
<td class="disabled"></td>
<td class="disabled"></td>
<tr>
- <td>(d) {{__("Inward Supplies(liable to reverse charge")}}</td>
+ <td>(d) {{__("Inward Supplies(liable to reverse charge)")}}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.txval, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.iamt, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.camt, 2) }}</td>
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 9e7a023..619734f 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -158,7 +158,7 @@
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
- self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y")
+ self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
self.set_itc_details(itc_details)
@@ -192,31 +192,27 @@
for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"])
- gst_category = "Registered Regular"
+ gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC':
reverse_charge = "Y"
+ itc_type = 'All Other ITC'
+ gst_category = ['Unregistered', 'Overseas']
else:
reverse_charge = "N"
for account_head in self.account_heads:
+ for category in gst_category:
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
- d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2)
- d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2)
- d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2)
- d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2)
-
- net_itc["iamt"] += flt(d["iamt"], 2)
- net_itc["camt"] += flt(d["camt"], 2)
- net_itc["samt"] += flt(d["samt"], 2)
- net_itc["csamt"] += flt(d["csamt"], 2)
+ for key in ['iamt', 'camt', 'samt', 'csamt']:
+ net_itc[key] += flt(d[key], 2)
for account_head in self.account_heads:
itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
- itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2)
- itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2)
- itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2)
- itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2)
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
@@ -274,17 +270,16 @@
""" #nosec
.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
- def get_itc_details(self, reverse_charge='N'):
-
+ def get_itc_details(self):
itc_amount = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
- where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
+ where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
and s.company_gstin = %s
group by t.account_head, s.gst_category, s.eligibility_for_itc
""",
- (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
itc_details = {}
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 3085a31..9fe29eb 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -9,6 +9,8 @@
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.regional.india import number_state_mapping
from six import string_types
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.utils import get_account_currency
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -658,5 +660,53 @@
elif val:
gst_accounts[val] = acc
-
return gst_accounts
+
+def make_reverse_charge_entries(doc, method):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'India':
+ return
+
+ if doc.reverse_charge == 'Y':
+ gl_entries = []
+ gst_accounts = get_gst_accounts(doc.company)
+ gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
+
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+ account_currency = get_account_currency(tax.account_head)
+
+ gl_entries.append(doc.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "posting_date": doc.posting_date,
+ "against": doc.supplier,
+ "credit": tax.base_tax_amount_after_discount_amount,
+ "credits_in_account_currency": tax.base_tax_amount_after_discount_amount \
+ if account_currency==doc.company_currency \
+ else tax.tax_amount_after_discount_amount
+ }, account_currency, item=tax)
+ )
+
+ gl_entries.append(doc.get_gl_dict(
+ {
+ "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to,
+ "cost_center": doc.cost_center,
+ "posting_date": doc.posting_date,
+ "party_type": 'Supplier',
+ "party": doc.supplier,
+ "against": tax.account_head,
+ "debit": tax.base_tax_amount_after_discount_amount,
+ "debit_in_account_currency": tax.base_tax_amount_after_discount_amount \
+ if account_currency==doc.company_currency \
+ else tax.tax_amount_after_discount_amount
+ }, account_currency, item=doc)
+ )
+
+ make_gl_entries(gl_entries)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index ac3bc20..682dfed 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -339,6 +339,7 @@
return lp_details
+@frappe.whitelist()
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from erpnext.controllers.queries import get_fields
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b8b0d40..74e742f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -400,6 +400,23 @@
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ def test_update_child_qty_rate_perm(self):
+ so = make_sales_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ frappe.set_user("Administrator")
+
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index e593499..eff17f8 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -769,22 +769,23 @@
"label": "Against Blanket Order"
},
{
- "fieldname": "bom_no",
- "fieldtype": "Link",
- "label": "BOM No",
- "no_copy": 1,
- "options": "BOM",
- "print_hide": 1
- },
- {
- "fieldname": "manufacturing_section_section",
- "fieldtype": "Section Break",
- "label": "Manufacturing Section"
- }
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "manufacturing_section_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacturing Section"
+ }
],
"idx": 1,
"istable": 1,
- "modified": "2020-05-15 18:13:43.006493",
+ "links": [],
+ "modified": "2020-05-29 20:54:32.309460",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index e78d0ff..f15f63d 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -5,7 +5,7 @@
import calendar
import frappe
from frappe import _
-from frappe.utils import cint, cstr
+from frappe.utils import cint, cstr, getdate
def execute(filters=None):
common_columns = [
@@ -160,7 +160,7 @@
return columns, data, None, None, None, 1
def get_customer_stats(filters, tree_view=False):
- """ Calculates number of new and repeated customers. """
+ """ Calculates number of new and repeated customers and revenue. """
company_condition = ''
if filters.get('company'):
company_condition = ' and company=%(company)s'
@@ -174,14 +174,14 @@
filters, as_dict=1):
key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+ new_or_repeat = 'new' if si.customer not in customers else 'repeat'
customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
- if not si.customer in customers:
- customers_in[key]['new'][0] += 1
- customers_in[key]['new'][1] += si.base_grand_total
+ # if filters.from_date <= si.posting_date.strftime('%Y-%m-%d'):
+ if getdate(filters.from_date) <= getdate(si.posting_date):
+ customers_in[key][new_or_repeat][0] += 1
+ customers_in[key][new_or_repeat][1] += si.base_grand_total
+ if new_or_repeat == 'new':
customers.append(si.customer)
- else:
- customers_in[key]['repeat'][0] += 1
- customers_in[key]['repeat'][1] += si.base_grand_total
return customers_in
diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json
index 1c1b0c3..5d3d3ac 100644
--- a/erpnext/support/doctype/support_settings/support_settings.json
+++ b/erpnext/support/doctype/support_settings/support_settings.json
@@ -1,5 +1,5 @@
{
- "actions": [],
+ "actions": "",
"creation": "2017-02-17 13:07:35.686409",
"doctype": "DocType",
"editable_grid": 1,
@@ -22,6 +22,10 @@
"post_description_key",
"post_route_key",
"post_route_string",
+ "greetings_section_section",
+ "greeting_title",
+ "column_break_19",
+ "greeting_subtitle",
"search_apis_sb",
"search_apis"
],
@@ -127,11 +131,40 @@
"fieldname": "allow_resetting_service_level_agreement",
"fieldtype": "Check",
"label": "Allow Resetting Service Level Agreement"
+ },
+ {
+ "default": "We're here to help",
+ "fieldname": "greeting_title",
+ "fieldtype": "Data",
+ "label": "Greeting Title",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "Browse help topics",
+ "fieldname": "greeting_subtitle",
+ "fieldtype": "Data",
+ "label": "Greeting Subtitle",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "greetings_section_section",
+ "fieldtype": "Section Break",
+ "label": "Greetings Section",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"issingle": 1,
"links": [],
- "modified": "2020-06-05 17:56:17.491684",
+ "modified": "2020-06-11 13:08:38.473616",
"modified_by": "Administrator",
"module": "Support",
"name": "Support Settings",
diff --git a/erpnext/templates/generators/student_admission.html b/erpnext/templates/generators/student_admission.html
index ae70df8..8b15344 100644
--- a/erpnext/templates/generators/student_admission.html
+++ b/erpnext/templates/generators/student_admission.html
@@ -14,12 +14,12 @@
{%- if introduction -%}
<div>{{ introduction }}</div>
-{% endif %}
+{% endif %}
-{%- if application_form_route -%}
+{%- if doc.enable_admission_application -%}
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}'>
+ href='/student-applicant'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/www/support/__init__.py b/erpnext/www/support/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/www/support/__init__.py
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
new file mode 100644
index 0000000..93da503
--- /dev/null
+++ b/erpnext/www/support/index.html
@@ -0,0 +1,58 @@
+{% extends "templates/web.html" %}
+
+{% block content %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <div class="hero-content">
+ <h1 class="hero-title">{{ greeting_title or _("We're here to help!") }}</h1>
+ {% if greeting_subtitle %}
+ <p class="hero-subtitle">{{ greeting_subtitle }}</p>
+ {% endif %}
+ </div>
+ </div>
+</section>
+
+{% if favorite_article_list %}
+<section class="section section-padding-top section-padding-bottom bg-light">
+ <div class='container'>
+ <h2>{{ _("Frequently Read Articles") }}</h2>
+ <div class="row">
+ {% for favorite_article in favorite_article_list %}
+ <div class="mt-4 col-12 col-sm-6 col-lg-4">
+ <div class="card card-md h-100 kb-card">
+ <div class="card-body">
+ <h6 class="card-subtitle mb-2 text-uppercase small text-muted">
+ {{ favorite_article['category'] }}</h6>
+ <h3 class="card-title">{{ favorite_article['title'] }}</h3>
+ <p class="card-text">{{ favorite_article['description'] }}</p>
+ </div>
+ <a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% if help_article_list %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <h2>{{ _("Help Articles") }}</h2>
+ <div class="row">
+ {% for item in help_article_list %}
+ <div class="mt-5 col-12 col-sm-6 col-lg-4">
+ <h5>{{ item['category'].name }}</h5>
+ <div>
+ {% for article in item['articles'] %}
+ <a href="{{ article.route }}" class="mt-2 d-block">{{ article.title }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
new file mode 100644
index 0000000..5d26743
--- /dev/null
+++ b/erpnext/www/support/index.py
@@ -0,0 +1,74 @@
+from __future__ import unicode_literals
+import frappe
+
+def get_context(context):
+ context.no_cache = 1
+ context.align_greeting = ''
+ setting = frappe.get_doc("Support Settings")
+
+ context.greeting_title = setting.greeting_title
+ context.greeting_subtitle = setting.greeting_subtitle
+
+ # Support content
+ favorite_articles = get_favorite_articles_by_page_view()
+ if len(favorite_articles) < 6:
+ name_list = []
+ if favorite_articles:
+ for article in favorite_articles:
+ name_list.append(article.name)
+ for record in (frappe.get_all("Help Article",
+ fields=["title", "content", "route", "category"],
+ filters={"name": ['not in', tuple(name_list)], "published": 1},
+ order_by="creation desc", limit=(6-len(favorite_articles)))):
+ favorite_articles.append(record)
+
+ context.favorite_article_list = get_favorite_articles(favorite_articles)
+ context.help_article_list = get_help_article_list()
+
+def get_favorite_articles_by_page_view():
+ return frappe.db.sql(
+ """
+ SELECT
+ t1.name as name,
+ t1.title as title,
+ t1.content as content,
+ t1.route as route,
+ t1.category as category,
+ count(t1.route) as count
+ FROM `tabHelp Article` AS t1
+ INNER JOIN
+ `tabWeb Page View` AS t2
+ ON t1.route = t2.path
+ WHERE t1.published = 1
+ GROUP BY route
+ ORDER BY count DESC
+ LIMIT 6;
+ """, as_dict=True)
+
+def get_favorite_articles(favorite_articles):
+ favorite_article_list=[]
+ for article in favorite_articles:
+ description = frappe.utils.strip_html(article.content)
+ if len(description) > 120:
+ description = description[:120] + '...'
+ favorite_article_dict = {
+ 'title': article.title,
+ 'description': description,
+ 'route': article.route,
+ 'category': article.category,
+ }
+ favorite_article_list.append(favorite_article_dict)
+ return favorite_article_list
+
+def get_help_article_list():
+ help_article_list=[]
+ category_list = frappe.get_all("Help Category", fields="name")
+ for category in category_list:
+ help_articles = frappe.get_all("Help Article", fields="*", filters={"category": category.name, "published": 1}, order_by="modified desc", limit=5)
+ if help_articles:
+ help_aricles_per_caetgory = {
+ 'category': category,
+ 'articles': help_articles,
+ }
+ help_article_list.append(help_aricles_per_caetgory)
+ return help_article_list
\ No newline at end of file