Website: Product Configurator and Bootstrap 4 (#15965)
- Refactored Homepage with customisable Hero Section - New Homepage Section to add content on Homepage as cards or using Custom HTML - Products page at "/all-products" with customisable filters - Item Configure dialog to find an Item Variant filtered by attribute values - Contact Us dialog on Item page - Customisable Item page content using the Website Content field
This commit is contained in:
parent
f060831cce
commit
5f8b358fd4
@ -11,6 +11,16 @@ def get_data():
|
|||||||
"name": "Homepage",
|
"name": "Homepage",
|
||||||
"description": _("Settings for website homepage"),
|
"description": _("Settings for website homepage"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Homepage Section",
|
||||||
|
"description": _("Add cards or custom sections on homepage"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "doctype",
|
||||||
|
"name": "Products Settings",
|
||||||
|
"description": _("Settings for website product listing"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "doctype",
|
"type": "doctype",
|
||||||
"name": "Shopping Cart Settings",
|
"name": "Shopping Cart Settings",
|
||||||
|
@ -22,7 +22,8 @@ web_include_css = "assets/css/erpnext-web.css"
|
|||||||
|
|
||||||
doctype_js = {
|
doctype_js = {
|
||||||
"Communication": "public/js/communication.js",
|
"Communication": "public/js/communication.js",
|
||||||
"Event": "public/js/event.js"
|
"Event": "public/js/event.js",
|
||||||
|
"Website Theme": "public/js/website_theme.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
welcome_email = "erpnext.setup.utils.welcome_email"
|
welcome_email = "erpnext.setup.utils.welcome_email"
|
||||||
|
@ -53,3 +53,26 @@ class JobOpening(WebsiteGenerator):
|
|||||||
def get_list_context(context):
|
def get_list_context(context):
|
||||||
context.title = _("Jobs")
|
context.title = _("Jobs")
|
||||||
context.introduction = _('Current Job Openings')
|
context.introduction = _('Current Job Openings')
|
||||||
|
context.get_list = get_job_openings
|
||||||
|
|
||||||
|
def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
|
||||||
|
fields = ['name', 'status', 'job_title', 'description']
|
||||||
|
|
||||||
|
filters = filters or {}
|
||||||
|
filters.update({
|
||||||
|
'status': 'Open'
|
||||||
|
})
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
filters.update({
|
||||||
|
'job_title': ['like', '%{0}%'.format(txt)],
|
||||||
|
'description': ['like', '%{0}%'.format(txt)]
|
||||||
|
})
|
||||||
|
|
||||||
|
return frappe.get_all(doctype,
|
||||||
|
filters,
|
||||||
|
fields,
|
||||||
|
start=limit_start,
|
||||||
|
page_length=limit_page_length,
|
||||||
|
order_by=order_by
|
||||||
|
)
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<div class="my-5">
|
||||||
|
<h3>{{ doc.job_title }}</h3>
|
||||||
|
<p>{{ doc.description }}</p>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-primary"
|
||||||
|
href="/job_application?new=1&job_title={{ doc.name }}">
|
||||||
|
{{ _("Apply Now") }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -577,6 +577,7 @@ erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
|
|||||||
erpnext.patches.v11_0.update_delivery_trip_status
|
erpnext.patches.v11_0.update_delivery_trip_status
|
||||||
erpnext.patches.v11_0.set_missing_gst_hsn_code
|
erpnext.patches.v11_0.set_missing_gst_hsn_code
|
||||||
erpnext.patches.v11_0.rename_bom_wo_fields
|
erpnext.patches.v11_0.rename_bom_wo_fields
|
||||||
|
erpnext.patches.v12_0.set_default_homepage_type
|
||||||
erpnext.patches.v11_0.rename_additional_salary_component_additional_salary
|
erpnext.patches.v11_0.rename_additional_salary_component_additional_salary
|
||||||
erpnext.patches.v11_0.renamed_from_to_fields_in_project
|
erpnext.patches.v11_0.renamed_from_to_fields_in_project
|
||||||
erpnext.patches.v11_0.add_permissions_in_gst_settings
|
erpnext.patches.v11_0.add_permissions_in_gst_settings
|
||||||
@ -584,5 +585,4 @@ erpnext.patches.v11_1.setup_guardian_role
|
|||||||
execute:frappe.delete_doc('DocType', 'Notification Control')
|
execute:frappe.delete_doc('DocType', 'Notification Control')
|
||||||
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
|
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
|
||||||
erpnext.patches.v12_0.set_task_status
|
erpnext.patches.v12_0.set_task_status
|
||||||
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
|
|
||||||
erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019
|
erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.sql('''
|
||||||
|
UPDATE `tabItem Variant Attribute` t1
|
||||||
|
INNER JOIN `tabItem` t2 ON t2.name = t1.parent
|
||||||
|
SET t1.variant_of = t2.variant_of
|
||||||
|
''')
|
4
erpnext/patches/v12_0/set_default_homepage_type.py
Normal file
4
erpnext/patches/v12_0/set_default_homepage_type.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default')
|
@ -11,7 +11,12 @@ frappe.ui.form.on('Homepage', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
frm.add_custom_button(__('Set Meta Tags'), () => {
|
||||||
|
frappe.utils.set_meta_tag('home');
|
||||||
|
});
|
||||||
|
frm.add_custom_button(__('Customize Homepage Sections'), () => {
|
||||||
|
frappe.set_route('List', 'Homepage Section', 'List');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
"autoname": "",
|
"autoname": "",
|
||||||
@ -10,18 +12,24 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 0,
|
"editable_grid": 0,
|
||||||
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 0,
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -31,24 +39,63 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"fieldname": "title",
|
"columns": 0,
|
||||||
"fieldtype": "Data",
|
"fieldname": "hero_section_based_on",
|
||||||
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"label": "TItle",
|
"in_standard_filter": 0,
|
||||||
|
"label": "Hero Section Based On",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Default\nSlideshow\nHomepage Section",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"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,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
@ -56,16 +103,88 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
|
"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,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"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": "Hero Section",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.hero_section_based_on === 'Default'",
|
||||||
"description": "Company Tagline for website homepage",
|
"description": "Company Tagline for website homepage",
|
||||||
"fieldname": "tag_line",
|
"fieldname": "tag_line",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -73,7 +192,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 0,
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Tag Line",
|
"label": "Tag Line",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -82,16 +203,22 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.hero_section_based_on === 'Default'",
|
||||||
"description": "Company Description for website homepage",
|
"description": "Company Description for website homepage",
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
@ -99,7 +226,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 0,
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -108,23 +237,133 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.hero_section_based_on === 'Default'",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.hero_section_based_on === 'Slideshow'",
|
||||||
|
"description": "",
|
||||||
|
"fieldname": "slideshow",
|
||||||
|
"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": "Homepage Slideshow",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Website Slideshow",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.hero_section_based_on === 'Homepage Section'",
|
||||||
|
"fieldname": "hero_section",
|
||||||
|
"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": "Homepage Section",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Homepage Section",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
"fieldname": "products_section",
|
"fieldname": "products_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Products",
|
"label": "Products",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -133,16 +372,21 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"default": "/products",
|
"default": "/products",
|
||||||
"fieldname": "products_url",
|
"fieldname": "products_url",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -150,7 +394,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "URL for \"All Products\"",
|
"label": "URL for \"All Products\"",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -159,16 +405,21 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"description": "Products to be shown on website homepage",
|
"description": "Products to be shown on website homepage",
|
||||||
"fieldname": "products",
|
"fieldname": "products",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
@ -176,7 +427,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Products",
|
"label": "Products",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -186,14 +439,17 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": "40px"
|
"width": "40px"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
@ -203,7 +459,7 @@
|
|||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2016-08-29 01:28:00.961623",
|
"modified": "2019-03-02 23:12:59.676202",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Portal",
|
"module": "Portal",
|
||||||
"name": "Homepage",
|
"name": "Homepage",
|
||||||
@ -212,7 +468,6 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
@ -232,7 +487,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
@ -254,8 +508,11 @@
|
|||||||
"quick_entry": 0,
|
"quick_entry": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "company",
|
"title_field": "company",
|
||||||
"track_seen": 0
|
"track_changes": 1,
|
||||||
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
@ -9,8 +9,6 @@ from frappe.website.utils import delete_page_cache
|
|||||||
|
|
||||||
class Homepage(Document):
|
class Homepage(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.products:
|
|
||||||
self.setup_items()
|
|
||||||
if not self.description:
|
if not self.description:
|
||||||
self.description = frappe._("This is an example website auto-generated from ERPNext")
|
self.description = frappe._("This is an example website auto-generated from ERPNext")
|
||||||
delete_page_cache('home')
|
delete_page_cache('home')
|
||||||
|
19
erpnext/portal/doctype/homepage/test_homepage.py
Normal file
19
erpnext/portal/doctype/homepage/test_homepage.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from frappe.tests.test_website import set_request
|
||||||
|
from frappe.website.render import render
|
||||||
|
|
||||||
|
class TestHomepage(unittest.TestCase):
|
||||||
|
def test_homepage_load(self):
|
||||||
|
set_request(method='GET', path='home')
|
||||||
|
response = render()
|
||||||
|
|
||||||
|
self.assertEquals(response.status_code, 200)
|
||||||
|
|
||||||
|
html = frappe.safe_decode(response.get_data())
|
||||||
|
self.assertTrue('<section class="hero-section' in html)
|
0
erpnext/portal/doctype/homepage_section/__init__.py
Normal file
0
erpnext/portal/doctype/homepage_section/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Homepage Section', {
|
||||||
|
|
||||||
|
});
|
336
erpnext/portal/doctype/homepage_section/homepage_section.json
Normal file
336
erpnext/portal/doctype/homepage_section/homepage_section.json
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "Prompt",
|
||||||
|
"beta": 0,
|
||||||
|
"creation": "2019-02-10 19:42:35.809238",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "section_based_on",
|
||||||
|
"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": "Section Based On",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Cards\nCustom HTML",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"collapsible_depends_on": "",
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.section_based_on === 'Cards'",
|
||||||
|
"fieldname": "section_cards_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": "Section Cards",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
|
"fieldname": "section_cards",
|
||||||
|
"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": "Section Cards",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Homepage Section Card",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"collapsible_depends_on": "",
|
||||||
|
"columns": 0,
|
||||||
|
"default": "3",
|
||||||
|
"depends_on": "",
|
||||||
|
"description": "Number of columns for this section. 3 cards will be shown per row if you select 3 columns.",
|
||||||
|
"fieldname": "no_of_columns",
|
||||||
|
"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": "Number of Columns",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "1\n2\n3\n4\n6",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"collapsible_depends_on": "",
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.section_based_on === 'Custom HTML'",
|
||||||
|
"fieldname": "custom_html_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": "Custom HTML",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "",
|
||||||
|
"description": "Use this field to render any custom HTML in the section.",
|
||||||
|
"fieldname": "section_html",
|
||||||
|
"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": "Section HTML",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "HTML",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "section_break_7",
|
||||||
|
"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": "",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"description": "Order in which sections should appear. 0 is first, 1 is second and so on.",
|
||||||
|
"fieldname": "section_order",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"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": "Section Order",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"idx": 0,
|
||||||
|
"image_view": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 0,
|
||||||
|
"max_attachments": 0,
|
||||||
|
"modified": "2019-03-04 23:52:31.290468",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Portal",
|
||||||
|
"name": "Homepage Section",
|
||||||
|
"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": "System Manager",
|
||||||
|
"set_user_permissions": 0,
|
||||||
|
"share": 1,
|
||||||
|
"submit": 0,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 0,
|
||||||
|
"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
|
||||||
|
}
|
12
erpnext/portal/doctype/homepage_section/homepage_section.py
Normal file
12
erpnext/portal/doctype/homepage_section/homepage_section.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
class HomepageSection(Document):
|
||||||
|
@property
|
||||||
|
def column_value(self):
|
||||||
|
return cint(12 / cint(self.no_of_columns or 3))
|
@ -0,0 +1,76 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from frappe.tests.test_website import set_request
|
||||||
|
from frappe.website.render import render
|
||||||
|
|
||||||
|
class TestHomepageSection(unittest.TestCase):
|
||||||
|
def test_homepage_section_card(self):
|
||||||
|
try:
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Homepage Section',
|
||||||
|
'name': 'Card Section',
|
||||||
|
'section_based_on': 'Cards',
|
||||||
|
'section_cards': [
|
||||||
|
{'title': 'Card 1', 'subtitle': 'Subtitle 1', 'content': 'This is test card 1', 'route': '/card-1'},
|
||||||
|
{'title': 'Card 2', 'subtitle': 'Subtitle 2', 'content': 'This is test card 2', 'image': 'test.jpg'},
|
||||||
|
],
|
||||||
|
'no_of_columns': 3
|
||||||
|
}).insert()
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
set_request(method='GET', path='home')
|
||||||
|
response = render()
|
||||||
|
|
||||||
|
self.assertEquals(response.status_code, 200)
|
||||||
|
|
||||||
|
html = frappe.safe_decode(response.get_data())
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
sections = soup.find('main').find_all('section')
|
||||||
|
self.assertEqual(len(sections), 3)
|
||||||
|
|
||||||
|
homepage_section = sections[2]
|
||||||
|
self.assertEqual(homepage_section.h3.text, 'Card Section')
|
||||||
|
|
||||||
|
cards = homepage_section.find_all(class_="card")
|
||||||
|
|
||||||
|
self.assertEqual(len(cards), 2)
|
||||||
|
self.assertEqual(cards[0].h5.text, 'Card 1')
|
||||||
|
self.assertEqual(cards[0].a['href'], '/card-1')
|
||||||
|
self.assertEqual(cards[1].p.text, 'Subtitle 2')
|
||||||
|
self.assertEqual(cards[1].find(class_='website-image-lazy')['data-src'], 'test.jpg')
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def test_homepage_section_custom_html(self):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Homepage Section',
|
||||||
|
'name': 'Custom HTML Section',
|
||||||
|
'section_based_on': 'Custom HTML',
|
||||||
|
'section_html': '<div class="custom-section">My custom html</div>',
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
set_request(method='GET', path='home')
|
||||||
|
response = render()
|
||||||
|
|
||||||
|
self.assertEquals(response.status_code, 200)
|
||||||
|
|
||||||
|
html = frappe.safe_decode(response.get_data())
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
sections = soup.find('main').find_all(class_='custom-section')
|
||||||
|
self.assertEqual(len(sections), 1)
|
||||||
|
|
||||||
|
homepage_section = sections[0]
|
||||||
|
self.assertEqual(homepage_section.text, 'My custom html')
|
||||||
|
|
||||||
|
# cleanup
|
||||||
|
frappe.db.rollback()
|
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"beta": 0,
|
||||||
|
"creation": "2019-02-10 19:39:02.734686",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"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": 1,
|
||||||
|
"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": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "subtitle",
|
||||||
|
"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": "Subtitle",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "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": "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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "content",
|
||||||
|
"fieldtype": "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": "Content",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "route",
|
||||||
|
"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": "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": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"idx": 0,
|
||||||
|
"image_view": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 1,
|
||||||
|
"max_attachments": 0,
|
||||||
|
"modified": "2019-02-10 20:11:41.040716",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Portal",
|
||||||
|
"name": "Homepage Section Card",
|
||||||
|
"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
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class HomepageSectionCard(Document):
|
||||||
|
pass
|
@ -3,6 +3,17 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Products Settings', {
|
frappe.ui.form.on('Products Settings', {
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
frappe.model.with_doctype('Item', () => {
|
||||||
|
const item_meta = frappe.get_meta('Item');
|
||||||
|
|
||||||
|
const valid_fields = item_meta.fields.filter(
|
||||||
|
df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
|
||||||
|
).map(df => ({ label: df.label, value: df.fieldname }));
|
||||||
|
|
||||||
|
const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
|
||||||
|
field.fieldtype = 'Select';
|
||||||
|
field.options = valid_fields;
|
||||||
|
frm.fields_dict.filter_fields.grid.refresh();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,255 +1,389 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_events_in_timeline": 0,
|
||||||
"allow_import": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_rename": 0,
|
"allow_import": 0,
|
||||||
"beta": 0,
|
"allow_rename": 0,
|
||||||
"creation": "2016-04-22 09:11:55.272398",
|
"beta": 0,
|
||||||
"custom": 0,
|
"creation": "2016-04-22 09:11:55.272398",
|
||||||
"docstatus": 0,
|
"custom": 0,
|
||||||
"doctype": "DocType",
|
"docstatus": 0,
|
||||||
"document_type": "",
|
"doctype": "DocType",
|
||||||
"editable_grid": 0,
|
"document_type": "",
|
||||||
"engine": "InnoDB",
|
"editable_grid": 0,
|
||||||
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"description": "If checked, the Home page will be the default Item Group for the website",
|
"description": "If checked, the Home page will be the default Item Group for the website",
|
||||||
"fieldname": "home_page_is_products",
|
"fieldname": "home_page_is_products",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Home Page is Products",
|
"label": "Home Page is Products",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "products_as_list",
|
"fieldname": "show_availability_status",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Show Products as a List",
|
"label": "Show Availability Status",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "show_availability_status",
|
"fieldname": "section_break_5",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
"in_standard_filter": 0,
|
"in_standard_filter": 0,
|
||||||
"label": "Show Availability Status",
|
"label": "Product Page",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"fieldname": "section_break_5",
|
"default": "6",
|
||||||
"fieldtype": "Section Break",
|
"fieldname": "products_per_page",
|
||||||
"hidden": 0,
|
"fieldtype": "Int",
|
||||||
"ignore_user_permissions": 0,
|
"hidden": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_user_permissions": 0,
|
||||||
"in_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_global_search": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 0,
|
"in_global_search": 0,
|
||||||
"in_standard_filter": 0,
|
"in_list_view": 0,
|
||||||
"length": 0,
|
"in_standard_filter": 0,
|
||||||
"no_copy": 0,
|
"label": "Products per Page",
|
||||||
"permlevel": 0,
|
"length": 0,
|
||||||
"precision": "",
|
"no_copy": 0,
|
||||||
"print_hide": 0,
|
"options": "",
|
||||||
"print_hide_if_no_value": 0,
|
"permlevel": 0,
|
||||||
"read_only": 0,
|
"precision": "",
|
||||||
"remember_last_selected_value": 0,
|
"print_hide": 0,
|
||||||
"report_hide": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"reqd": 0,
|
"read_only": 0,
|
||||||
"search_index": 0,
|
"remember_last_selected_value": 0,
|
||||||
"set_only_once": 0,
|
"report_hide": 0,
|
||||||
"translatable": 0,
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "6",
|
"fieldname": "enable_field_filters",
|
||||||
"fieldname": "products_per_page",
|
"fieldtype": "Check",
|
||||||
"fieldtype": "Int",
|
"hidden": 0,
|
||||||
"hidden": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_xss_filter": 0,
|
||||||
"ignore_xss_filter": 0,
|
"in_filter": 0,
|
||||||
"in_filter": 0,
|
"in_global_search": 0,
|
||||||
"in_global_search": 0,
|
"in_list_view": 0,
|
||||||
"in_list_view": 0,
|
"in_standard_filter": 0,
|
||||||
"in_standard_filter": 0,
|
"label": "Enable Field Filters",
|
||||||
"label": "Products per Page",
|
"length": 0,
|
||||||
"length": 0,
|
"no_copy": 0,
|
||||||
"no_copy": 0,
|
"permlevel": 0,
|
||||||
"options": "",
|
"precision": "",
|
||||||
"permlevel": 0,
|
"print_hide": 0,
|
||||||
"precision": "",
|
"print_hide_if_no_value": 0,
|
||||||
"print_hide": 0,
|
"read_only": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"read_only": 0,
|
"report_hide": 0,
|
||||||
"remember_last_selected_value": 0,
|
"reqd": 0,
|
||||||
"report_hide": 0,
|
"search_index": 0,
|
||||||
"reqd": 0,
|
"set_only_once": 0,
|
||||||
"search_index": 0,
|
"translatable": 0,
|
||||||
"set_only_once": 0,
|
"unique": 0
|
||||||
"translatable": 0,
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "enable_field_filters",
|
||||||
|
"fieldname": "filter_fields",
|
||||||
|
"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": "Item Fields",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Website Filter Field",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "enable_attribute_filters",
|
||||||
|
"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": "Enable Attribute Filters",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "enable_attribute_filters",
|
||||||
|
"fieldname": "filter_attributes",
|
||||||
|
"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": "Attributes",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Website Attribute",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "hide_variants",
|
||||||
|
"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": "Hide Variants",
|
||||||
|
"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
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"image_view": 0,
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-08-14 17:59:58.473100",
|
"modified": "2019-03-07 19:18:31.822309",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Portal",
|
"module": "Portal",
|
||||||
"name": "Products Settings",
|
"name": "Products Settings",
|
||||||
"name_case": "",
|
"name_case": "",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
"cancel": 0,
|
||||||
"cancel": 0,
|
"create": 1,
|
||||||
"create": 1,
|
"delete": 1,
|
||||||
"delete": 1,
|
"email": 1,
|
||||||
"email": 1,
|
"export": 0,
|
||||||
"export": 0,
|
"if_owner": 0,
|
||||||
"if_owner": 0,
|
"import": 0,
|
||||||
"import": 0,
|
"permlevel": 0,
|
||||||
"permlevel": 0,
|
"print": 1,
|
||||||
"print": 1,
|
"read": 1,
|
||||||
"read": 1,
|
"report": 0,
|
||||||
"report": 0,
|
"role": "Website Manager",
|
||||||
"role": "Website Manager",
|
"set_user_permissions": 0,
|
||||||
"set_user_permissions": 0,
|
"share": 1,
|
||||||
"share": 1,
|
"submit": 0,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
"show_name_in_global_search": 0,
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
@ -5,6 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class ProductsSettings(Document):
|
class ProductsSettings(Document):
|
||||||
@ -14,6 +15,26 @@ class ProductsSettings(Document):
|
|||||||
website_settings.home_page = 'products'
|
website_settings.home_page = 'products'
|
||||||
website_settings.save()
|
website_settings.save()
|
||||||
|
|
||||||
|
self.validate_field_filters()
|
||||||
|
self.validate_attribute_filters()
|
||||||
|
|
||||||
|
def validate_field_filters(self):
|
||||||
|
if not (self.enable_field_filters and self.filter_fields): return
|
||||||
|
|
||||||
|
item_meta = frappe.get_meta('Item')
|
||||||
|
valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ['Link', 'Table MultiSelect']]
|
||||||
|
|
||||||
|
for f in self.filter_fields:
|
||||||
|
if f.fieldname not in valid_fields:
|
||||||
|
frappe.throw(_('Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type "Link" or "Table MultiSelect"').format(f.idx, f.fieldname))
|
||||||
|
|
||||||
|
def validate_attribute_filters(self):
|
||||||
|
if not (self.enable_attribute_filters and self.filter_attributes): return
|
||||||
|
|
||||||
|
# if attribute filters are enabled, hide_variants should be disabled
|
||||||
|
self.hide_variants = 0
|
||||||
|
|
||||||
|
|
||||||
def home_page_is_products(doc, method):
|
def home_page_is_products(doc, method):
|
||||||
'''Called on saving Website Settings'''
|
'''Called on saving Website Settings'''
|
||||||
home_page_is_products = cint(frappe.db.get_single_value('Products Settings', 'home_page_is_products'))
|
home_page_is_products = cint(frappe.db.get_single_value('Products Settings', 'home_page_is_products'))
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"beta": 0,
|
||||||
|
"creation": "2019-01-01 13:04:54.479079",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "attribute",
|
||||||
|
"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": "Attribute",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Item Attribute",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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-01-01 13:04:59.715572",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Portal",
|
||||||
|
"name": "Website Attribute",
|
||||||
|
"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
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class WebsiteAttribute(Document):
|
||||||
|
pass
|
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"beta": 0,
|
||||||
|
"creation": "2018-12-31 17:06:08.716134",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "fieldname",
|
||||||
|
"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": "Fieldname",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"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": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"idx": 0,
|
||||||
|
"image_view": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 1,
|
||||||
|
"max_attachments": 0,
|
||||||
|
"modified": "2019-01-01 18:26:11.550380",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Portal",
|
||||||
|
"name": "Website Filter Field",
|
||||||
|
"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
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class WebsiteFilterField(Document):
|
||||||
|
pass
|
0
erpnext/portal/product_configurator/__init__.py
Normal file
0
erpnext/portal/product_configurator/__init__.py
Normal file
94
erpnext/portal/product_configurator/item_variants_cache.py
Normal file
94
erpnext/portal/product_configurator/item_variants_cache.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
class ItemVariantsCacheManager:
|
||||||
|
def __init__(self, item_code):
|
||||||
|
self.item_code = item_code
|
||||||
|
|
||||||
|
def get_item_variants_data(self):
|
||||||
|
val = frappe.cache().hget('item_variants_data', self.item_code)
|
||||||
|
|
||||||
|
if not val:
|
||||||
|
self.build_cache()
|
||||||
|
|
||||||
|
return frappe.cache().hget('item_variants_data', self.item_code)
|
||||||
|
|
||||||
|
|
||||||
|
def get_attribute_value_item_map(self):
|
||||||
|
val = frappe.cache().hget('attribute_value_item_map', self.item_code)
|
||||||
|
|
||||||
|
if not val:
|
||||||
|
self.build_cache()
|
||||||
|
|
||||||
|
return frappe.cache().hget('attribute_value_item_map', self.item_code)
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_attribute_value_map(self):
|
||||||
|
val = frappe.cache().hget('item_attribute_value_map', self.item_code)
|
||||||
|
|
||||||
|
if not val:
|
||||||
|
self.build_cache()
|
||||||
|
|
||||||
|
return frappe.cache().hget('item_attribute_value_map', self.item_code)
|
||||||
|
|
||||||
|
|
||||||
|
def get_optional_attributes(self):
|
||||||
|
val = frappe.cache().hget('optional_attributes', self.item_code)
|
||||||
|
|
||||||
|
if not val:
|
||||||
|
self.build_cache()
|
||||||
|
|
||||||
|
return frappe.cache().hget('optional_attributes', self.item_code)
|
||||||
|
|
||||||
|
|
||||||
|
def build_cache(self):
|
||||||
|
parent_item_code = self.item_code
|
||||||
|
|
||||||
|
attributes = [a.attribute for a in frappe.db.get_all('Item Variant Attribute',
|
||||||
|
{'parent': parent_item_code}, ['attribute'], order_by='idx asc')
|
||||||
|
]
|
||||||
|
|
||||||
|
item_variants_data = frappe.db.get_all('Item Variant Attribute',
|
||||||
|
{'variant_of': parent_item_code}, ['parent', 'attribute', 'attribute_value'],
|
||||||
|
order_by='parent',
|
||||||
|
as_list=1
|
||||||
|
)
|
||||||
|
|
||||||
|
attribute_value_item_map = frappe._dict({})
|
||||||
|
item_attribute_value_map = frappe._dict({})
|
||||||
|
|
||||||
|
for row in item_variants_data:
|
||||||
|
item_code, attribute, attribute_value = row
|
||||||
|
# (attr, value) => [item1, item2]
|
||||||
|
attribute_value_item_map.setdefault((attribute, attribute_value), []).append(item_code)
|
||||||
|
# item => {attr1: value1, attr2: value2}
|
||||||
|
item_attribute_value_map.setdefault(item_code, {})[attribute] = attribute_value
|
||||||
|
|
||||||
|
optional_attributes = set()
|
||||||
|
for item_code, attr_dict in item_attribute_value_map.items():
|
||||||
|
for attribute in attributes:
|
||||||
|
if attribute not in attr_dict:
|
||||||
|
optional_attributes.add(attribute)
|
||||||
|
|
||||||
|
frappe.cache().hset('attribute_value_item_map', parent_item_code, attribute_value_item_map)
|
||||||
|
frappe.cache().hset('item_attribute_value_map', parent_item_code, item_attribute_value_map)
|
||||||
|
frappe.cache().hset('item_variants_data', parent_item_code, item_variants_data)
|
||||||
|
frappe.cache().hset('optional_attributes', parent_item_code, optional_attributes)
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
keys = ['attribute_value_item_map', 'item_attribute_value_map', 'item_variants_data', 'optional_attributes']
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
frappe.cache().hdel(key, self.item_code)
|
||||||
|
|
||||||
|
|
||||||
|
def build_cache(item_code):
|
||||||
|
frappe.cache().hset('item_cache_build_in_progress', item_code, 1)
|
||||||
|
print('ItemVariantsCacheManager: Building cache for', item_code)
|
||||||
|
i = ItemVariantsCacheManager(item_code)
|
||||||
|
i.build_cache()
|
||||||
|
frappe.cache().hset('item_cache_build_in_progress', item_code, 0)
|
||||||
|
|
||||||
|
def enqueue_build_cache(item_code):
|
||||||
|
if frappe.cache().hget('item_cache_build_in_progress', item_code):
|
||||||
|
return
|
||||||
|
frappe.enqueue(build_cache, item_code=item_code, queue='short')
|
@ -0,0 +1,84 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import frappe, unittest
|
||||||
|
from frappe.tests.test_website import set_request, get_html_for_route
|
||||||
|
from frappe.website.render import render
|
||||||
|
from erpnext.portal.product_configurator.utils import get_products_for_website
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item_variant
|
||||||
|
|
||||||
|
test_dependencies = ["Item"]
|
||||||
|
|
||||||
|
class TestProductConfigurator(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_variant_item()
|
||||||
|
|
||||||
|
def test_product_list(self):
|
||||||
|
template_items = frappe.get_all('Item', {'show_in_website': 1})
|
||||||
|
variant_items = frappe.get_all('Item', {'show_variant_in_website': 1})
|
||||||
|
|
||||||
|
products_settings = frappe.get_doc('Products Settings')
|
||||||
|
products_settings.enable_field_filters = 1
|
||||||
|
products_settings.append('filter_fields', {'fieldname': 'item_group'})
|
||||||
|
products_settings.append('filter_fields', {'fieldname': 'stock_uom'})
|
||||||
|
products_settings.save()
|
||||||
|
|
||||||
|
html = get_html_for_route('all-products')
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
products_list = soup.find(class_='products-list')
|
||||||
|
items = products_list.find_all(class_='card')
|
||||||
|
self.assertEqual(len(items), len(template_items + variant_items))
|
||||||
|
|
||||||
|
items_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_in_website': 1})
|
||||||
|
variants_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_variant_in_website': 1})
|
||||||
|
|
||||||
|
# mock query params
|
||||||
|
frappe.form_dict = frappe._dict({
|
||||||
|
'field_filters': '{"item_group":["_Test Item Group Desktops"]}'
|
||||||
|
})
|
||||||
|
html = get_html_for_route('all-products')
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
products_list = soup.find(class_='products-list')
|
||||||
|
items = products_list.find_all(class_='card')
|
||||||
|
self.assertEqual(len(items), len(items_with_item_group + variants_with_item_group))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_products_for_website(self):
|
||||||
|
items = get_products_for_website(attribute_filters={
|
||||||
|
'Test Size': ['Medium']
|
||||||
|
})
|
||||||
|
self.assertEqual(len(items), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def create_variant_item(self):
|
||||||
|
if not frappe.db.exists('Item', '_Test Variant Item 1'):
|
||||||
|
frappe.get_doc({
|
||||||
|
"description": "_Test Variant Item 12",
|
||||||
|
"doctype": "Item",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"variant_of": "_Test Variant Item",
|
||||||
|
"item_code": "_Test Variant Item 1",
|
||||||
|
"item_group": "_Test Item Group",
|
||||||
|
"item_name": "_Test Variant Item 1",
|
||||||
|
"stock_uom": "_Test UOM",
|
||||||
|
"item_defaults": [{
|
||||||
|
"company": "_Test Company",
|
||||||
|
"default_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
|
"buying_cost_center": "_Test Cost Center - _TC",
|
||||||
|
"selling_cost_center": "_Test Cost Center - _TC",
|
||||||
|
"income_account": "Sales - _TC"
|
||||||
|
}],
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"attribute": "Test Size",
|
||||||
|
"attribute_value": "Medium"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"show_variant_in_website": 1
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
402
erpnext/portal/product_configurator/utils.py
Normal file
402
erpnext/portal/product_configurator/utils.py
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
import frappe
|
||||||
|
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||||
|
|
||||||
|
def get_field_filter_data():
|
||||||
|
product_settings = get_product_settings()
|
||||||
|
filter_fields = [row.fieldname for row in product_settings.filter_fields]
|
||||||
|
|
||||||
|
meta = frappe.get_meta('Item')
|
||||||
|
fields = [df for df in meta.fields if df.fieldname in filter_fields]
|
||||||
|
|
||||||
|
filter_data = []
|
||||||
|
for f in fields:
|
||||||
|
doctype = f.get_link_doctype()
|
||||||
|
|
||||||
|
# apply enable/disable filter
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
filters = {}
|
||||||
|
if meta.has_field('enabled'):
|
||||||
|
filters['enabled'] = 1
|
||||||
|
if meta.has_field('disabled'):
|
||||||
|
filters['disabled'] = 0
|
||||||
|
|
||||||
|
values = [d.name for d in frappe.get_all(doctype, filters)]
|
||||||
|
filter_data.append([f, values])
|
||||||
|
|
||||||
|
return filter_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_attribute_filter_data():
|
||||||
|
product_settings = get_product_settings()
|
||||||
|
attributes = [row.attribute for row in product_settings.filter_attributes]
|
||||||
|
attribute_docs = [
|
||||||
|
frappe.get_doc('Item Attribute', attribute) for attribute in attributes
|
||||||
|
]
|
||||||
|
|
||||||
|
# mark attribute values as checked if they are present in the request url
|
||||||
|
if frappe.form_dict:
|
||||||
|
for attr in attribute_docs:
|
||||||
|
if attr.name in frappe.form_dict:
|
||||||
|
value = frappe.form_dict[attr.name]
|
||||||
|
if value:
|
||||||
|
enabled_values = value.split(',')
|
||||||
|
else:
|
||||||
|
enabled_values = []
|
||||||
|
|
||||||
|
for v in enabled_values:
|
||||||
|
for item_attribute_row in attr.item_attribute_values:
|
||||||
|
if v == item_attribute_row.attribute_value:
|
||||||
|
item_attribute_row.checked = True
|
||||||
|
|
||||||
|
return attribute_docs
|
||||||
|
|
||||||
|
|
||||||
|
def get_products_for_website(field_filters=None, attribute_filters=None, search=None):
|
||||||
|
|
||||||
|
if attribute_filters:
|
||||||
|
item_codes = get_item_codes_by_attributes(attribute_filters)
|
||||||
|
items_by_attributes = get_items([['name', 'in', item_codes]])
|
||||||
|
|
||||||
|
if field_filters:
|
||||||
|
items_by_fields = get_items_by_fields(field_filters)
|
||||||
|
|
||||||
|
if attribute_filters and not field_filters:
|
||||||
|
return items_by_attributes
|
||||||
|
|
||||||
|
if field_filters and not attribute_filters:
|
||||||
|
return items_by_fields
|
||||||
|
|
||||||
|
if field_filters and attribute_filters:
|
||||||
|
items_intersection = []
|
||||||
|
item_codes_in_attribute = [item.name for item in items_by_attributes]
|
||||||
|
|
||||||
|
for item in items_by_fields:
|
||||||
|
if item.name in item_codes_in_attribute:
|
||||||
|
items_intersection.append(item)
|
||||||
|
|
||||||
|
return items_intersection
|
||||||
|
|
||||||
|
if search:
|
||||||
|
return get_items(search=search)
|
||||||
|
|
||||||
|
return get_items()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_products_html_for_website(field_filters=None, attribute_filters=None):
|
||||||
|
field_filters = frappe.parse_json(field_filters)
|
||||||
|
attribute_filters = frappe.parse_json(attribute_filters)
|
||||||
|
|
||||||
|
items = get_products_for_website(field_filters, attribute_filters)
|
||||||
|
html = ''.join(get_html_for_items(items))
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for attribute, values in attribute_filters.items():
|
||||||
|
attribute_values = values
|
||||||
|
|
||||||
|
if not attribute_values: continue
|
||||||
|
|
||||||
|
wheres = []
|
||||||
|
query_values = []
|
||||||
|
for attribute_value in attribute_values:
|
||||||
|
wheres.append('( attribute = %s and attribute_value = %s )')
|
||||||
|
query_values += [attribute, attribute_value]
|
||||||
|
|
||||||
|
attribute_query = ' or '.join(wheres)
|
||||||
|
|
||||||
|
if template_item_code:
|
||||||
|
variant_of_query = 'AND t2.variant_of = %s'
|
||||||
|
query_values.append(template_item_code)
|
||||||
|
else:
|
||||||
|
variant_of_query = ''
|
||||||
|
|
||||||
|
query = '''
|
||||||
|
SELECT
|
||||||
|
t1.parent
|
||||||
|
FROM
|
||||||
|
`tabItem Variant Attribute` t1
|
||||||
|
WHERE
|
||||||
|
1 = 1
|
||||||
|
AND (
|
||||||
|
{attribute_query}
|
||||||
|
)
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
`tabItem` t2
|
||||||
|
WHERE
|
||||||
|
t2.name = t1.parent
|
||||||
|
{variant_of_query}
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
t1.parent
|
||||||
|
ORDER BY
|
||||||
|
NULL
|
||||||
|
'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
|
||||||
|
|
||||||
|
item_codes = set([r[0] for r in frappe.db.sql(query, query_values)])
|
||||||
|
items.append(item_codes)
|
||||||
|
|
||||||
|
res = list(set.intersection(*items))
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_attributes_and_values(item_code):
|
||||||
|
'''Build a list of attributes and their possible values.
|
||||||
|
This will ignore the values upon selection of which there cannot exist one item.
|
||||||
|
'''
|
||||||
|
item_cache = ItemVariantsCacheManager(item_code)
|
||||||
|
item_variants_data = item_cache.get_item_variants_data()
|
||||||
|
|
||||||
|
attributes = get_item_attributes(item_code)
|
||||||
|
attribute_list = [a.attribute for a in attributes]
|
||||||
|
|
||||||
|
valid_options = {}
|
||||||
|
for item_code, attribute, attribute_value in item_variants_data:
|
||||||
|
if attribute in attribute_list:
|
||||||
|
valid_options.setdefault(attribute, set()).add(attribute_value)
|
||||||
|
|
||||||
|
for attr in attributes:
|
||||||
|
attr['values'] = valid_options.get(attr.attribute, [])
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_next_attribute_and_values(item_code, selected_attributes):
|
||||||
|
'''Find the count of Items that match the selected attributes.
|
||||||
|
Also, find the attribute values that are not applicable for further searching.
|
||||||
|
If less than equal to 10 items are found, return item_codes of those items.
|
||||||
|
If one item is matched exactly, return item_code of that item.
|
||||||
|
'''
|
||||||
|
selected_attributes = frappe.parse_json(selected_attributes)
|
||||||
|
|
||||||
|
item_cache = ItemVariantsCacheManager(item_code)
|
||||||
|
item_variants_data = item_cache.get_item_variants_data()
|
||||||
|
|
||||||
|
attributes = get_item_attributes(item_code)
|
||||||
|
attribute_list = [a.attribute for a in attributes]
|
||||||
|
filtered_items = get_items_with_selected_attributes(item_code, selected_attributes)
|
||||||
|
|
||||||
|
next_attribute = None
|
||||||
|
|
||||||
|
for attribute in attribute_list:
|
||||||
|
if attribute not in selected_attributes:
|
||||||
|
next_attribute = attribute
|
||||||
|
break
|
||||||
|
|
||||||
|
valid_options_for_attributes = frappe._dict({})
|
||||||
|
|
||||||
|
for a in attribute_list:
|
||||||
|
valid_options_for_attributes[a] = set()
|
||||||
|
|
||||||
|
selected_attribute = selected_attributes.get(a, None)
|
||||||
|
if selected_attribute:
|
||||||
|
# already selected attribute values are valid options
|
||||||
|
valid_options_for_attributes[a].add(selected_attribute)
|
||||||
|
|
||||||
|
for row in item_variants_data:
|
||||||
|
item_code, attribute, attribute_value = row
|
||||||
|
if item_code in filtered_items and attribute not in selected_attributes and attribute in attribute_list:
|
||||||
|
valid_options_for_attributes[attribute].add(attribute_value)
|
||||||
|
|
||||||
|
optional_attributes = item_cache.get_optional_attributes()
|
||||||
|
exact_match = []
|
||||||
|
# search for exact match if all selected attributes are required attributes
|
||||||
|
if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
|
||||||
|
item_attribute_value_map = item_cache.get_item_attribute_value_map()
|
||||||
|
for item_code, attr_dict in item_attribute_value_map.items():
|
||||||
|
if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()):
|
||||||
|
exact_match.append(item_code)
|
||||||
|
|
||||||
|
filtered_items_count = len(filtered_items)
|
||||||
|
|
||||||
|
# get product info if exact match
|
||||||
|
from erpnext.shopping_cart.product_info import get_product_info_for_website
|
||||||
|
if exact_match:
|
||||||
|
data = get_product_info_for_website(exact_match[0])
|
||||||
|
product_info = data.product_info
|
||||||
|
if not data.cart_settings.show_price:
|
||||||
|
product_info = None
|
||||||
|
else:
|
||||||
|
product_info = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'next_attribute': next_attribute,
|
||||||
|
'valid_options_for_attributes': valid_options_for_attributes,
|
||||||
|
'filtered_items_count': filtered_items_count,
|
||||||
|
'filtered_items': filtered_items if filtered_items_count < 10 else [],
|
||||||
|
'exact_match': exact_match,
|
||||||
|
'product_info': product_info
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_items_with_selected_attributes(item_code, selected_attributes):
|
||||||
|
item_cache = ItemVariantsCacheManager(item_code)
|
||||||
|
attribute_value_item_map = item_cache.get_attribute_value_item_map()
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for attribute, value in selected_attributes.items():
|
||||||
|
items.append(set(attribute_value_item_map[(attribute, value)]))
|
||||||
|
|
||||||
|
return set.intersection(*items)
|
||||||
|
|
||||||
|
|
||||||
|
def get_items_by_fields(field_filters):
|
||||||
|
meta = frappe.get_meta('Item')
|
||||||
|
filters = []
|
||||||
|
for fieldname, values in field_filters.items():
|
||||||
|
if not values: continue
|
||||||
|
|
||||||
|
_doctype = 'Item'
|
||||||
|
_fieldname = fieldname
|
||||||
|
|
||||||
|
df = meta.get_field(fieldname)
|
||||||
|
if df.fieldtype == 'Table MultiSelect':
|
||||||
|
child_doctype = df.options
|
||||||
|
child_meta = frappe.get_meta(child_doctype)
|
||||||
|
fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 })
|
||||||
|
if fields:
|
||||||
|
_doctype = child_doctype
|
||||||
|
_fieldname = fields[0].fieldname
|
||||||
|
|
||||||
|
if len(values) == 1:
|
||||||
|
filters.append([_doctype, _fieldname, '=', values[0]])
|
||||||
|
else:
|
||||||
|
filters.append([_doctype, _fieldname, 'in', values])
|
||||||
|
|
||||||
|
return get_items(filters)
|
||||||
|
|
||||||
|
|
||||||
|
def get_items(filters=None, search=None):
|
||||||
|
start = frappe.form_dict.start or 0
|
||||||
|
products_settings = get_product_settings()
|
||||||
|
page_length = products_settings.products_per_page
|
||||||
|
|
||||||
|
filters = filters or []
|
||||||
|
# convert to list of filters
|
||||||
|
if isinstance(filters, dict):
|
||||||
|
filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
|
||||||
|
|
||||||
|
show_in_website_condition = ''
|
||||||
|
if products_settings.hide_variants:
|
||||||
|
show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
|
||||||
|
else:
|
||||||
|
show_in_website_condition = get_conditions([
|
||||||
|
['show_in_website', '=', 1],
|
||||||
|
['show_variant_in_website', '=', 1]
|
||||||
|
], 'or')
|
||||||
|
|
||||||
|
search_condition = ''
|
||||||
|
if search:
|
||||||
|
search = '%{}%'.format(search)
|
||||||
|
or_filters = [
|
||||||
|
['name', 'like', search],
|
||||||
|
['item_name', 'like', search],
|
||||||
|
['description', 'like', search],
|
||||||
|
['item_group', 'like', search]
|
||||||
|
]
|
||||||
|
search_condition = get_conditions(or_filters, 'or')
|
||||||
|
|
||||||
|
filter_condition = get_conditions(filters, 'and')
|
||||||
|
|
||||||
|
where_conditions = ' and '.join(
|
||||||
|
[condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition]
|
||||||
|
)
|
||||||
|
|
||||||
|
left_joins = []
|
||||||
|
for f in filters:
|
||||||
|
if len(f) == 4 and f[0] != 'Item':
|
||||||
|
left_joins.append(f[0])
|
||||||
|
|
||||||
|
left_join = ' '.join(['LEFT JOIN `tab{0}` on (`tab{0}`.parent = `tabItem`.name)'.format(l) for l in left_joins])
|
||||||
|
|
||||||
|
results = frappe.db.sql('''
|
||||||
|
SELECT
|
||||||
|
`tabItem`.`name`, `tabItem`.`item_name`,
|
||||||
|
`tabItem`.`website_image`, `tabItem`.`image`,
|
||||||
|
`tabItem`.`web_long_description`, `tabItem`.`description`,
|
||||||
|
`tabItem`.`route`
|
||||||
|
FROM
|
||||||
|
`tabItem`
|
||||||
|
{left_join}
|
||||||
|
WHERE
|
||||||
|
{where_conditions}
|
||||||
|
GROUP BY
|
||||||
|
`tabItem`.`name`
|
||||||
|
ORDER BY
|
||||||
|
`tabItem`.`weightage` DESC
|
||||||
|
LIMIT
|
||||||
|
{page_length}
|
||||||
|
OFFSET
|
||||||
|
{start}
|
||||||
|
'''.format(
|
||||||
|
where_conditions=where_conditions,
|
||||||
|
start=start,
|
||||||
|
page_length=page_length,
|
||||||
|
left_join=left_join
|
||||||
|
)
|
||||||
|
, as_dict=1)
|
||||||
|
|
||||||
|
for r in results:
|
||||||
|
r.description = r.web_long_description or r.description
|
||||||
|
r.image = r.website_image or r.image
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_conditions(filter_list, and_or='and'):
|
||||||
|
from frappe.model.db_query import DatabaseQuery
|
||||||
|
|
||||||
|
if not filter_list:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
conditions = []
|
||||||
|
DatabaseQuery('Item').build_filter_conditions(filter_list, conditions, ignore_permissions=True)
|
||||||
|
join_by = ' {0} '.format(and_or)
|
||||||
|
|
||||||
|
return '(' + join_by.join(conditions) + ')'
|
||||||
|
|
||||||
|
# utilities
|
||||||
|
|
||||||
|
def get_item_attributes(item_code):
|
||||||
|
attributes = frappe.db.get_all('Item Variant Attribute',
|
||||||
|
fields=['attribute'],
|
||||||
|
filters={
|
||||||
|
'parenttype': 'Item',
|
||||||
|
'parent': item_code
|
||||||
|
},
|
||||||
|
order_by='idx asc'
|
||||||
|
)
|
||||||
|
|
||||||
|
optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
|
||||||
|
|
||||||
|
for a in attributes:
|
||||||
|
if a.attribute in optional_attributes:
|
||||||
|
a.optional = True
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
def get_html_for_items(items):
|
||||||
|
html = []
|
||||||
|
for item in items:
|
||||||
|
html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
|
||||||
|
'item': item
|
||||||
|
}))
|
||||||
|
return html
|
||||||
|
|
||||||
|
def get_product_settings():
|
||||||
|
doc = frappe.get_cached_doc('Products Settings')
|
||||||
|
doc.products_per_page = doc.products_per_page or 20
|
||||||
|
return doc
|
@ -11,7 +11,7 @@
|
|||||||
"public/js/shopping_cart.js"
|
"public/js/shopping_cart.js"
|
||||||
],
|
],
|
||||||
"css/erpnext-web.css": [
|
"css/erpnext-web.css": [
|
||||||
"public/less/website.less"
|
"public/scss/website.scss"
|
||||||
],
|
],
|
||||||
"js/marketplace.min.js": [
|
"js/marketplace.min.js": [
|
||||||
"public/js/hub/marketplace.js"
|
"public/js/hub/marketplace.js"
|
||||||
|
@ -48,6 +48,7 @@ $.extend(shopping_cart, {
|
|||||||
args: {
|
args: {
|
||||||
item_code: opts.item_code,
|
item_code: opts.item_code,
|
||||||
qty: opts.qty,
|
qty: opts.qty,
|
||||||
|
additional_notes: opts.additional_notes !== undefined ? opts.additional_notes : undefined,
|
||||||
with_items: opts.with_items || 0
|
with_items: opts.with_items || 0
|
||||||
},
|
},
|
||||||
btn: opts.btn,
|
btn: opts.btn,
|
||||||
@ -94,11 +95,12 @@ $.extend(shopping_cart, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shopping_cart_update: function(item_code, newVal, cart_dropdown) {
|
shopping_cart_update: function({item_code, qty, cart_dropdown, additional_notes}) {
|
||||||
frappe.freeze();
|
frappe.freeze();
|
||||||
shopping_cart.update_cart({
|
shopping_cart.update_cart({
|
||||||
item_code: item_code,
|
item_code,
|
||||||
qty: newVal,
|
qty,
|
||||||
|
additional_notes,
|
||||||
with_items: 1,
|
with_items: 1,
|
||||||
btn: this,
|
btn: this,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
@ -131,7 +133,7 @@ $.extend(shopping_cart, {
|
|||||||
}
|
}
|
||||||
input.val(newVal);
|
input.val(newVal);
|
||||||
var item_code = input.attr("data-item-code");
|
var item_code = input.attr("data-item-code");
|
||||||
shopping_cart.shopping_cart_update(item_code, newVal, true);
|
shopping_cart.shopping_cart_update({item_code, qty: newVal, cart_dropdown: true});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<span class="text-muted">({%= __("Shipping") %})</span>{% } %}
|
<span class="text-muted">({%= __("Shipping") %})</span>{% } %}
|
||||||
|
|
||||||
<a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}"
|
<a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}"
|
||||||
class="btn btn-default btn-xs pull-right"
|
class="btn btn-light btn-xs pull-right"
|
||||||
style="margin-top:-3px; margin-right: -5px;">
|
style="margin-top:-3px; margin-right: -5px;">
|
||||||
{%= __("Edit") %}</a>
|
{%= __("Edit") %}</a>
|
||||||
</p>
|
</p>
|
||||||
@ -19,5 +19,5 @@
|
|||||||
{% if(!addr_list.length) { %}
|
{% if(!addr_list.length) { %}
|
||||||
<p class="text-muted small">{%= __("No address added yet.") %}</p>
|
<p class="text-muted small">{%= __("No address added yet.") %}</p>
|
||||||
{% } %}
|
{% } %}
|
||||||
<p><button class="btn btn-xs btn-default btn-address">{{ __("New Address") }}</button></p>
|
<p><button class="btn btn-xs btn-light btn-address">{{ __("New Address") }}</button></p>
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<span class="text-muted">– {%= contact_list[i].designation %}</span>
|
<span class="text-muted">– {%= contact_list[i].designation %}</span>
|
||||||
{% } %}
|
{% } %}
|
||||||
<a href="#Form/Contact/{%= encodeURIComponent(contact_list[i].name) %}"
|
<a href="#Form/Contact/{%= encodeURIComponent(contact_list[i].name) %}"
|
||||||
class="btn btn-xs btn-default pull-right"
|
class="btn btn-xs btn-light pull-right"
|
||||||
style="margin-top:-3px; margin-right: -5px;">
|
style="margin-top:-3px; margin-right: -5px;">
|
||||||
{%= __("Edit") %}</a>
|
{%= __("Edit") %}</a>
|
||||||
</p>
|
</p>
|
||||||
@ -33,6 +33,6 @@
|
|||||||
{% if(!contact_list.length) { %}
|
{% if(!contact_list.length) { %}
|
||||||
<p class="text-muted small">{%= __("No contacts added yet.") %}</p>
|
<p class="text-muted small">{%= __("No contacts added yet.") %}</p>
|
||||||
{% } %}
|
{% } %}
|
||||||
<p><button class="btn btn-xs btn-default btn-contact">
|
<p><button class="btn btn-xs btn-light btn-contact">
|
||||||
{{ __("New Contact") }}</button>
|
{{ __("New Contact") }}</button>
|
||||||
</p>
|
</p>
|
17
erpnext/public/js/website_theme.js
Normal file
17
erpnext/public/js/website_theme.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
// MIT License. See license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Website Theme', {
|
||||||
|
apply_custom_theme(frm) {
|
||||||
|
let custom_theme = frm.doc.custom_theme;
|
||||||
|
custom_theme = custom_theme.split('\n');
|
||||||
|
if (
|
||||||
|
frm.doc.apply_custom_theme
|
||||||
|
&& custom_theme.length === 2
|
||||||
|
&& custom_theme[1].includes('frappe/public/scss/website')
|
||||||
|
) {
|
||||||
|
frm.set_value('custom_theme',
|
||||||
|
`$primary: #7575ff;\n@import "frappe/public/scss/website";\n@import "erpnext/public/scss/website";`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
69
erpnext/public/less/products.less
Normal file
69
erpnext/public/less/products.less
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
@import "variables.less";
|
||||||
|
|
||||||
|
.products-list .product-image {
|
||||||
|
display: inline-block;
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image.no-image {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--gray);
|
||||||
|
background: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-options {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-slideshow-image {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
object-fit: contain;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: var(--primary);
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0.25rem;
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
@ -245,10 +245,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.number-spinner {
|
// .number-spinner {
|
||||||
width:100px;
|
// width:100px;
|
||||||
margin-top:5px;
|
// margin-top:5px;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.cart-btn {
|
.cart-btn {
|
||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
@ -361,3 +361,24 @@
|
|||||||
border-color: @brand-primary;
|
border-color: @brand-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-slideshow-image {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
object-fit: contain;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
border-color: @brand-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-products {
|
||||||
|
.card-img-top {
|
||||||
|
max-height: 300px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
1
erpnext/public/node_modules
Symbolic link
1
erpnext/public/node_modules
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/Users/netchampfaris/frappe-bench/apps/erpnext/node_modules
|
53
erpnext/public/scss/website.scss
Normal file
53
erpnext/public/scss/website.scss
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
@import "frappe/public/scss/variables";
|
||||||
|
|
||||||
|
.product-image img {
|
||||||
|
min-height: 20rem;
|
||||||
|
max-height: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-options {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-slideshow-image {
|
||||||
|
height: 3rem;
|
||||||
|
width: 3rem;
|
||||||
|
object-fit: contain;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
border-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.address-card {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: $primary;
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 0.25rem;
|
||||||
|
background: $primary;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
"allow_events_in_timeline": 0,
|
"allow_events_in_timeline": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
"autoname": "",
|
"autoname": "",
|
||||||
"beta": 0,
|
"beta": 0,
|
||||||
"creation": "2013-03-07 11:42:57",
|
"creation": "2013-03-07 11:42:57",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
@ -1934,32 +1934,96 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "shopping_cart_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": "Shopping Cart",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "additional_notes",
|
||||||
|
"fieldtype": "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": "Additional Notes",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"menu_index": 0,
|
"menu_index": 0,
|
||||||
"modified": "2019-02-18 18:57:25.277633",
|
"modified": "2019-01-09 17:49:41.606821",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation Item",
|
"name": "Quotation Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 0,
|
"quick_entry": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
"show_name_in_global_search": 0,
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0,
|
"track_seen": 0,
|
||||||
"track_views": 0
|
"track_views": 0
|
||||||
}
|
}
|
@ -71,8 +71,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
|||||||
"items": get_product_list_for_group(product_group = self.name, start=start,
|
"items": get_product_list_for_group(product_group = self.name, start=start,
|
||||||
limit=context.page_length + 1, search=frappe.form_dict.get("search")),
|
limit=context.page_length + 1, search=frappe.form_dict.get("search")),
|
||||||
"parents": get_parent_item_groups(self.parent_item_group),
|
"parents": get_parent_item_groups(self.parent_item_group),
|
||||||
"title": self.name,
|
"title": self.name
|
||||||
"products_as_list": cint(frappe.db.get_single_value('Website Settings', 'products_as_list'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if self.slideshow:
|
if self.slideshow:
|
||||||
@ -119,7 +118,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
|
|||||||
for item in data:
|
for item in data:
|
||||||
set_product_info_for_website(item)
|
set_product_info_for_website(item)
|
||||||
|
|
||||||
return [get_item_for_list_in_html(r) for r in data]
|
return data
|
||||||
|
|
||||||
def get_child_groups_for_list_in_html(item_group, start, limit, search):
|
def get_child_groups_for_list_in_html(item_group, start, limit, search):
|
||||||
search_filters = None
|
search_filters = None
|
||||||
@ -141,7 +140,7 @@ def get_child_groups_for_list_in_html(item_group, start, limit, search):
|
|||||||
limit = limit
|
limit = limit
|
||||||
)
|
)
|
||||||
|
|
||||||
return [get_item_for_list_in_html(r) for r in data]
|
return data
|
||||||
|
|
||||||
def adjust_qty_for_expired_items(data):
|
def adjust_qty_for_expired_items(data):
|
||||||
adjusted_data = []
|
adjusted_data = []
|
||||||
@ -172,9 +171,7 @@ def get_item_for_list_in_html(context):
|
|||||||
context["show_availability_status"] = cint(frappe.db.get_single_value('Products Settings',
|
context["show_availability_status"] = cint(frappe.db.get_single_value('Products Settings',
|
||||||
'show_availability_status'))
|
'show_availability_status'))
|
||||||
|
|
||||||
products_template = 'templates/includes/products_as_grid.html'
|
products_template = 'templates/includes/products_as_list.html'
|
||||||
if cint(frappe.db.get_single_value('Products Settings', 'products_as_list')):
|
|
||||||
products_template = 'templates/includes/products_as_list.html'
|
|
||||||
|
|
||||||
return frappe.get_template(products_template).render(context)
|
return frappe.get_template(products_template).render(context)
|
||||||
|
|
||||||
@ -188,15 +185,20 @@ def get_group_item_count(item_group):
|
|||||||
|
|
||||||
|
|
||||||
def get_parent_item_groups(item_group_name):
|
def get_parent_item_groups(item_group_name):
|
||||||
|
base_parents = [
|
||||||
|
{"name": frappe._("Home"), "route":"/"},
|
||||||
|
{"name": frappe._("All Products"), "route":"/all-products"},
|
||||||
|
]
|
||||||
if not item_group_name:
|
if not item_group_name:
|
||||||
return [{"name": frappe._("Home"), "route":"/"}]
|
return base_parents
|
||||||
|
|
||||||
item_group = frappe.get_doc("Item Group", item_group_name)
|
item_group = frappe.get_doc("Item Group", item_group_name)
|
||||||
parent_groups = frappe.db.sql("""select name, route from `tabItem Group`
|
parent_groups = frappe.db.sql("""select name, route from `tabItem Group`
|
||||||
where lft <= %s and rgt >= %s
|
where lft <= %s and rgt >= %s
|
||||||
and show_in_website=1
|
and show_in_website=1
|
||||||
order by lft asc""", (item_group.lft, item_group.rgt), as_dict=True)
|
order by lft asc""", (item_group.lft, item_group.rgt), as_dict=True)
|
||||||
|
|
||||||
return [{"name": frappe._("Home"), "route":"/"}] + parent_groups
|
return base_parents + parent_groups
|
||||||
|
|
||||||
def invalidate_cache_for(doc, item_group=None):
|
def invalidate_cache_for(doc, item_group=None):
|
||||||
if not item_group:
|
if not item_group:
|
||||||
|
@ -45,7 +45,7 @@ class website_maker(object):
|
|||||||
website_settings.append("top_bar_items", {
|
website_settings.append("top_bar_items", {
|
||||||
"doctype": "Top Bar Item",
|
"doctype": "Top Bar Item",
|
||||||
"label": _("Products"),
|
"label": _("Products"),
|
||||||
"url": "/products"
|
"url": "/all-products"
|
||||||
})
|
})
|
||||||
website_settings.save()
|
website_settings.save()
|
||||||
|
|
||||||
|
@ -45,7 +45,8 @@ def get_cart_quotation(doc=None):
|
|||||||
for address in addresses],
|
for address in addresses],
|
||||||
"billing_addresses": [{"name": address.name, "display": address.display}
|
"billing_addresses": [{"name": address.name, "display": address.display}
|
||||||
for address in addresses],
|
for address in addresses],
|
||||||
"shipping_rules": get_applicable_shipping_rules(party)
|
"shipping_rules": get_applicable_shipping_rules(party),
|
||||||
|
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -83,7 +84,14 @@ def place_order():
|
|||||||
return sales_order.name
|
return sales_order.name
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_cart(item_code, qty, with_items=False):
|
def request_for_quotation():
|
||||||
|
quotation = _get_cart_quotation()
|
||||||
|
quotation.flags.ignore_permissions = True
|
||||||
|
quotation.submit()
|
||||||
|
return quotation.name
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_cart(item_code, qty, additional_notes=None, with_items=False):
|
||||||
quotation = _get_cart_quotation()
|
quotation = _get_cart_quotation()
|
||||||
|
|
||||||
empty_card = False
|
empty_card = False
|
||||||
@ -101,10 +109,12 @@ def update_cart(item_code, qty, with_items=False):
|
|||||||
quotation.append("items", {
|
quotation.append("items", {
|
||||||
"doctype": "Quotation Item",
|
"doctype": "Quotation Item",
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"qty": qty
|
"qty": qty,
|
||||||
|
"additional_notes": additional_notes
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
quotation_items[0].qty = qty
|
quotation_items[0].qty = qty
|
||||||
|
quotation_items[0].additional_notes = additional_notes
|
||||||
|
|
||||||
apply_cart_settings(quotation=quotation)
|
apply_cart_settings(quotation=quotation)
|
||||||
|
|
||||||
@ -140,6 +150,45 @@ def get_shopping_cart_menu(context=None):
|
|||||||
|
|
||||||
return frappe.render_template('templates/includes/cart/cart_dropdown.html', context)
|
return frappe.render_template('templates/includes/cart/cart_dropdown.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def add_new_address(doc):
|
||||||
|
doc = frappe.parse_json(doc)
|
||||||
|
doc.update({
|
||||||
|
'doctype': 'Address'
|
||||||
|
})
|
||||||
|
address = frappe.get_doc(doc)
|
||||||
|
address.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
return address
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def create_lead_for_item_inquiry(lead, subject, message):
|
||||||
|
lead = frappe.parse_json(lead)
|
||||||
|
lead_doc = frappe.new_doc('Lead')
|
||||||
|
lead_doc.update(lead)
|
||||||
|
lead_doc.set('lead_owner', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
lead_doc.save(ignore_permissions=True)
|
||||||
|
except frappe.exceptions.DuplicateEntryError:
|
||||||
|
frappe.clear_messages()
|
||||||
|
lead_doc = frappe.get_doc('Lead', {'email_id': lead['email_id']})
|
||||||
|
|
||||||
|
lead_doc.add_comment('Comment', text='''
|
||||||
|
<div>
|
||||||
|
<h5>{subject}</h5>
|
||||||
|
<p>{message}</p>
|
||||||
|
</div>
|
||||||
|
'''.format(subject=subject, message=message))
|
||||||
|
|
||||||
|
return lead_doc
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_terms_and_conditions(terms_name):
|
||||||
|
return frappe.db.get_value('Terms and Conditions', terms_name, 'terms')
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_cart_address(address_fieldname, address_name):
|
def update_cart_address(address_fieldname, address_name):
|
||||||
quotation = _get_cart_quotation()
|
quotation = _get_cart_quotation()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -41,10 +41,10 @@ def get_product_info_for_website(item_code):
|
|||||||
if item:
|
if item:
|
||||||
product_info["qty"] = item[0].qty
|
product_info["qty"] = item[0].qty
|
||||||
|
|
||||||
return {
|
return frappe._dict({
|
||||||
"product_info": product_info,
|
"product_info": product_info,
|
||||||
"cart_settings": cart_settings
|
"cart_settings": cart_settings
|
||||||
}
|
})
|
||||||
|
|
||||||
def set_product_info_for_website(item):
|
def set_product_info_for_website(item):
|
||||||
"""set product price uom for website"""
|
"""set product price uom for website"""
|
||||||
|
@ -180,6 +180,10 @@ frappe.ui.form.on("Item", {
|
|||||||
if (frm.doc.default_warehouse && !frm.doc.website_warehouse){
|
if (frm.doc.default_warehouse && !frm.doc.website_warehouse){
|
||||||
frm.set_value("website_warehouse", frm.doc.default_warehouse);
|
frm.set_value("website_warehouse", frm.doc.default_warehouse);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set_meta_tags(frm) {
|
||||||
|
frappe.utils.set_meta_tag(frm.doc.route);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
"allow_events_in_timeline": 0,
|
"allow_events_in_timeline": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 1,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:item_code",
|
"autoname": "field:item_code",
|
||||||
@ -3860,6 +3860,39 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
|
||||||
|
"fieldname": "set_meta_tags",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"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": "Set Meta Tags",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -3994,6 +4027,39 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.",
|
||||||
|
"fieldname": "website_content",
|
||||||
|
"fieldtype": "HTML 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": "Website Content",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_in_quick_entry": 0,
|
"allow_in_quick_entry": 0,
|
||||||
@ -4194,7 +4260,7 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"has_web_view": 1,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"icon": "fa fa-tag",
|
"icon": "fa fa-tag",
|
||||||
@ -4206,7 +4272,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 1,
|
"max_attachments": 1,
|
||||||
"modified": "2019-02-16 17:43:56.039611",
|
"modified": "2019-03-08 11:47:59.269724",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
@ -4377,4 +4443,4 @@
|
|||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 0,
|
"track_seen": 0,
|
||||||
"track_views": 0
|
"track_views": 0
|
||||||
}
|
}
|
@ -9,11 +9,11 @@ import erpnext
|
|||||||
import frappe
|
import frappe
|
||||||
import copy
|
import copy
|
||||||
from erpnext.controllers.item_variant import (ItemVariantExistsError,
|
from erpnext.controllers.item_variant import (ItemVariantExistsError,
|
||||||
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
|
copy_attributes_to_variant, get_variant, make_variant_item_code, validate_item_variant_attributes)
|
||||||
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
|
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
|
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
|
||||||
now_datetime, random_string, strip)
|
now_datetime, random_string, strip)
|
||||||
from frappe.utils.html_utils import clean_html
|
from frappe.utils.html_utils import clean_html
|
||||||
from frappe.website.doctype.website_slideshow.website_slideshow import \
|
from frappe.website.doctype.website_slideshow.website_slideshow import \
|
||||||
get_slideshow
|
get_slideshow
|
||||||
@ -40,7 +40,7 @@ class Item(WebsiteGenerator):
|
|||||||
website = frappe._dict(
|
website = frappe._dict(
|
||||||
page_title_field="item_name",
|
page_title_field="item_name",
|
||||||
condition_field="show_in_website",
|
condition_field="show_in_website",
|
||||||
template="templates/generators/item.html",
|
template="templates/generators/item/item.html",
|
||||||
no_cache=1
|
no_cache=1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ class Item(WebsiteGenerator):
|
|||||||
'''Add a new price'''
|
'''Add a new price'''
|
||||||
if not price_list:
|
if not price_list:
|
||||||
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
|
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
|
||||||
or frappe.db.get_value('Price List', _('Standard Selling')))
|
or frappe.db.get_value('Price List', _('Standard Selling')))
|
||||||
if price_list:
|
if price_list:
|
||||||
item_price = frappe.get_doc({
|
item_price = frappe.get_doc({
|
||||||
"doctype": "Item Price",
|
"doctype": "Item Price",
|
||||||
@ -199,7 +199,7 @@ class Item(WebsiteGenerator):
|
|||||||
def make_route(self):
|
def make_route(self):
|
||||||
if not self.route:
|
if not self.route:
|
||||||
return cstr(frappe.db.get_value('Item Group', self.item_group,
|
return cstr(frappe.db.get_value('Item Group', self.item_group,
|
||||||
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
|
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
|
||||||
|
|
||||||
def validate_website_image(self):
|
def validate_website_image(self):
|
||||||
"""Validate if the website image is a public file"""
|
"""Validate if the website image is a public file"""
|
||||||
@ -222,7 +222,7 @@ class Item(WebsiteGenerator):
|
|||||||
if not file_doc:
|
if not file_doc:
|
||||||
if not auto_set_website_image:
|
if not auto_set_website_image:
|
||||||
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
|
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
|
||||||
.format(self.website_image, self.name))
|
.format(self.website_image, self.name))
|
||||||
|
|
||||||
self.website_image = None
|
self.website_image = None
|
||||||
|
|
||||||
@ -313,6 +313,8 @@ class Item(WebsiteGenerator):
|
|||||||
self.set_variant_context(context)
|
self.set_variant_context(context)
|
||||||
self.set_attribute_context(context)
|
self.set_attribute_context(context)
|
||||||
self.set_disabled_attributes(context)
|
self.set_disabled_attributes(context)
|
||||||
|
self.set_metatags(context)
|
||||||
|
self.set_shopping_cart_data(context)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -323,8 +325,8 @@ class Item(WebsiteGenerator):
|
|||||||
# load variants
|
# load variants
|
||||||
# also used in set_attribute_context
|
# also used in set_attribute_context
|
||||||
context.variants = frappe.get_all("Item",
|
context.variants = frappe.get_all("Item",
|
||||||
filters={"variant_of": self.name, "show_variant_in_website": 1},
|
filters={"variant_of": self.name, "show_variant_in_website": 1},
|
||||||
order_by="name asc")
|
order_by="name asc")
|
||||||
|
|
||||||
variant = frappe.form_dict.variant
|
variant = frappe.form_dict.variant
|
||||||
if not variant and context.variants:
|
if not variant and context.variants:
|
||||||
@ -335,7 +337,7 @@ class Item(WebsiteGenerator):
|
|||||||
context.variant = frappe.get_doc("Item", variant)
|
context.variant = frappe.get_doc("Item", variant)
|
||||||
|
|
||||||
for fieldname in ("website_image", "web_long_description", "description",
|
for fieldname in ("website_image", "web_long_description", "description",
|
||||||
"website_specifications"):
|
"website_specifications"):
|
||||||
if context.variant.get(fieldname):
|
if context.variant.get(fieldname):
|
||||||
value = context.variant.get(fieldname)
|
value = context.variant.get(fieldname)
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
@ -358,8 +360,12 @@ class Item(WebsiteGenerator):
|
|||||||
# load attributes
|
# load attributes
|
||||||
for v in context.variants:
|
for v in context.variants:
|
||||||
v.attributes = frappe.get_all("Item Variant Attribute",
|
v.attributes = frappe.get_all("Item Variant Attribute",
|
||||||
fields=["attribute", "attribute_value"],
|
fields=["attribute", "attribute_value"],
|
||||||
filters={"parent": v.name})
|
filters={"parent": v.name})
|
||||||
|
# make a map for easier access in templates
|
||||||
|
v.attribute_map = frappe._dict({})
|
||||||
|
for attr in v.attributes:
|
||||||
|
v.attribute_map[attr.attribute] = attr.attribute_value
|
||||||
|
|
||||||
for attr in v.attributes:
|
for attr in v.attributes:
|
||||||
values = attribute_values_available.setdefault(attr.attribute, [])
|
values = attribute_values_available.setdefault(attr.attribute, [])
|
||||||
@ -431,6 +437,31 @@ class Item(WebsiteGenerator):
|
|||||||
if not find_variant(combination):
|
if not find_variant(combination):
|
||||||
context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
|
context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
|
||||||
|
|
||||||
|
def set_metatags(self, context):
|
||||||
|
context.metatags = frappe._dict({})
|
||||||
|
|
||||||
|
safe_description = frappe.utils.to_markdown(self.description)
|
||||||
|
|
||||||
|
context.metatags.url = frappe.utils.get_url() + '/' + context.route
|
||||||
|
|
||||||
|
if context.website_image:
|
||||||
|
if context.website_image.startswith('http'):
|
||||||
|
url = context.website_image
|
||||||
|
else:
|
||||||
|
url = frappe.utils.get_url() + context.website_image
|
||||||
|
context.metatags.image = url
|
||||||
|
|
||||||
|
context.metatags.description = safe_description[:300]
|
||||||
|
|
||||||
|
context.metatags.title = self.item_name or self.item_code
|
||||||
|
|
||||||
|
context.metatags['og:type'] = 'product'
|
||||||
|
context.metatags['og:site_name'] = 'ERPNext'
|
||||||
|
|
||||||
|
def set_shopping_cart_data(self, context):
|
||||||
|
from erpnext.shopping_cart.product_info import get_product_info_for_website
|
||||||
|
context.shopping_cart = get_product_info_for_website(self.name)
|
||||||
|
|
||||||
def add_default_uom_in_conversion_factor_table(self):
|
def add_default_uom_in_conversion_factor_table(self):
|
||||||
uom_conv_list = [d.uom for d in self.get("uoms")]
|
uom_conv_list = [d.uom for d in self.get("uoms")]
|
||||||
if self.stock_uom not in uom_conv_list:
|
if self.stock_uom not in uom_conv_list:
|
||||||
@ -533,7 +564,7 @@ class Item(WebsiteGenerator):
|
|||||||
warehouse += [d.get("warehouse")]
|
warehouse += [d.get("warehouse")]
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
|
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
|
||||||
.format(d.idx, d.warehouse), DuplicateReorderRows)
|
.format(d.idx, d.warehouse), DuplicateReorderRows)
|
||||||
|
|
||||||
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
|
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
|
||||||
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
|
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
|
||||||
@ -553,7 +584,7 @@ class Item(WebsiteGenerator):
|
|||||||
def update_item_price(self):
|
def update_item_price(self):
|
||||||
frappe.db.sql("""update `tabItem Price` set item_name=%s,
|
frappe.db.sql("""update `tabItem Price` set item_name=%s,
|
||||||
item_description=%s, brand=%s where item_code=%s""",
|
item_description=%s, brand=%s where item_code=%s""",
|
||||||
(self.item_name, self.description, self.brand, self.name))
|
(self.item_name, self.description, self.brand, self.name))
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
super(Item, self).on_trash()
|
super(Item, self).on_trash()
|
||||||
@ -575,7 +606,7 @@ class Item(WebsiteGenerator):
|
|||||||
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
|
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
|
||||||
if new_properties != [cstr(self.get(fld)) for fld in field_list]:
|
if new_properties != [cstr(self.get(fld)) for fld in field_list]:
|
||||||
frappe.throw(_("To merge, following properties must be same for both items")
|
frappe.throw(_("To merge, following properties must be same for both items")
|
||||||
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
|
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
|
||||||
|
|
||||||
def after_rename(self, old_name, new_name, merge):
|
def after_rename(self, old_name, new_name, merge):
|
||||||
if self.route:
|
if self.route:
|
||||||
@ -598,7 +629,7 @@ class Item(WebsiteGenerator):
|
|||||||
item_wise_tax_detail.pop(old_name)
|
item_wise_tax_detail.pop(old_name)
|
||||||
|
|
||||||
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
|
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
|
||||||
json.dumps(item_wise_tax_detail), update_modified=False)
|
json.dumps(item_wise_tax_detail), update_modified=False)
|
||||||
|
|
||||||
def set_last_purchase_rate(self, new_name):
|
def set_last_purchase_rate(self, new_name):
|
||||||
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
|
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
|
||||||
@ -626,7 +657,7 @@ class Item(WebsiteGenerator):
|
|||||||
self.set("website_specifications", [])
|
self.set("website_specifications", [])
|
||||||
if self.item_group:
|
if self.item_group:
|
||||||
for label, desc in frappe.db.get_values("Item Website Specification",
|
for label, desc in frappe.db.get_values("Item Website Specification",
|
||||||
{"parent": self.item_group}, ["label", "description"]):
|
{"parent": self.item_group}, ["label", "description"]):
|
||||||
row = self.append("website_specifications")
|
row = self.append("website_specifications")
|
||||||
row.label = label
|
row.label = label
|
||||||
row.description = desc
|
row.description = desc
|
||||||
@ -700,7 +731,7 @@ class Item(WebsiteGenerator):
|
|||||||
|
|
||||||
def update_variants(self):
|
def update_variants(self):
|
||||||
if self.flags.dont_update_variants or \
|
if self.flags.dont_update_variants or \
|
||||||
frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
|
frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
|
||||||
return
|
return
|
||||||
if self.has_variants:
|
if self.has_variants:
|
||||||
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
|
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
|
||||||
@ -751,7 +782,7 @@ class Item(WebsiteGenerator):
|
|||||||
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
|
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
|
||||||
if template_uom != self.stock_uom:
|
if template_uom != self.stock_uom:
|
||||||
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
|
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
|
||||||
.format(self.stock_uom, template_uom))
|
.format(self.stock_uom, template_uom))
|
||||||
|
|
||||||
def validate_uom_conversion_factor(self):
|
def validate_uom_conversion_factor(self):
|
||||||
if self.uoms:
|
if self.uoms:
|
||||||
@ -783,10 +814,14 @@ class Item(WebsiteGenerator):
|
|||||||
variant = get_variant(self.variant_of, args, self.name)
|
variant = get_variant(self.variant_of, args, self.name)
|
||||||
if variant:
|
if variant:
|
||||||
frappe.throw(_("Item variant {0} exists with same attributes")
|
frappe.throw(_("Item variant {0} exists with same attributes")
|
||||||
.format(variant), ItemVariantExistsError)
|
.format(variant), ItemVariantExistsError)
|
||||||
|
|
||||||
validate_item_variant_attributes(self, args)
|
validate_item_variant_attributes(self, args)
|
||||||
|
|
||||||
|
# copy variant_of value for each attribute row
|
||||||
|
for d in self.attributes:
|
||||||
|
d.variant_of = self.variant_of
|
||||||
|
|
||||||
|
|
||||||
def get_timeline_data(doctype, name):
|
def get_timeline_data(doctype, name):
|
||||||
'''returns timeline data based on stock ledger entry'''
|
'''returns timeline data based on stock ledger entry'''
|
||||||
@ -866,18 +901,18 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
|||||||
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
|
limit 1""", (item_code, cstr(doc_name)), as_dict=1)
|
||||||
|
|
||||||
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
|
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
|
||||||
or "1900-01-01")
|
or "1900-01-01")
|
||||||
purchase_receipt_date = getdate(last_purchase_receipt and
|
purchase_receipt_date = getdate(last_purchase_receipt and
|
||||||
last_purchase_receipt[0].posting_date or "1900-01-01")
|
last_purchase_receipt[0].posting_date or "1900-01-01")
|
||||||
|
|
||||||
if (purchase_order_date > purchase_receipt_date) or \
|
if (purchase_order_date > purchase_receipt_date) or \
|
||||||
(last_purchase_order and not last_purchase_receipt):
|
(last_purchase_order and not last_purchase_receipt):
|
||||||
# use purchase order
|
# use purchase order
|
||||||
last_purchase = last_purchase_order[0]
|
last_purchase = last_purchase_order[0]
|
||||||
purchase_date = purchase_order_date
|
purchase_date = purchase_order_date
|
||||||
|
|
||||||
elif (purchase_receipt_date > purchase_order_date) or \
|
elif (purchase_receipt_date > purchase_order_date) or \
|
||||||
(last_purchase_receipt and not last_purchase_order):
|
(last_purchase_receipt and not last_purchase_order):
|
||||||
# use purchase receipt
|
# use purchase receipt
|
||||||
last_purchase = last_purchase_receipt[0]
|
last_purchase = last_purchase_receipt[0]
|
||||||
purchase_date = purchase_receipt_date
|
purchase_date = purchase_receipt_date
|
||||||
@ -907,7 +942,7 @@ def invalidate_cache_for_item(doc):
|
|||||||
invalidate_cache_for(doc, doc.item_group)
|
invalidate_cache_for(doc, doc.item_group)
|
||||||
|
|
||||||
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
|
website_item_groups = list(set((doc.get("old_website_item_groups") or [])
|
||||||
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
|
+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
|
||||||
|
|
||||||
for item_group in website_item_groups:
|
for item_group in website_item_groups:
|
||||||
invalidate_cache_for(doc, item_group)
|
invalidate_cache_for(doc, item_group)
|
||||||
@ -915,6 +950,22 @@ def invalidate_cache_for_item(doc):
|
|||||||
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
|
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
|
||||||
invalidate_cache_for(doc, doc.old_item_group)
|
invalidate_cache_for(doc, doc.old_item_group)
|
||||||
|
|
||||||
|
invalidate_item_variants_cache_for_website(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def invalidate_item_variants_cache_for_website(doc):
|
||||||
|
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||||
|
|
||||||
|
item_code = None
|
||||||
|
if doc.has_variants and doc.show_in_website:
|
||||||
|
item_code = doc.name
|
||||||
|
elif doc.variant_of and frappe.db.get_value('Item', doc.variant_of, 'show_in_website'):
|
||||||
|
item_code = doc.variant_of
|
||||||
|
|
||||||
|
if item_code:
|
||||||
|
item_cache = ItemVariantsCacheManager(item_code)
|
||||||
|
item_cache.clear_cache()
|
||||||
|
|
||||||
|
|
||||||
def check_stock_uom_with_bin(item, stock_uom):
|
def check_stock_uom_with_bin(item, stock_uom):
|
||||||
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
|
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
|
||||||
@ -922,7 +973,7 @@ def check_stock_uom_with_bin(item, stock_uom):
|
|||||||
|
|
||||||
matched = True
|
matched = True
|
||||||
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
||||||
{"item_code": item}, "stock_uom")
|
{"item_code": item}, "stock_uom")
|
||||||
|
|
||||||
if ref_uom:
|
if ref_uom:
|
||||||
if cstr(ref_uom) != cstr(stock_uom):
|
if cstr(ref_uom) != cstr(stock_uom):
|
||||||
@ -931,7 +982,7 @@ def check_stock_uom_with_bin(item, stock_uom):
|
|||||||
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
|
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
|
||||||
for bin in bin_list:
|
for bin in bin_list:
|
||||||
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
|
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
|
||||||
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
|
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
|
||||||
matched = False
|
matched = False
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -309,7 +309,8 @@
|
|||||||
"warehouse_reorder_level": 20,
|
"warehouse_reorder_level": 20,
|
||||||
"warehouse_reorder_qty": 20
|
"warehouse_reorder_qty": 20
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"show_in_website": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "_Test Item 1",
|
"description": "_Test Item 1",
|
||||||
|
6
erpnext/stock/doctype/item_attribute/item_attribute.js
Normal file
6
erpnext/stock/doctype/item_attribute/item_attribute.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Item Attribute', {
|
||||||
|
|
||||||
|
});
|
@ -1,212 +1,294 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:attribute_name",
|
"autoname": "field:attribute_name",
|
||||||
|
"beta": 0,
|
||||||
"creation": "2014-09-26 03:49:54.899170",
|
"creation": "2014-09-26 03:49:54.899170",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
|
"editable_grid": 0,
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "attribute_name",
|
"fieldname": "attribute_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Attribute Name",
|
"label": "Attribute Name",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 0
|
"translatable": 0,
|
||||||
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "numeric_values",
|
"fieldname": "numeric_values",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Numeric Values",
|
"label": "Numeric Values",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "numeric_values",
|
"depends_on": "numeric_values",
|
||||||
"fieldname": "section_break_4",
|
"fieldname": "section_break_4",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
"fieldname": "from_range",
|
"fieldname": "from_range",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "From Range",
|
"label": "From Range",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
"fieldname": "increment",
|
"fieldname": "increment",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Increment",
|
"label": "Increment",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "column_break_8",
|
"fieldname": "column_break_8",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
"fieldname": "to_range",
|
"fieldname": "to_range",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "To Range",
|
"label": "To Range",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "eval: !doc.numeric_values",
|
"depends_on": "eval: !doc.numeric_values",
|
||||||
"fieldname": "section_break_5",
|
"fieldname": "section_break_5",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
"fieldname": "item_attribute_values",
|
"fieldname": "item_attribute_values",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Item Attribute Values",
|
"label": "Item Attribute Values",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -214,24 +296,29 @@
|
|||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"icon": "fa fa-edit",
|
"icon": "fa fa-edit",
|
||||||
|
"idx": 0,
|
||||||
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
|
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2015-11-16 06:29:48.198647",
|
"modified": "2019-01-01 13:17:46.524806",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Attribute",
|
"name": "Item Attribute",
|
||||||
@ -240,7 +327,6 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 0,
|
|
||||||
"cancel": 0,
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
@ -259,8 +345,13 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"quick_entry": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1,
|
||||||
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
@ -14,6 +15,40 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "variant_of",
|
||||||
|
"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": "Variant Of",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Item",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -41,10 +76,12 @@
|
|||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -70,10 +107,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -102,10 +141,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -133,10 +174,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -163,10 +206,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -194,10 +239,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -225,10 +272,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -254,10 +303,12 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
@ -285,6 +336,7 @@
|
|||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -299,7 +351,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2017-12-11 11:26:25.126350",
|
"modified": "2019-01-03 15:36:59.129006",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Variant Attribute",
|
"name": "Item Variant Attribute",
|
||||||
@ -313,5 +365,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 0,
|
"track_changes": 0,
|
||||||
"track_seen": 0
|
"track_seen": 0,
|
||||||
|
"track_views": 0
|
||||||
}
|
}
|
@ -1,143 +0,0 @@
|
|||||||
{% extends "templates/web.html" %}
|
|
||||||
|
|
||||||
{% block title %} {{ title }} {% endblock %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
{% include "templates/includes/breadcrumbs.html" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
|
||||||
{% from "erpnext/templates/includes/macros.html" import product_image %}
|
|
||||||
<div class="item-content">
|
|
||||||
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
|
|
||||||
<div class="row">
|
|
||||||
<div class="row">
|
|
||||||
{% if slideshow %}
|
|
||||||
{% set slideshow_items = frappe.get_list(doctype="Website Slideshow Item", fields=["image"], filters={ "parent": doc.slideshow }) %}
|
|
||||||
<div class="col-md-1">
|
|
||||||
{%- for slideshow_item in slideshow_items -%}
|
|
||||||
{% set image_src = slideshow_item['image'] %}
|
|
||||||
{% if image_src %}
|
|
||||||
<div class="item-alternative-image border">
|
|
||||||
<img src="{{ image_src }}" height="50" weight="50" />
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-5">
|
|
||||||
<div class="item-image">
|
|
||||||
{% set first_image = slideshow_items[0]['image'] %}
|
|
||||||
{{ product_image(first_image, "product-full-image") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{ product_image(website_image, "product-full-image") }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<h2 itemprop="name">{{ item_name }}</h2>
|
|
||||||
<p class="text-muted">
|
|
||||||
{{ _("Item Code") }}: <span itemprop="productID">{{ variant and variant.name or name }}</span>
|
|
||||||
</p>
|
|
||||||
<br>
|
|
||||||
<div class="item-attribute-selectors">
|
|
||||||
{% if has_variants and attributes %}
|
|
||||||
|
|
||||||
{% for d in attributes %}
|
|
||||||
{% if attribute_values[d.attribute] -%}
|
|
||||||
<div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
|
|
||||||
style="margin-bottom: 10px;">
|
|
||||||
<h6 class="text-muted">{{ _(d.attribute) }}</h6>
|
|
||||||
<select class="form-control"
|
|
||||||
style="max-width: 140px"
|
|
||||||
data-attribute="{{ d.attribute }}">
|
|
||||||
{% for value in attribute_values[d.attribute] %}
|
|
||||||
<option value="{{ value }}"
|
|
||||||
{% if selected_attributes and selected_attributes[d.attribute]==value -%}
|
|
||||||
selected
|
|
||||||
{%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
|
|
||||||
disabled
|
|
||||||
{%- endif %}>
|
|
||||||
{{ _(value) }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div>
|
|
||||||
<div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
|
|
||||||
<h4 class="item-price hide" itemprop="price"></h4>
|
|
||||||
<div class="item-stock hide" itemprop="availability"></div>
|
|
||||||
</div>
|
|
||||||
<div class="item-cart hide">
|
|
||||||
<div id="item-spinner">
|
|
||||||
<span style="display: inline-block">
|
|
||||||
<div class="input-group number-spinner">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button class="btn btn-default cart-btn" data-dir="dwn">
|
|
||||||
–</button>
|
|
||||||
</span>
|
|
||||||
<input class="form-control text-right cart-qty" value="1">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
|
|
||||||
+</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div id="item-add-to-cart">
|
|
||||||
<button class="btn btn-primary btn-sm">
|
|
||||||
{{ _("Add to Cart") }}</button>
|
|
||||||
</div>
|
|
||||||
<div id="item-update-cart" style="display: none;">
|
|
||||||
<a href="/cart" class='btn btn-sm btn-default'>
|
|
||||||
<i class='octicon octicon-check'></i>
|
|
||||||
{{ _("View in Cart") }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row item-website-description margin-top">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="h6 text-uppercase">{{ _("Description") }}</div>
|
|
||||||
<div itemprop="description" class="item-desc">
|
|
||||||
{{ web_long_description or description or _("No description given") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if website_specifications -%}
|
|
||||||
<div class="row item-website-specification margin-top">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="h6 text-uppercase">{{ _("Specifications") }}</div>
|
|
||||||
|
|
||||||
<table class="table">
|
|
||||||
{% for d in website_specifications -%}
|
|
||||||
<tr>
|
|
||||||
<td class="text-muted" style="width: 30%;">{{ d.label }}</td>
|
|
||||||
<td>{{ d.description }}</td>
|
|
||||||
</tr>
|
|
||||||
{%- endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
{% include "templates/includes/product_page.js" %}
|
|
||||||
|
|
||||||
{% if variant_info %}
|
|
||||||
window.variant_info = {{ variant_info }};
|
|
||||||
{% else %}
|
|
||||||
window.variant_info = null;
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
32
erpnext/templates/generators/item/item.html
Normal file
32
erpnext/templates/generators/item/item.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends "templates/web.html" %}
|
||||||
|
|
||||||
|
{% block title %} {{ title }} {% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{% include "templates/includes/breadcrumbs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
{% from "erpnext/templates/includes/macros.html" import product_image %}
|
||||||
|
<div class="item-content">
|
||||||
|
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
|
||||||
|
<div class="row mb-5">
|
||||||
|
{% include "templates/generators/item/item_image.html" %}
|
||||||
|
{% include "templates/generators/item/item_details.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "templates/generators/item/item_specifications.html" %}
|
||||||
|
|
||||||
|
{{ doc.website_content or '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block base_scripts %}
|
||||||
|
<!-- js should be loaded in body! -->
|
||||||
|
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/control.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
|
||||||
|
{% endblock %}
|
67
erpnext/templates/generators/item/item_add_to_cart.html
Normal file
67
erpnext/templates/generators/item/item_add_to_cart.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
|
||||||
|
|
||||||
|
{% set cart_settings = shopping_cart.cart_settings %}
|
||||||
|
{% set product_info = shopping_cart.product_info %}
|
||||||
|
|
||||||
|
<div class="item-cart row mt-2" data-variant-item-code="{{ item_code }}">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{% if cart_settings.show_price and product_info.price %}
|
||||||
|
<h4>
|
||||||
|
{{ product_info.price.formatted_price_sales_uom }}
|
||||||
|
<small class="text-muted">({{ product_info.price.formatted_price }} / {{ product_info.uom }})</small>
|
||||||
|
</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% if cart_settings.show_stock_availability %}
|
||||||
|
<div>
|
||||||
|
{% if product_info.in_stock == 0 %}
|
||||||
|
<span class="text-danger">
|
||||||
|
{{ _('Not in stock') }}
|
||||||
|
</span>
|
||||||
|
{% elif product_info.in_stock == 1 %}
|
||||||
|
<span class="text-success">
|
||||||
|
{{ _('In stock') }}
|
||||||
|
{% if product_info.show_stock_qty and product_info.stock_qty %}
|
||||||
|
({{ product_info.stock_qty[0][0] }})
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="mt-3">
|
||||||
|
<a href="/cart"
|
||||||
|
class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
{{ _("View in Cart") }}
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
data-item-code="{{item_code}}"
|
||||||
|
class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
|
||||||
|
>
|
||||||
|
{{ _("Add to Cart") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$('.page_content').on('click', '.btn-add-to-cart', (e) => {
|
||||||
|
const $btn = $(e.currentTarget);
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
const item_code = $btn.data('item-code');
|
||||||
|
erpnext.shopping_cart.update_cart({
|
||||||
|
item_code,
|
||||||
|
qty: 1,
|
||||||
|
callback(r) {
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
if (r.message) {
|
||||||
|
$('.btn-add-to-cart, .btn-view-in-cart').toggleClass('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endif %}
|
23
erpnext/templates/generators/item/item_configure.html
Normal file
23
erpnext/templates/generators/item/item_configure.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
|
||||||
|
{% set cart_settings = shopping_cart.cart_settings %}
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
{% if cart_settings.show_configure_button | int %}
|
||||||
|
<button class="btn btn-primary btn-configure"
|
||||||
|
data-item-code="{{ doc.name }}"
|
||||||
|
data-item-name="{{ doc.item_name }}"
|
||||||
|
>
|
||||||
|
{{ _('Configure') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if cart_settings.show_contact_us_button | int %}
|
||||||
|
<button class="btn btn-link btn-inquiry" data-item-code="{{ doc.name }}">
|
||||||
|
{{ _('Contact Us') }}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
{% include "templates/generators/item/item_configure.js" %}
|
||||||
|
{% include "templates/generators/item/item_inquiry.js" %}
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
318
erpnext/templates/generators/item/item_configure.js
Normal file
318
erpnext/templates/generators/item/item_configure.js
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
class ItemConfigure {
|
||||||
|
constructor(item_code, item_name) {
|
||||||
|
this.item_code = item_code;
|
||||||
|
this.item_name = item_name;
|
||||||
|
|
||||||
|
this.get_attributes_and_values()
|
||||||
|
.then(attribute_data => {
|
||||||
|
this.attribute_data = attribute_data;
|
||||||
|
this.show_configure_dialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show_configure_dialog() {
|
||||||
|
const fields = this.attribute_data.map(a => {
|
||||||
|
return {
|
||||||
|
fieldtype: 'Select',
|
||||||
|
label: a.attribute,
|
||||||
|
fieldname: a.attribute,
|
||||||
|
options: a.values.map(v => {
|
||||||
|
return {
|
||||||
|
label: v,
|
||||||
|
value: v
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
change: (e) => {
|
||||||
|
this.on_attribute_selection(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog = new frappe.ui.Dialog({
|
||||||
|
title: __('Configure {0}', [this.item_name]),
|
||||||
|
fields,
|
||||||
|
on_hide: () => {
|
||||||
|
set_continue_configuration();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.attribute_data.forEach(a => {
|
||||||
|
const field = this.dialog.get_field(a.attribute);
|
||||||
|
const $a = $(`<a href>${__("Clear")}</a>`);
|
||||||
|
$a.on('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.dialog.set_value(a.attribute, '');
|
||||||
|
});
|
||||||
|
field.$wrapper.find('.help-box').append($a);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.append_status_area();
|
||||||
|
this.dialog.show();
|
||||||
|
|
||||||
|
this.dialog.set_values(JSON.parse(localStorage.getItem(this.get_cache_key())));
|
||||||
|
|
||||||
|
$('.btn-configure').prop('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_attribute_selection(e) {
|
||||||
|
if (e) {
|
||||||
|
const changed_fieldname = $(e.target).data('fieldname');
|
||||||
|
this.show_range_input_if_applicable(changed_fieldname);
|
||||||
|
} else {
|
||||||
|
this.show_range_input_for_all_fields();
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = this.dialog.get_values();
|
||||||
|
if (Object.keys(values).length === 0) {
|
||||||
|
this.clear_status();
|
||||||
|
localStorage.removeItem(this.get_cache_key());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save state
|
||||||
|
localStorage.setItem(this.get_cache_key(), JSON.stringify(values));
|
||||||
|
|
||||||
|
// show
|
||||||
|
this.set_loading_status();
|
||||||
|
|
||||||
|
this.get_next_attribute_and_values(values)
|
||||||
|
.then(data => {
|
||||||
|
const {
|
||||||
|
valid_options_for_attributes,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
this.set_item_found_status(data);
|
||||||
|
|
||||||
|
for (let attribute in valid_options_for_attributes) {
|
||||||
|
const valid_options = valid_options_for_attributes[attribute];
|
||||||
|
const options = this.dialog.get_field(attribute).df.options;
|
||||||
|
const new_options = options.map(o => {
|
||||||
|
o.disabled = !valid_options.includes(o.value);
|
||||||
|
return o;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.set_df_property(attribute, 'options', new_options);
|
||||||
|
this.dialog.get_field(attribute).set_options();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show_range_input_for_all_fields() {
|
||||||
|
this.dialog.fields.forEach(f => {
|
||||||
|
this.show_range_input_if_applicable(f.fieldname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
show_range_input_if_applicable(fieldname) {
|
||||||
|
const changed_field = this.dialog.get_field(fieldname);
|
||||||
|
const changed_value = changed_field.get_value();
|
||||||
|
if (changed_value && changed_value.includes(' to ')) {
|
||||||
|
// possible range input
|
||||||
|
let numbers = changed_value.split(' to ');
|
||||||
|
numbers = numbers.map(number => parseFloat(number));
|
||||||
|
|
||||||
|
if (!numbers.some(n => isNaN(n))) {
|
||||||
|
numbers.sort((a, b) => a - b);
|
||||||
|
if (changed_field.$input_wrapper.find('.range-selector').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parent = $('<div class="range-selector">')
|
||||||
|
.insertBefore(changed_field.$input_wrapper.find('.help-box'));
|
||||||
|
const control = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Int',
|
||||||
|
label: __('Enter value betweeen {0} and {1}', [numbers[0], numbers[1]]),
|
||||||
|
change: () => {
|
||||||
|
const value = control.get_value();
|
||||||
|
if (value < numbers[0] || value > numbers[1]) {
|
||||||
|
control.$wrapper.addClass('was-validated');
|
||||||
|
control.set_description(
|
||||||
|
__('Value must be between {0} and {1}', [numbers[0], numbers[1]]));
|
||||||
|
control.$input[0].setCustomValidity('error');
|
||||||
|
} else {
|
||||||
|
control.$wrapper.removeClass('was-validated');
|
||||||
|
control.set_description('');
|
||||||
|
control.$input[0].setCustomValidity('');
|
||||||
|
this.update_range_values(fieldname, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render_input: true,
|
||||||
|
parent
|
||||||
|
});
|
||||||
|
control.$wrapper.addClass('mt-3');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_range_values(attribute, range_value) {
|
||||||
|
this.range_values = this.range_values || {};
|
||||||
|
this.range_values[attribute] = range_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
show_remaining_optional_attributes() {
|
||||||
|
// show all attributes if remaining
|
||||||
|
// unselected attributes are all optional
|
||||||
|
const unselected_attributes = this.dialog.fields.filter(df => {
|
||||||
|
const value_selected = this.dialog.get_value(df.fieldname);
|
||||||
|
return !value_selected;
|
||||||
|
});
|
||||||
|
const is_optional_attribute = df => {
|
||||||
|
const optional_attributes = this.attribute_data
|
||||||
|
.filter(a => a.optional).map(a => a.attribute);
|
||||||
|
return optional_attributes.includes(df.fieldname);
|
||||||
|
};
|
||||||
|
if (unselected_attributes.every(is_optional_attribute)) {
|
||||||
|
unselected_attributes.forEach(df => {
|
||||||
|
this.dialog.fields_dict[df.fieldname].$wrapper.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_loading_status() {
|
||||||
|
this.dialog.$status_area.html(`
|
||||||
|
<div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
|
||||||
|
${__('Loading...')}
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_item_found_status(data) {
|
||||||
|
const html = this.get_html_for_item_found(data);
|
||||||
|
this.dialog.$status_area.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_status() {
|
||||||
|
this.dialog.$status_area.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) {
|
||||||
|
const exact_match_message = __('1 exact match.');
|
||||||
|
const one_item = exact_match.length === 1 ?
|
||||||
|
exact_match[0] :
|
||||||
|
filtered_items_count === 1 ?
|
||||||
|
filtered_items[0] : '';
|
||||||
|
|
||||||
|
const item_add_to_cart = one_item ? `
|
||||||
|
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
|
||||||
|
<div>
|
||||||
|
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
|
||||||
|
</div>
|
||||||
|
<a href data-action="btn_add_to_cart" data-item-code="${one_item}">
|
||||||
|
${__('Add to cart')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`: '';
|
||||||
|
|
||||||
|
const items_found = filtered_items_count === 1 ?
|
||||||
|
__('{0} item found.', [filtered_items_count]) :
|
||||||
|
__('{0} items found.', [filtered_items_count]);
|
||||||
|
|
||||||
|
const item_found_status = `
|
||||||
|
<div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
|
||||||
|
<span>
|
||||||
|
${exact_match.length === 1 ? '' : items_found}
|
||||||
|
${exact_match.length === 1 ? `<span>${exact_match_message}</span>` : ''}
|
||||||
|
</span>
|
||||||
|
<a href data-action="btn_clear_values">
|
||||||
|
${__('Clear values')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
${item_add_to_cart}
|
||||||
|
${item_found_status}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn_add_to_cart(e) {
|
||||||
|
if (frappe.session.user !== 'Guest') {
|
||||||
|
localStorage.removeItem(this.get_cache_key());
|
||||||
|
}
|
||||||
|
const item_code = $(e.currentTarget).data('item-code');
|
||||||
|
const additional_notes = Object.keys(this.range_values || {}).map(attribute => {
|
||||||
|
return `${attribute}: ${this.range_values[attribute]}`;
|
||||||
|
}).join('\n');
|
||||||
|
erpnext.shopping_cart.update_cart({
|
||||||
|
item_code,
|
||||||
|
additional_notes,
|
||||||
|
qty: 1
|
||||||
|
});
|
||||||
|
this.dialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
btn_clear_values() {
|
||||||
|
this.dialog.fields_list.forEach(f => {
|
||||||
|
f.df.options = f.df.options.map(option => {
|
||||||
|
option.disabled = false;
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.dialog.clear();
|
||||||
|
this.on_attribute_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
append_status_area() {
|
||||||
|
this.dialog.$status_area = $('<div class="status-area">');
|
||||||
|
this.dialog.$wrapper.find('.modal-body').prepend(this.dialog.$status_area);
|
||||||
|
this.dialog.$wrapper.on('click', '[data-action]', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const $target = $(e.currentTarget);
|
||||||
|
const action = $target.data('action');
|
||||||
|
const method = this[action];
|
||||||
|
method.call(this, e);
|
||||||
|
});
|
||||||
|
this.dialog.$body.css({ maxHeight: '75vh', overflow: 'auto', overflowX: 'hidden' });
|
||||||
|
}
|
||||||
|
|
||||||
|
get_next_attribute_and_values(selected_attributes) {
|
||||||
|
return this.call('erpnext.portal.product_configurator.utils.get_next_attribute_and_values', {
|
||||||
|
item_code: this.item_code,
|
||||||
|
selected_attributes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get_attributes_and_values() {
|
||||||
|
return this.call('erpnext.portal.product_configurator.utils.get_attributes_and_values', {
|
||||||
|
item_code: this.item_code
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get_cache_key() {
|
||||||
|
return `configure:${this.item_code}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
call(method, args) {
|
||||||
|
// promisified frappe.call
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
frappe.call(method, args)
|
||||||
|
.then(r => resolve(r.message))
|
||||||
|
.fail(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_continue_configuration() {
|
||||||
|
const $btn_configure = $('.btn-configure');
|
||||||
|
const { itemCode } = $btn_configure.data();
|
||||||
|
|
||||||
|
if (localStorage.getItem(`configure:${itemCode}`)) {
|
||||||
|
$btn_configure.text(__('Continue Configuration'));
|
||||||
|
} else {
|
||||||
|
$btn_configure.text(__('Configure'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.ready(() => {
|
||||||
|
const $btn_configure = $('.btn-configure');
|
||||||
|
if (!$btn_configure.length) return;
|
||||||
|
const { itemCode, itemName } = $btn_configure.data();
|
||||||
|
|
||||||
|
set_continue_configuration();
|
||||||
|
|
||||||
|
$btn_configure.on('click', () => {
|
||||||
|
$btn_configure.prop('disabled', true);
|
||||||
|
new ItemConfigure(itemCode, itemName);
|
||||||
|
});
|
||||||
|
});
|
22
erpnext/templates/generators/item/item_details.html
Normal file
22
erpnext/templates/generators/item/item_details.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<div class="col-md-8">
|
||||||
|
<!-- title -->
|
||||||
|
<h1 itemprop="name">
|
||||||
|
{{ item_name }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted">
|
||||||
|
<span>{{ _("Item Code") }}:</span>
|
||||||
|
<span itemprop="productID">{{ doc.name }}</span>
|
||||||
|
</p>
|
||||||
|
<!-- description -->
|
||||||
|
<div itemprop="description">
|
||||||
|
{{ doc.web_long_description or doc.description or _("No description given") | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if has_variants %}
|
||||||
|
<!-- configure template -->
|
||||||
|
{% include "templates/generators/item/item_configure.html" %}
|
||||||
|
{% else %}
|
||||||
|
<!-- add variant to cart -->
|
||||||
|
{% include "templates/generators/item/item_add_to_cart.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
107
erpnext/templates/generators/item/item_image.html
Normal file
107
erpnext/templates/generators/item/item_image.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<div class="col-md-4 h-100">
|
||||||
|
{% if slides %}
|
||||||
|
{{ product_image(slides[0].image, 'product-image') }}
|
||||||
|
<div class="item-slideshow">
|
||||||
|
{% for item in slides %}
|
||||||
|
<img class="item-slideshow-image mt-2 {% if loop.first %}active{% endif %}"
|
||||||
|
src="{{ item.image }}" alt="{{ item.heading }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Simple image slideshow -->
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$('.page_content').on('click', '.item-slideshow-image', (e) => {
|
||||||
|
const $img = $(e.currentTarget);
|
||||||
|
const link = $img.prop('src');
|
||||||
|
const $product_image = $('.product-image');
|
||||||
|
$product_image.find('a').prop('href', link);
|
||||||
|
$product_image.find('img').prop('src', link);
|
||||||
|
|
||||||
|
$('.item-slideshow-image').removeClass('active');
|
||||||
|
$img.addClass('active');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% else %}
|
||||||
|
{{ product_image(website_image or image or 'no-image.jpg') }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Simple image preview -->
|
||||||
|
|
||||||
|
<div class="image-zoom-view" style="display: none;">
|
||||||
|
<button type="button" class="close" aria-label="Close">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.website-image {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-zoom-view {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 1080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-zoom-view img {
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-zoom-view button {
|
||||||
|
position: absolute;
|
||||||
|
right: 3rem;
|
||||||
|
top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-zoom-view svg {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
const $zoom_wrapper = $('.image-zoom-view');
|
||||||
|
|
||||||
|
$('.website-image').on('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const $img = $(e.target);
|
||||||
|
const src = $img.prop('src');
|
||||||
|
if (!src) return;
|
||||||
|
show_preview(src);
|
||||||
|
});
|
||||||
|
|
||||||
|
$zoom_wrapper.on('click', 'button', hide_preview);
|
||||||
|
|
||||||
|
$(document).on('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
hide_preview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function show_preview(src) {
|
||||||
|
$zoom_wrapper.show();
|
||||||
|
const $img = $(`<img src="${src}">`)
|
||||||
|
$zoom_wrapper.append($img);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_preview() {
|
||||||
|
$zoom_wrapper.find('img').remove();
|
||||||
|
$zoom_wrapper.hide();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
70
erpnext/templates/generators/item/item_inquiry.js
Normal file
70
erpnext/templates/generators/item/item_inquiry.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
frappe.ready(() => {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __('Contact Us'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: __('Full Name'),
|
||||||
|
fieldname: 'lead_name',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: __('Organization Name'),
|
||||||
|
fieldname: 'company_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: __('Email'),
|
||||||
|
fieldname: 'email_id',
|
||||||
|
options: 'Email',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: __('Subject'),
|
||||||
|
fieldname: 'subject',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: 'Text',
|
||||||
|
label: __('Message'),
|
||||||
|
fieldname: 'message',
|
||||||
|
reqd: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action: send_inquiry,
|
||||||
|
primary_action_label: __('Send')
|
||||||
|
});
|
||||||
|
|
||||||
|
function send_inquiry() {
|
||||||
|
const values = d.get_values();
|
||||||
|
const doc = Object.assign({}, values);
|
||||||
|
delete doc.subject;
|
||||||
|
delete doc.message;
|
||||||
|
|
||||||
|
d.hide();
|
||||||
|
|
||||||
|
frappe.call('erpnext.shopping_cart.cart.create_lead_for_item_inquiry', {
|
||||||
|
lead: doc,
|
||||||
|
subject: values.subject,
|
||||||
|
message: values.message
|
||||||
|
}).then(r => {
|
||||||
|
if (r.message) {
|
||||||
|
d.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.btn-inquiry').click((e) => {
|
||||||
|
const $btn = $(e.target);
|
||||||
|
const item_code = $btn.data('item-code');
|
||||||
|
d.set_value('subject', 'Inquiry about ' + item_code);
|
||||||
|
if (!['Administrator', 'Guest'].includes(frappe.session.user)) {
|
||||||
|
d.set_value('email_id', frappe.session.user);
|
||||||
|
d.set_value('lead_name', frappe.get_cookie('full_name'));
|
||||||
|
}
|
||||||
|
|
||||||
|
d.show();
|
||||||
|
});
|
||||||
|
});
|
16
erpnext/templates/generators/item/item_specifications.html
Normal file
16
erpnext/templates/generators/item/item_specifications.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% if doc.website_specifications -%}
|
||||||
|
<div class="row item-website-specification mt-5">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h6 class="text-uppercase text-muted">{{ _("Specifications") }}</h6>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
{% for d in doc.website_specifications -%}
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 30%;">{{ d.label }}</td>
|
||||||
|
<td>{{ d.description }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
@ -9,29 +9,32 @@
|
|||||||
{% include "templates/includes/slideshow.html" %}
|
{% include "templates/includes/slideshow.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if description %}<!-- description -->
|
{% if description %}<!-- description -->
|
||||||
<div itemprop="description">{{ description or ""}}</div>
|
<div class="mb-3" itemprop="description">{{ description or ""}}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="row">
|
||||||
{% if items %}
|
<div class="col-md-8">
|
||||||
<div id="search-list" {% if not products_as_list -%} class="row" {%- endif %}>
|
{% if items %}
|
||||||
{% for i in range(0, page_length) %}
|
<div id="search-list">
|
||||||
{% if items[i] %}
|
{% for i in range(0, page_length) %}
|
||||||
{{ items[i] }}
|
{% if items[i] %}
|
||||||
|
{%- set item = items[i] %}
|
||||||
|
{% include "erpnext/www/all-products/item_row.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="item-group-nav-buttons">
|
||||||
|
{% if frappe.form_dict.start|int > 0 %}
|
||||||
|
<a class="btn btn-outline-secondary" href="/{{ pathname }}?start={{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% if items|length > page_length %}
|
||||||
</div>
|
<a class="btn btn-outline-secondary" href="/{{ pathname }}?start={{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</a>
|
||||||
<div class="text-center item-group-nav-buttons">
|
{% endif %}
|
||||||
{% if frappe.form_dict.start|int > 0 %}
|
</div>
|
||||||
<a class="btn btn-default" href="/{{ pathname }}?start={{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</a>
|
{% else %}
|
||||||
{% endif %}
|
|
||||||
{% if items|length > page_length %}
|
|
||||||
<a class="btn btn-default" href="/{{ pathname }}?start={{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="text-muted">{{ _("No items listed") }}.</div>
|
<div class="text-muted">{{ _("No items listed") }}.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,12 +1,12 @@
|
|||||||
<div class="web-list-item">
|
<div class="web-list-item mb-3">
|
||||||
<a href="/addresses?name={{ doc.name | urlencode }}" class="no-decoration">
|
<a href="/addresses?name={{ doc.name | urlencode }}" class="no-underline text-reset">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3">
|
<div class="col-3">
|
||||||
<span class="indicator {{ "red" if doc.address_type=="Office" else "green" if doc.address_type=="Billing" else "blue" if doc.address_type=="Shipping" else "darkgrey" }}">{{ doc.address_title }}</span>
|
<span class="indicator {{ "red" if doc.address_type=="Office" else "green" if doc.address_type=="Billing" else "blue" if doc.address_type=="Shipping" else "darkgrey" }}">{{ doc.address_title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-2"> {{ _(doc.address_type) }} </div>
|
<div class="col-2"> {{ _(doc.address_type) }} </div>
|
||||||
<div class="col-xs-2"> {{ doc.city }} </div>
|
<div class="col-2"> {{ doc.city }} </div>
|
||||||
<div class="col-xs-5 text-right small text-muted">
|
<div class="col-5 text-right small text-muted">
|
||||||
{{ frappe.get_doc(doc).get_display() }}
|
{{ frappe.get_doc(doc).get_display() }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,41 +16,33 @@ $.extend(shopping_cart, {
|
|||||||
bind_events: function() {
|
bind_events: function() {
|
||||||
shopping_cart.bind_address_select();
|
shopping_cart.bind_address_select();
|
||||||
shopping_cart.bind_place_order();
|
shopping_cart.bind_place_order();
|
||||||
|
shopping_cart.bind_request_quotation();
|
||||||
shopping_cart.bind_change_qty();
|
shopping_cart.bind_change_qty();
|
||||||
|
shopping_cart.bind_change_notes();
|
||||||
shopping_cart.bind_dropdown_cart_buttons();
|
shopping_cart.bind_dropdown_cart_buttons();
|
||||||
},
|
},
|
||||||
|
|
||||||
bind_address_select: function() {
|
bind_address_select: function() {
|
||||||
$(".cart-addresses").find('input[data-address-name]').on("click", function() {
|
$(".cart-addresses").on('click', '.address-card', function(e) {
|
||||||
if($(this).prop("checked")) {
|
const $card = $(e.currentTarget);
|
||||||
var me = this;
|
const address_fieldname = $card.closest('[data-fieldname]').attr('data-fieldname');
|
||||||
|
const address_name = $card.closest('[data-address-name]').attr('data-address-name');
|
||||||
|
|
||||||
// uncheck other shipping or billing addresses:
|
return frappe.call({
|
||||||
if ( $(this).is('input[data-fieldname=customer_address]') ) {
|
type: "POST",
|
||||||
$('input[data-fieldname=customer_address]').not(this).prop('checked', false);
|
method: "erpnext.shopping_cart.cart.update_cart_address",
|
||||||
} else {
|
freeze: true,
|
||||||
$('input[data-fieldname=shipping_address_name]').not(this).prop('checked', false);
|
args: {
|
||||||
}
|
address_fieldname,
|
||||||
|
address_name
|
||||||
return frappe.call({
|
},
|
||||||
type: "POST",
|
callback: function(r) {
|
||||||
method: "erpnext.shopping_cart.cart.update_cart_address",
|
if(!r.exc) {
|
||||||
freeze: true,
|
$(".cart-tax-items").html(r.message.taxes);
|
||||||
args: {
|
|
||||||
address_fieldname: $(this).attr("data-fieldname"),
|
|
||||||
address_name: $(this).attr("data-address-name")
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
if(!r.exc) {
|
|
||||||
$(".cart-tax-items").html(r.message.taxes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
bind_place_order: function() {
|
bind_place_order: function() {
|
||||||
@ -59,12 +51,18 @@ $.extend(shopping_cart, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
bind_request_quotation: function() {
|
||||||
|
$('.btn-request-for-quotation').on('click', function() {
|
||||||
|
shopping_cart.request_quotation(this);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
bind_change_qty: function() {
|
bind_change_qty: function() {
|
||||||
// bind update button
|
// bind update button
|
||||||
$(".cart-items").on("change", ".cart-qty", function() {
|
$(".cart-items").on("change", ".cart-qty", function() {
|
||||||
var item_code = $(this).attr("data-item-code");
|
var item_code = $(this).attr("data-item-code");
|
||||||
var newVal = $(this).val();
|
var newVal = $(this).val();
|
||||||
shopping_cart.shopping_cart_update(item_code, newVal);
|
shopping_cart.shopping_cart_update({item_code, qty: newVal});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".cart-items").on('click', '.number-spinner button', function () {
|
$(".cart-items").on('click', '.number-spinner button', function () {
|
||||||
@ -82,7 +80,21 @@ $.extend(shopping_cart, {
|
|||||||
}
|
}
|
||||||
input.val(newVal);
|
input.val(newVal);
|
||||||
var item_code = input.attr("data-item-code");
|
var item_code = input.attr("data-item-code");
|
||||||
shopping_cart.shopping_cart_update(item_code, newVal);
|
shopping_cart.shopping_cart_update({item_code, qty: newVal});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
bind_change_notes: function() {
|
||||||
|
$('.cart-items').on('change', 'textarea', function() {
|
||||||
|
const $textarea = $(this);
|
||||||
|
const item_code = $textarea.attr('data-item-code');
|
||||||
|
const qty = $textarea.closest('tr').find('.cart-qty').val();
|
||||||
|
const notes = $textarea.val();
|
||||||
|
shopping_cart.shopping_cart_update({
|
||||||
|
item_code,
|
||||||
|
qty,
|
||||||
|
additional_notes: notes
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -150,7 +162,32 @@ $.extend(shopping_cart, {
|
|||||||
.html(msg || frappe._("Something went wrong!"))
|
.html(msg || frappe._("Something went wrong!"))
|
||||||
.toggle(true);
|
.toggle(true);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = "/orders/" + encodeURIComponent(r.message);
|
window.open('/orders/' + encodeURIComponent(r.message), '_blank');
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
request_quotation: function(btn) {
|
||||||
|
return frappe.call({
|
||||||
|
type: "POST",
|
||||||
|
method: "erpnext.shopping_cart.cart.request_for_quotation",
|
||||||
|
btn: btn,
|
||||||
|
callback: function(r) {
|
||||||
|
if(r.exc) {
|
||||||
|
var msg = "";
|
||||||
|
if(r._server_messages) {
|
||||||
|
msg = JSON.parse(r._server_messages || []).join("<br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#cart-error")
|
||||||
|
.empty()
|
||||||
|
.html(msg || frappe._("Something went wrong!"))
|
||||||
|
.toggle(true);
|
||||||
|
} else {
|
||||||
|
window.open('/printview?doctype=Quotation&name=' + r.message, '_blank');
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
12
erpnext/templates/includes/cart/address_card.html
Normal file
12
erpnext/templates/includes/cart/address_card.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div class="card address-card h-100">
|
||||||
|
<div class="check" style="position: absolute; right: 15px; top: 15px;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ address.name }}</h5>
|
||||||
|
<p class="card-text text-muted">
|
||||||
|
{{ address.display }}
|
||||||
|
</p>
|
||||||
|
<a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,26 +1,141 @@
|
|||||||
{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
|
{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
|
||||||
<div class="row">
|
|
||||||
{% if addresses|length == 1%}
|
{% if addresses | length == 1%}
|
||||||
{% set select_address = True %}
|
{% set select_address = True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="h6 text-uppercase">{{ _("Shipping Address") }}</div>
|
<div class="mb-3" data-section="shipping-address">
|
||||||
<div id="cart-shipping-address" class="panel-group"
|
<h6 class="text-uppercase">{{ _("Shipping Address") }}</h6>
|
||||||
data-fieldname="shipping_address_name">
|
<div class="row no-gutters" data-fieldname="shipping_address_name">
|
||||||
{% for address in shipping_addresses %}
|
{% for address in shipping_addresses %}
|
||||||
{{ show_address(address, doc, "shipping_address_name", select_address) }}
|
<div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
|
||||||
{% endfor %}
|
{% include "templates/includes/cart/address_card.html" %}
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-default btn-sm" href="/addresses">
|
{% endfor %}
|
||||||
{{ _("Manage Addresses") }}</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="h6 text-uppercase">{{ _("Billing Address") }}</div>
|
|
||||||
<div id="cart-billing-address" class="panel-group"
|
|
||||||
data-fieldname="customer_address">
|
|
||||||
{% for address in billing_addresses %}
|
|
||||||
{{ show_address(address, doc, "customer_address", select_address) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3" data-section="billing-address">
|
||||||
|
<h6 class="text-uppercase">{{ _("Billing Address") }}</h6>
|
||||||
|
<div class="row no-gutters" data-fieldname="customer_address">
|
||||||
|
{% for address in billing_addresses %}
|
||||||
|
<div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.customer_address == address.name %} data-active {% endif %}>
|
||||||
|
{% include "templates/includes/cart/address_card.html" %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="input_same_billing" checked>
|
||||||
|
<label class="custom-control-label" for="input_same_billing">{{ _('Billing Address is same as Shipping Address') }}</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm mt-3 btn-new-address">{{ _("Add a new address") }}</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$(document).on('click', '.address-card', (e) => {
|
||||||
|
const $target = $(e.currentTarget);
|
||||||
|
const $section = $target.closest('[data-section]');
|
||||||
|
$section.find('.address-card').removeClass('active');
|
||||||
|
$target.addClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#input_same_billing').change((e) => {
|
||||||
|
const $check = $(e.target);
|
||||||
|
toggle_billing_address_section(!$check.is(':checked'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.btn-new-address').click(() => {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __('New Address'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: __('Address Title'),
|
||||||
|
fieldname: 'address_title',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Address Type'),
|
||||||
|
fieldname: 'address_type',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
options: [
|
||||||
|
'Billing',
|
||||||
|
'Shipping'
|
||||||
|
],
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Address Line 1'),
|
||||||
|
fieldname: 'address_line1',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Address Line 2'),
|
||||||
|
fieldname: 'address_line2',
|
||||||
|
fieldtype: 'Data'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('City/Town'),
|
||||||
|
fieldname: 'city',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('State'),
|
||||||
|
fieldname: 'state',
|
||||||
|
fieldtype: 'Data'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Pin Code'),
|
||||||
|
fieldname: 'pincode',
|
||||||
|
fieldtype: 'Data'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Country'),
|
||||||
|
fieldname: 'country',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
reqd: 1
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primary_action_label: __('Save'),
|
||||||
|
primary_action: (values) => {
|
||||||
|
frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
|
||||||
|
.then(r => {
|
||||||
|
d.hide();
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
d.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setup_state() {
|
||||||
|
const shipping_address = $('[data-section="shipping-address"]')
|
||||||
|
.find('[data-address-name][data-active]').attr('data-address-name');
|
||||||
|
|
||||||
|
const billing_address = $('[data-section="billing-address"]')
|
||||||
|
.find('[data-address-name][data-active]').attr('data-address-name');
|
||||||
|
|
||||||
|
$('#input_same_billing').prop('checked', shipping_address === billing_address).trigger('change');
|
||||||
|
|
||||||
|
if (!shipping_address && !billing_address) {
|
||||||
|
$('#input_same_billing').prop('checked', true).trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shipping_address) {
|
||||||
|
$(`[data-section="shipping-address"] [data-address-name="${shipping_address}"] .address-card`).addClass('active');
|
||||||
|
}
|
||||||
|
if (billing_address) {
|
||||||
|
$(`[data-section="billing-address"] [data-address-name="${billing_address}"] .address-card`).addClass('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_state();
|
||||||
|
|
||||||
|
function toggle_billing_address_section(flag) {
|
||||||
|
$('[data-section="billing-address"]').toggle(flag);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@ -1,31 +1,42 @@
|
|||||||
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
|
|
||||||
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %}
|
|
||||||
|
|
||||||
{% for d in doc.items %}
|
{% for d in doc.items %}
|
||||||
<div class="row checkout">
|
<tr data-name="{{ d.name }}">
|
||||||
<div class="col-sm-8 col-xs-6 col-name-description">
|
<td>
|
||||||
{{ item_name_and_description(d) }}
|
<div class="font-weight-bold">
|
||||||
</div>
|
{{ d.item_name }}
|
||||||
<div class="col-sm-2 col-xs-3 text-right col-qty">
|
</div>
|
||||||
<span style="display: inline-block">
|
<div>
|
||||||
<div class="input-group number-spinner">
|
{{ d.item_code }}
|
||||||
<span class="input-group-btn">
|
</div>
|
||||||
<button class="btn btn-default cart-btn" data-dir="dwn">
|
{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
|
||||||
–</button>
|
{% if variant_of %}
|
||||||
</span>
|
<span class="text-muted">
|
||||||
<input class="form-control text-right cart-qty"
|
{{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
|
||||||
value = "{{ d.get_formatted('qty') }}"
|
|
||||||
data-item-code="{{ d.item_code }}">
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
|
|
||||||
+</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="col-sm-2 col-xs-3 text-right col-amount">
|
<div class="mt-2">
|
||||||
{{ d.get_formatted("amount") }}
|
<textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">{{d.additional_notes or ''}}</textarea>
|
||||||
<p class="text-muted small item-rate">{{ _("Rate") }} {{ d.get_formatted("rate") }}</p>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
<td class="text-right">
|
||||||
{% endfor %}
|
<div class="input-group number-spinner">
|
||||||
|
<span class="input-group-prepend d-none d-sm-inline-block">
|
||||||
|
<button class="btn btn-outline-secondary cart-btn" data-dir="dwn">–</button>
|
||||||
|
</span>
|
||||||
|
<input class="form-control text-right cart-qty border-secondary" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
|
||||||
|
<span class="input-group-append d-none d-sm-inline-block">
|
||||||
|
<button class="btn btn-outline-secondary cart-btn" data-dir="up">+</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{% if cart_settings.enable_checkout %}
|
||||||
|
<td class="text-right">
|
||||||
|
<div>
|
||||||
|
{{ d.get_formatted('amount') }}
|
||||||
|
</div>
|
||||||
|
<span class="text-muted">
|
||||||
|
{{ _('Rate:') }} {{ d.get_formatted('rate') }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
{% if not hide_footer_signup %}
|
{% if not hide_footer_signup %}
|
||||||
<div class='input-group input-group-sm pull-right footer-subscribe'>
|
<div class="input-group">
|
||||||
<input class="form-control" type="text" id="footer-subscribe-email"
|
<input type="text" class="form-control border-secondary"
|
||||||
placeholder="{{ _('Your email address') }}...">
|
id="footer-subscribe-email"
|
||||||
<span class='input-group-btn'>
|
placeholder="{{ _('Your email address...') }}"
|
||||||
<button class="btn btn-default" type="button"
|
aria-label="{{ _('Your email address...') }}"
|
||||||
id="footer-subscribe-button">{{ _("Get Updates") }}</button>
|
aria-describedby="footer-subscribe-button">
|
||||||
</span>
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-secondary"
|
||||||
|
type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,2 +1 @@
|
|||||||
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">
|
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
|
||||||
Powered by ERPNext</a>
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
{% macro product_image_square(website_image, css_class="") %}
|
{% macro product_image_square(website_image, css_class="") %}
|
||||||
{% if website_image -%}
|
|
||||||
<meta itemprop="image" content="{{ frappe.utils.quoted(website_image) | abs_url }}"></meta>
|
|
||||||
{%- endif %}
|
|
||||||
<div class="product-image product-image-square
|
<div class="product-image product-image-square
|
||||||
{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
|
{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
|
||||||
{% if website_image -%}
|
{% if website_image -%}
|
||||||
@ -11,12 +8,8 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro product_image(website_image, css_class="") %}
|
{% macro product_image(website_image, css_class="") %}
|
||||||
<div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
|
<div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
|
||||||
{% if website_image -%}
|
<img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
|
||||||
<a href="{{ frappe.utils.quoted(website_image) }}">
|
|
||||||
<img itemprop="image" src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
|
|
||||||
</a>
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
@ -33,3 +26,35 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_homepage_section(section) %}
|
||||||
|
|
||||||
|
{% if section.section_based_on == 'Custom HTML' and section.section_html %}
|
||||||
|
{{ section.section_html }}
|
||||||
|
{% elif section.section_based_on == 'Cards' %}
|
||||||
|
<section class="container my-5">
|
||||||
|
<h3>{{ section.name }}</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for card in section.section_cards %}
|
||||||
|
<div class="col-md-{{ section.column_value }} mb-4">
|
||||||
|
<div class="card h-100 justify-content-between">
|
||||||
|
{% if card.image %}
|
||||||
|
<div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ card.title }}</h5>
|
||||||
|
<p class="card-subtitle mb-2 text-muted">{{ card.subtitle or '' }}</p>
|
||||||
|
<p class="card-text">{{ card.content | truncate(140, True) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body flex-grow-0">
|
||||||
|
<a href="{{ card.route }}" class="card-link">{{ _('More details') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endmacro %}
|
@ -1,12 +1,10 @@
|
|||||||
{% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
|
{% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
|
||||||
|
|
||||||
{% block navbar_right_extension %}
|
{% block navbar_right_extension %}
|
||||||
<li class="shopping-cart hidden">
|
<li class="shopping-cart cart-icon hidden">
|
||||||
<div class="cart-icon">
|
<a href="/cart" class="nav-link">
|
||||||
<a class="dropdown-toggle" href="#" data-toggle="dropdown" id="navLogin">
|
{{ _("Cart") }}
|
||||||
{{ _("Cart") }} <span class="badge-wrapper" id="cart-count"></span>
|
<span class="badge badge-primary" id="cart-count"></span>
|
||||||
</a>
|
</a>
|
||||||
<div id="cart-overlay" class="dropdown-menu shopping-cart-menu"></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -9,7 +9,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 col-sm-10">
|
<div class="col-xs-8 col-sm-10">
|
||||||
{{ d.item_code }}
|
{{ d.item_code }}
|
||||||
<div class="text-muted small item-description">{{ d.description }}</div>
|
<div class="text-muted small item-description">
|
||||||
|
{{ html2text(d.description) | truncate(140) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
@ -25,14 +27,14 @@
|
|||||||
{{ d.item_name|truncate(25) }}
|
{{ d.item_name|truncate(25) }}
|
||||||
<div class="input-group number-spinner">
|
<div class="input-group number-spinner">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default cart-btn" data-dir="dwn">
|
<button class="btn btn-light cart-btn" data-dir="dwn">
|
||||||
–</button>
|
–</button>
|
||||||
</span>
|
</span>
|
||||||
<input class="form-control text-right cart-qty"
|
<input class="form-control text-right cart-qty"
|
||||||
value = "{{ d.get_formatted('qty') }}"
|
value = "{{ d.get_formatted('qty') }}"
|
||||||
data-item-code="{{ d.item_code }}">
|
data-item-code="{{ d.item_code }}">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default cart-btn" data-dir="up">
|
<button class="btn btn-light cart-btn" data-dir="up">
|
||||||
+</button>
|
+</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
{% if doc.taxes %}
|
{% if doc.taxes %}
|
||||||
<div class="row tax-net-total-row">
|
<tr>
|
||||||
<div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
|
<td class="text-right" colspan="2">
|
||||||
<div class="col-xs-6 text-right">
|
{{ _("Net Total") }}
|
||||||
{{ doc.get_formatted("net_total") }}</div>
|
</td>
|
||||||
</div>
|
<td class="text-right">
|
||||||
|
{{ doc.get_formatted("net_total") }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for d in doc.taxes %}
|
{% for d in doc.taxes %}
|
||||||
{% if d.base_tax_amount > 0 %}
|
{% if d.base_tax_amount > 0 %}
|
||||||
<div class="row tax-row">
|
<tr>
|
||||||
<div class="col-xs-6 text-right">{{ d.description }}</div>
|
<td class="text-right" colspan="2">
|
||||||
<div class="col-xs-6 text-right">
|
{{ d.description }}
|
||||||
{{ d.get_formatted("base_tax_amount") }}</div>
|
</td>
|
||||||
</div>
|
<td class="text-right">
|
||||||
|
{{ d.get_formatted("base_tax_amount") }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="row tax-grand-total-row">
|
|
||||||
<div class="col-xs-6 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
|
<tr>
|
||||||
<div class="col-xs-6 text-right">
|
<th class="text-right" colspan="2">
|
||||||
<span class="tax-grand-total bold">
|
{{ _("Grand Total") }}
|
||||||
{{ doc.get_formatted("grand_total") }}
|
</th>
|
||||||
</span>
|
<th class="text-right">
|
||||||
</div>
|
{{ doc.get_formatted("grand_total") }}
|
||||||
</div>
|
</th>
|
||||||
|
</tr>
|
||||||
|
@ -1,215 +0,0 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
||||||
// License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
frappe.ready(function() {
|
|
||||||
window.item_code = $('[itemscope] [itemprop="productID"]').text().trim();
|
|
||||||
var qty = 0;
|
|
||||||
|
|
||||||
frappe.call({
|
|
||||||
type: "POST",
|
|
||||||
method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
|
|
||||||
args: {
|
|
||||||
item_code: get_item_code()
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
|
||||||
if(r.message) {
|
|
||||||
if(r.message.cart_settings.enabled) {
|
|
||||||
$(".item-cart, .item-price, .item-stock").toggleClass("hide", (!!!r.message.product_info.price || !!!r.message.product_info.in_stock));
|
|
||||||
}
|
|
||||||
if(r.message.cart_settings.show_price) {
|
|
||||||
$(".item-price").toggleClass("hide", false);
|
|
||||||
}
|
|
||||||
if(r.message.cart_settings.show_stock_availability) {
|
|
||||||
$(".item-stock").toggleClass("hide", false);
|
|
||||||
}
|
|
||||||
if(r.message.product_info.price) {
|
|
||||||
$(".item-price")
|
|
||||||
.html(r.message.product_info.price.formatted_price_sales_uom + "<div style='font-size: small'>\
|
|
||||||
(" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")</div>");
|
|
||||||
|
|
||||||
if(r.message.product_info.in_stock==0) {
|
|
||||||
$(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
|
|
||||||
}
|
|
||||||
else if(r.message.product_info.in_stock==1) {
|
|
||||||
var qty_display = "{{ _("In stock") }}";
|
|
||||||
if (r.message.product_info.show_stock_qty) {
|
|
||||||
qty_display += " ("+r.message.product_info.stock_qty+")";
|
|
||||||
}
|
|
||||||
$(".item-stock").html("<div style='color: green'>\
|
|
||||||
<i class='fa fa-check'></i> "+qty_display+"</div>");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(r.message.product_info.qty) {
|
|
||||||
qty = r.message.product_info.qty;
|
|
||||||
toggle_update_cart(r.message.product_info.qty);
|
|
||||||
} else {
|
|
||||||
toggle_update_cart(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$("#item-add-to-cart button").on("click", function() {
|
|
||||||
frappe.provide('erpnext.shopping_cart');
|
|
||||||
|
|
||||||
erpnext.shopping_cart.update_cart({
|
|
||||||
item_code: get_item_code(),
|
|
||||||
qty: $("#item-spinner .cart-qty").val(),
|
|
||||||
callback: function(r) {
|
|
||||||
if(!r.exc) {
|
|
||||||
toggle_update_cart(1);
|
|
||||||
qty = 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
btn: this,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#item-spinner").on('click', '.number-spinner button', function () {
|
|
||||||
var btn = $(this),
|
|
||||||
input = btn.closest('.number-spinner').find('input'),
|
|
||||||
oldValue = input.val().trim(),
|
|
||||||
newVal = 0;
|
|
||||||
|
|
||||||
if (btn.attr('data-dir') == 'up') {
|
|
||||||
newVal = parseInt(oldValue) + 1;
|
|
||||||
} else if (btn.attr('data-dir') == 'dwn') {
|
|
||||||
if (parseInt(oldValue) > 1) {
|
|
||||||
newVal = parseInt(oldValue) - 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
newVal = parseInt(oldValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.val(newVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("[itemscope] .item-view-attribute .form-control").on("change", function() {
|
|
||||||
try {
|
|
||||||
var item_code = encodeURIComponent(get_item_code());
|
|
||||||
|
|
||||||
} catch(e) {
|
|
||||||
// unable to find variant
|
|
||||||
// then chose the closest available one
|
|
||||||
|
|
||||||
var attribute = $(this).attr("data-attribute");
|
|
||||||
var attribute_value = $(this).val();
|
|
||||||
var item_code = find_closest_match(attribute, attribute_value);
|
|
||||||
|
|
||||||
if (!item_code) {
|
|
||||||
frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute]))
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = window.location.pathname + "?variant=" + item_code;
|
|
||||||
});
|
|
||||||
|
|
||||||
// change the item image src when alternate images are hovered
|
|
||||||
$(document.body).on('mouseover', '.item-alternative-image', (e) => {
|
|
||||||
const $alternative_image = $(e.currentTarget);
|
|
||||||
const src = $alternative_image.find('img').prop('src');
|
|
||||||
$('.item-image img').prop('src', src);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var toggle_update_cart = function(qty) {
|
|
||||||
$("#item-add-to-cart").toggle(qty ? false : true);
|
|
||||||
$("#item-update-cart")
|
|
||||||
.toggle(qty ? true : false)
|
|
||||||
.find("input").val(qty);
|
|
||||||
$("#item-spinner").toggle(qty ? false : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_item_code() {
|
|
||||||
var variant_info = window.variant_info;
|
|
||||||
if(variant_info) {
|
|
||||||
var attributes = get_selected_attributes();
|
|
||||||
var no_of_attributes = Object.keys(attributes).length;
|
|
||||||
|
|
||||||
for(var i in variant_info) {
|
|
||||||
var variant = variant_info[i];
|
|
||||||
|
|
||||||
if (variant.attributes.length < no_of_attributes) {
|
|
||||||
// the case when variant has less attributes than template
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var match = true;
|
|
||||||
for(var j in variant.attributes) {
|
|
||||||
if(attributes[variant.attributes[j].attribute]
|
|
||||||
!= variant.attributes[j].attribute_value
|
|
||||||
) {
|
|
||||||
match = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(match) {
|
|
||||||
return variant.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw "Unable to match variant";
|
|
||||||
} else {
|
|
||||||
return window.item_code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function find_closest_match(selected_attribute, selected_attribute_value) {
|
|
||||||
// find the closest match keeping the selected attribute in focus and get the item code
|
|
||||||
|
|
||||||
var attributes = get_selected_attributes();
|
|
||||||
|
|
||||||
var previous_match_score = 0;
|
|
||||||
var previous_no_of_attributes = 0;
|
|
||||||
var matched;
|
|
||||||
|
|
||||||
var variant_info = window.variant_info;
|
|
||||||
for(var i in variant_info) {
|
|
||||||
var variant = variant_info[i];
|
|
||||||
var match_score = 0;
|
|
||||||
var has_selected_attribute = false;
|
|
||||||
|
|
||||||
for(var j in variant.attributes) {
|
|
||||||
if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
|
|
||||||
match_score = match_score + 1;
|
|
||||||
|
|
||||||
if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
|
|
||||||
has_selected_attribute = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_selected_attribute
|
|
||||||
&& ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) {
|
|
||||||
previous_match_score = match_score;
|
|
||||||
matched = variant;
|
|
||||||
previous_no_of_attributes = variant.attributes.length;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
for (var j in matched.attributes) {
|
|
||||||
var attr = matched.attributes[j];
|
|
||||||
$('[itemscope]')
|
|
||||||
.find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
|
|
||||||
.val(attr.attribute_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matched.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_selected_attributes() {
|
|
||||||
var attributes = {};
|
|
||||||
$('[itemscope]').find(".item-view-attribute .form-control").each(function() {
|
|
||||||
attributes[$(this).attr('data-attribute')] = $(this).val();
|
|
||||||
});
|
|
||||||
return attributes;
|
|
||||||
}
|
|
@ -2,18 +2,25 @@
|
|||||||
|
|
||||||
{% block title %} {{ _("Shopping Cart") }} {% endblock %}
|
{% block title %} {{ _("Shopping Cart") }} {% endblock %}
|
||||||
|
|
||||||
{% block header %}<h2>{{ _("My Cart") }}</h2>{% endblock %}
|
{% block header %}<h1>{{ _("Shopping Cart") }}</h1>{% endblock %}
|
||||||
|
|
||||||
|
<!--
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script>{% include "templates/includes/cart.js" %}</script>
|
<script>{% include "templates/includes/cart.js" %}</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
{% block header_actions %}
|
{% block header_actions %}
|
||||||
{% if doc.items %}
|
{% if doc.items and cart_settings.enable_checkout %}
|
||||||
<button class="btn btn-primary btn-place-order btn-sm"
|
<button class="btn btn-primary btn-place-order" type="button">
|
||||||
type="button">
|
{{ _("Place Order") }}
|
||||||
{{ _("Place Order") }}</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if doc.items and not cart_settings.enable_checkout %}
|
||||||
|
<button class="btn btn-primary btn-request-for-quotation" type="button">
|
||||||
|
{{ _("Request for Quotation") }}
|
||||||
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -22,58 +29,89 @@
|
|||||||
{% from "templates/includes/macros.html" import item_name_and_description %}
|
{% from "templates/includes/macros.html" import item_name_and_description %}
|
||||||
|
|
||||||
<div class="cart-container">
|
<div class="cart-container">
|
||||||
<div id="cart-container">
|
<div id="cart-error" class="alert alert-danger" style="display: none;"></div>
|
||||||
<div id="cart-error" class="alert alert-danger"
|
|
||||||
style="display: none;"></div>
|
{% if doc.items %}
|
||||||
<div id="cart-items">
|
<table class="table table-bordered mt-3">
|
||||||
<div class="row cart-item-header text-muted">
|
<thead>
|
||||||
<div class="col-sm-8 col-xs-6 h6 text-uppercase">
|
<tr>
|
||||||
{{ _("Item") }}
|
<th width="60%">{{ _('Item') }}</th>
|
||||||
</div>
|
<th width="20%" class="text-right">{{ _('Quantity') }}</th>
|
||||||
<div class="col-sm-2 col-xs-3 text-center h6 text-uppercase">
|
{% if cart_settings.enable_checkout %}
|
||||||
{{ _("Qty") }}
|
<th width="20%" class="text-right">{{ _('Subtotal') }}</th>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="col-sm-2 col-xs-3 text-right h6 text-uppercase">
|
</tr>
|
||||||
{{ _("Subtotal") }}
|
</thead>
|
||||||
</div>
|
<tbody class="cart-items">
|
||||||
</div>
|
{% include "templates/includes/cart/cart_items.html" %}
|
||||||
{% if doc.items %}
|
</tbody>
|
||||||
<div class="cart-items">
|
{% if cart_settings.enable_checkout %}
|
||||||
{% include "templates/includes/cart/cart_items.html" %}
|
<tfoot class="cart-tax-items">
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="empty-cart">{{ _("Cart is Empty") }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if doc.items %}
|
|
||||||
<!-- taxes -->
|
|
||||||
<div class="row cart-taxes">
|
|
||||||
<div class="col-sm-6"><!-- empty --></div>
|
|
||||||
<div class="col-sm-6 text-right cart-tax-items">
|
|
||||||
{% include "templates/includes/order/order_taxes.html" %}
|
{% include "templates/includes/order/order_taxes.html" %}
|
||||||
</div>
|
</tfoot>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if doc.tc_name %}
|
|
||||||
<div class="cart-terms" style="display: none;" title={{doc.tc_name}}>
|
|
||||||
{{doc.tc_name}}
|
|
||||||
{{doc.terms}}
|
|
||||||
</div>
|
|
||||||
<div class="cart-link">
|
|
||||||
<a href="#" onclick="show_terms();return false;">*{{ __("Terms and Conditions") }}</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">{{ _('Your cart is Empty') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="cart-addresses">
|
{% if doc.items %}
|
||||||
{% include "templates/includes/cart/cart_address.html" %}
|
{% if doc.tc_name %}
|
||||||
|
<div class="terms-and-conditions-link">
|
||||||
|
<a href class="link-terms-and-conditions" data-terms-name="{{ doc.tc_name }}">
|
||||||
|
{{ _("Terms and Conditions") }}
|
||||||
|
</a>
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$('.link-terms-and-conditions').click((e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const $link = $(e.target);
|
||||||
|
const terms_name = $link.attr('data-terms-name');
|
||||||
|
show_terms_and_conditions(terms_name);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
function show_terms_and_conditions(terms_name) {
|
||||||
|
frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
|
||||||
|
.then(r => {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: terms_name,
|
||||||
|
message: r.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p class="cart-footer text-right">
|
{% if cart_settings.enable_checkout %}
|
||||||
<button class="btn btn-primary btn-place-order btn-sm" type="button">
|
<div class="cart-addresses mt-5">
|
||||||
{{ _("Place Order") }}</button></p>
|
{% include "templates/includes/cart/cart_address.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-12">
|
||||||
|
{% if cart_settings.enable_checkout %}
|
||||||
|
<a href="/orders">
|
||||||
|
{{ _('See past orders') }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/quotations">
|
||||||
|
{{ _('See past quotations') }}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block base_scripts %}
|
||||||
|
<!-- js should be loaded in body! -->
|
||||||
|
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/control.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
value='{{ frappe.form_dict.q or ''}}'
|
value='{{ frappe.form_dict.q or ''}}'
|
||||||
{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
|
{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
|
||||||
<input type='submit'
|
<input type='submit'
|
||||||
class='btn btn-sm btn-default btn-search' value="{{ _("Search") }}">
|
class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
9
erpnext/templates/pages/home.css
Normal file
9
erpnext/templates/pages/home.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* csslint ignore:start */
|
||||||
|
{% if homepage.hero_image %}
|
||||||
|
.hero-image {
|
||||||
|
background-image: url("{{ homepage.hero_image }}");
|
||||||
|
background-size: cover;
|
||||||
|
padding: 10rem 0;
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
/* csslint ignore:end */
|
@ -1,75 +1,75 @@
|
|||||||
{% extends "templates/web.html" %}
|
{% extends "templates/web.html" %}
|
||||||
{% from "erpnext/templates/includes/macros.html" import product_image_square %}
|
|
||||||
|
|
||||||
{% block page_content %}
|
{% from "erpnext/templates/includes/macros.html" import render_homepage_section %}
|
||||||
|
|
||||||
<div class="row">
|
{% block content %}
|
||||||
<div class="col-sm-12">
|
<main>
|
||||||
<div class="hero">
|
{% if homepage.hero_section_based_on == 'Default' %}
|
||||||
<h1 class="text-center">{{ homepage.tag_line or '' }}</h1>
|
<section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}">
|
||||||
<p class="text-center">{{ homepage.description or '' }}</p>
|
<div class="container py-5">
|
||||||
|
<h1 class="d-none d-sm-block display-4">{{ homepage.tag_line }}</h1>
|
||||||
|
<h1 class="d-block d-sm-none">{{ homepage.tag_line }}</h1>
|
||||||
|
<h2 class="d-none d-sm-block">{{ homepage.description }}</h2>
|
||||||
|
<h3 class="d-block d-sm-none">{{ homepage.description }}</h3>
|
||||||
</div>
|
</div>
|
||||||
{% if homepage.products %}
|
|
||||||
<div class='featured-products-section' itemscope itemtype="http://schema.org/Product">
|
<div class="container">
|
||||||
<h5 class='featured-product-heading'>{{ _("Featured Products") }}</h5>
|
<a href="{{ explore_link }}" class="mb-5 btn btn-primary">{{ _('Explore') }}</a>
|
||||||
<div class="featured-products">
|
</div>
|
||||||
<div id="search-list" class="row" style="margin-top:40px;">
|
</section>
|
||||||
{% for item in homepage.products %}
|
{% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
|
||||||
<a class="product-link" href="{{ item.route|abs_url }}">
|
<section class="hero-section">
|
||||||
<div class="col-sm-4 col-xs-4 product-image-wrapper">
|
{% include "templates/includes/slideshow.html" %}
|
||||||
<div class="product-image-img">
|
</section>
|
||||||
<!-- thumbnail not updated, and used as background image in item card -->
|
{% elif homepage.hero_section_based_on == 'Homepage Section' %}
|
||||||
{{ product_image_square(item.image) }}
|
{{ render_homepage_section(homepage.hero_section_doc) }}
|
||||||
<div class="product-text" itemprop="name">{{ item.item_name }}</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
{% if homepage.products %}
|
||||||
</a>
|
<section class="container section-products my-5">
|
||||||
{% endfor %}
|
<h3>{{ _('Products') }}</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% for item in homepage.products %}
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 justify-content-between">
|
||||||
|
<div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ item.image }}" data-alt="{{ item.item_name }}"></div>
|
||||||
|
<div class="card-body flex-grow-0">
|
||||||
|
<h5 class="card-title">{{ item.item_name }}</h5>
|
||||||
|
<a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center padding">
|
{% endfor %}
|
||||||
<a href="{{ homepage.products_url or "/products" }}" class="btn btn-primary all-products">
|
|
||||||
{{ _("View All Products") }}</a></div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</section>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block style %}
|
{% if blogs %}
|
||||||
<style>
|
<section class="container my-5">
|
||||||
.hero {
|
<h3>{{ _('Publications') }}</h3>
|
||||||
padding-top: 50px;
|
|
||||||
padding-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero h1 {
|
<div class="row">
|
||||||
font-size: 40px;
|
{% for blog in blogs %}
|
||||||
font-weight: 200;
|
<div class="col-md-4 mb-4">
|
||||||
}
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ blog.title }}</h5>
|
||||||
|
<p class="card-subtitle mb-2 text-muted">{{ _('By {0}').format(blog.blogger) }}</p>
|
||||||
|
<p class="card-text">{{ blog.blog_intro }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body flex-grow-0">
|
||||||
|
<a href="{{ blog.route }}" class="card-link">{{ _('Read blog') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
.home-login {
|
{% for section in homepage_sections %}
|
||||||
margin-top: 30px;
|
{{ render_homepage_section(section) }}
|
||||||
}
|
{% endfor %}
|
||||||
.btn-login {
|
</main>
|
||||||
width: 80px;
|
{% endblock %}
|
||||||
}
|
|
||||||
|
|
||||||
.featured-product-heading, .all-products {
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.all-products {
|
|
||||||
font-weight: 300;
|
|
||||||
padding-left: 25px;
|
|
||||||
padding-right: 25px;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
@ -15,15 +15,38 @@ def get_context(context):
|
|||||||
if route:
|
if route:
|
||||||
item.route = '/' + route
|
item.route = '/' + route
|
||||||
|
|
||||||
context.title = homepage.title or homepage.company
|
homepage.title = homepage.title or homepage.company
|
||||||
|
context.title = homepage.title
|
||||||
# show atleast 3 products
|
|
||||||
if len(homepage.products) < 3:
|
|
||||||
for i in range(3 - len(homepage.products)):
|
|
||||||
homepage.append('products', {
|
|
||||||
'item_code': 'product-{0}'.format(i),
|
|
||||||
'item_name': frappe._('Product {0}').format(i),
|
|
||||||
'route': '#'
|
|
||||||
})
|
|
||||||
|
|
||||||
context.homepage = homepage
|
context.homepage = homepage
|
||||||
|
|
||||||
|
if homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section:
|
||||||
|
homepage.hero_section_doc = frappe.get_doc('Homepage Section', homepage.hero_section)
|
||||||
|
|
||||||
|
if homepage.slideshow:
|
||||||
|
doc = frappe.get_doc('Website Slideshow', homepage.slideshow)
|
||||||
|
context.slideshow = homepage.slideshow
|
||||||
|
context.slideshow_header = doc.header
|
||||||
|
context.slides = doc.slideshow_items
|
||||||
|
|
||||||
|
context.blogs = frappe.get_all('Blog Post',
|
||||||
|
fields=['title', 'blogger', 'blog_intro', 'route'],
|
||||||
|
filters={
|
||||||
|
'published': 1
|
||||||
|
},
|
||||||
|
order_by='modified desc',
|
||||||
|
limit=3
|
||||||
|
)
|
||||||
|
|
||||||
|
# filter out homepage section which is used as hero section
|
||||||
|
homepage_hero_section = homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section
|
||||||
|
homepage_sections = frappe.get_all('Homepage Section',
|
||||||
|
filters=[['name', '!=', homepage_hero_section]] if homepage_hero_section else None,
|
||||||
|
order_by='section_order asc'
|
||||||
|
)
|
||||||
|
context.homepage_sections = [frappe.get_doc('Homepage Section', name) for name in homepage_sections]
|
||||||
|
|
||||||
|
context.metatags = context.metatags or frappe._dict({})
|
||||||
|
context.metatags.image = homepage.hero_image or None
|
||||||
|
context.metatags.description = homepage.description or None
|
||||||
|
|
||||||
|
context.explore_link = '/all-products'
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header_actions %}
|
{% block header_actions %}
|
||||||
<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
|
<a class='btn btn-xs btn-light' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
@ -70,5 +70,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -9,7 +9,7 @@
|
|||||||
<label for="leave">Why do you want to leave this chapter</label>
|
<label for="leave">Why do you want to leave this chapter</label>
|
||||||
<input type="text" name="leave" class="form-control" id="leave">
|
<input type="text" name="leave" class="form-control" id="leave">
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-default btn-leave" data-title= "{{ chapter.name }}" id="btn-leave">Submit
|
<button type="button" class="btn btn-light btn-leave" data-title= "{{ chapter.name }}" id="btn-leave">Submit
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,23 +8,22 @@
|
|||||||
{% block title %}{{ doc.name }}{% endblock %}
|
{% block title %}{{ doc.name }}{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h1>{{ doc.name }}</h1>
|
<h1 class="m-0">{{ doc.name }}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header_actions %}
|
{% block header_actions %}
|
||||||
<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
|
<a href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
|
|
||||||
<div class="row transaction-subheading">
|
<div class="row transaction-subheading">
|
||||||
<div class="col-xs-6">
|
<div class="col-6">
|
||||||
|
|
||||||
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
|
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
|
||||||
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6 text-muted text-right small">
|
<div class="col-6 text-muted text-right small">
|
||||||
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
|
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
|
||||||
{% if doc.valid_till %}
|
{% if doc.valid_till %}
|
||||||
<p>
|
<p>
|
||||||
@ -34,16 +33,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class='small' style='padding-top: 15px;'>
|
<p class="small my-3">
|
||||||
{% if doc.doctype == 'Supplier Quotation' %}
|
{%- set party_name = doc.supplier_name if doc.doctype == 'Supplier Quotation' else doc.customer_name %}
|
||||||
<b>{{ doc.supplier_name}}</b>
|
<b>{{ party_name }}</b>
|
||||||
{% else %}
|
|
||||||
<b>{{ doc.customer_name}}</b>
|
{% if doc.contact_display and doc.contact_display != party_name %}
|
||||||
{% endif %}
|
<br>
|
||||||
{% if doc.contact_display %}
|
{{ doc.contact_display }}
|
||||||
<br>
|
{% endif %}
|
||||||
{{ doc.contact_display }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if doc._header %}
|
{% if doc._header %}
|
||||||
@ -55,7 +52,7 @@
|
|||||||
<!-- items -->
|
<!-- items -->
|
||||||
<div class="order-item-table">
|
<div class="order-item-table">
|
||||||
<div class="row order-items order-item-header text-muted">
|
<div class="row order-items order-item-header text-muted">
|
||||||
<div class="col-sm-6 col-xs-6 h6 text-uppercase">
|
<div class="col-sm-6 col-6 h6 text-uppercase">
|
||||||
{{ _("Item") }}
|
{{ _("Item") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
|
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
|
||||||
@ -67,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% for d in doc.items %}
|
{% for d in doc.items %}
|
||||||
<div class="row order-items">
|
<div class="row order-items">
|
||||||
<div class="col-sm-6 col-xs-6">
|
<div class="col-sm-6 col-6">
|
||||||
{{ item_name_and_description(d) }}
|
{{ item_name_and_description(d) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3 col-xs-3 text-right">
|
<div class="col-sm-3 col-xs-3 text-right">
|
||||||
@ -85,11 +82,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- taxes -->
|
<!-- taxes -->
|
||||||
<div class="order-taxes row">
|
<div class="order-taxes d-flex justify-content-end">
|
||||||
<div class="col-sm-6"><!-- empty --></div>
|
<table>
|
||||||
<div class="col-sm-6 text-right">
|
|
||||||
{% include "erpnext/templates/includes/order/order_taxes.html" %}
|
{% include "erpnext/templates/includes/order/order_taxes.html" %}
|
||||||
</div>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -115,7 +111,7 @@
|
|||||||
<div class="control-input">
|
<div class="control-input">
|
||||||
<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
|
<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
|
||||||
</div>
|
</div>
|
||||||
<p class="help-box small text-muted hidden-xs"> Available Points: {{ available_loyalty_points }} </p>
|
<p class="help-box small text-muted d-none d-sm-block"> Available Points: {{ available_loyalty_points }} </p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -25,7 +25,7 @@ frappe.ready(function() {
|
|||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<div class="more-btn"
|
<div class="more-btn"
|
||||||
style="display: none; text-align: center;">
|
style="display: none; text-align: center;">
|
||||||
<button class="btn btn-default">{{ _("More...") }}</button>
|
<button class="btn btn-light">{{ _("More...") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,11 +20,11 @@
|
|||||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ doc.percent_complete|round|int }}%;">
|
aria-valuemin="0" aria-valuemax="100" style="width:{{ doc.percent_complete|round|int }}%;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h4 style="float: left;">{{ _("Tasks") }}</h4>
|
<h4 style="float: left;">{{ _("Tasks") }}</h4>
|
||||||
<a class="btn btn-secondary btn-default btn-sm" style="float: right; position: relative; top: 10px;" href='/tasks?new=1&project={{ doc.project_name }}'>{{ _("New task") }}</a>
|
<a class="btn btn-secondary btn-light btn-sm" style="float: right; position: relative; top: 10px;" href='/tasks?new=1&project={{ doc.project_name }}'>{{ _("New task") }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<div class="page-header-actions-block" data-html-block="header-actions">
|
<div class="page-header-actions-block" data-html-block="header-actions">
|
||||||
<button type="submit" class="btn btn-primary btn-sm btn-form-submit">
|
<button type="submit" class="btn btn-primary btn-sm btn-form-submit">
|
||||||
{{ __("Update") }}</button>
|
{{ __("Update") }}</button>
|
||||||
<a href="tasks" class="btn btn-default btn-sm">
|
<a href="tasks" class="btn btn-light btn-sm">
|
||||||
{{ __("Cancel") }}</a>
|
{{ __("Cancel") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -91,7 +91,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-form-wrapper">
|
<div class="comment-form-wrapper">
|
||||||
<a class="add-comment btn btn-default btn-sm">{{ __("Add Comment") }}</a>
|
<a class="add-comment btn btn-light btn-sm">{{ __("Add Comment") }}</a>
|
||||||
<div style="display: none;" id="comment-form">
|
<div style="display: none;" id="comment-form">
|
||||||
<p>{{ __("Add Comment") }}</p>
|
<p>{{ __("Add Comment") }}</p>
|
||||||
<form>
|
<form>
|
||||||
|
0
erpnext/www/all-products/__init__.py
Normal file
0
erpnext/www/all-products/__init__.py
Normal file
163
erpnext/www/all-products/index.html
Normal file
163
erpnext/www/all-products/index.html
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
{% extends "templates/web.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ _('Products') }}{% endblock %}
|
||||||
|
{% block header %}
|
||||||
|
<h1>{{ _('Products') }}</h1>
|
||||||
|
{% endblock header %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8">
|
||||||
|
<div class="input-group input-group-sm mb-3">
|
||||||
|
<input type="search" class="form-control" placeholder="{{_('Search')}}"
|
||||||
|
aria-label="{{_('Product Search')}}" aria-describedby="product-search"
|
||||||
|
value="{{ frappe.form_dict.search or '' }}"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-4 pl-0">
|
||||||
|
<button class="btn btn-light btn-sm btn-block d-md-none"
|
||||||
|
type="button"
|
||||||
|
data-toggle="collapse"
|
||||||
|
data-target="#product-filters"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="product-filters"
|
||||||
|
style="white-space: nowrap;"
|
||||||
|
>
|
||||||
|
{{ _('Toggle Filters') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 order-2 col-md-8 order-md-1 products-list">
|
||||||
|
{% if items %}
|
||||||
|
{% for item in items %}
|
||||||
|
{% include "erpnext/www/all-products/item_row.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% include "erpnext/www/all-products/not_found.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-12 order-1 col-md-4 order-md-2">
|
||||||
|
|
||||||
|
{% if frappe.form_dict.start or frappe.form_dict.field_filters or frappe.form_dict.search %}
|
||||||
|
<a class="mb-3 d-inline-block" href="/all-products">{{ _('Clear filters') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="collapse d-md-block" id="product-filters">
|
||||||
|
{% for field_filter in field_filters %}
|
||||||
|
{%- set item_field = field_filter[0] %}
|
||||||
|
{%- set values = field_filter[1] %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<h6>{{ item_field.label }}</h6>
|
||||||
|
|
||||||
|
{% if values | len > 20 %}
|
||||||
|
<!-- show inline filter if values more than 20 -->
|
||||||
|
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if values %}
|
||||||
|
<div class="filter-options">
|
||||||
|
{% for value in values %}
|
||||||
|
<div class="custom-control custom-checkbox" data-value="{{ value }}">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="product-filter field-filter custom-control-input"
|
||||||
|
id="{{value}}"
|
||||||
|
data-filter-name="{{ item_field.fieldname }}"
|
||||||
|
data-filter-value="{{ value }}"
|
||||||
|
>
|
||||||
|
<label class="custom-control-label" for="{{value}}">
|
||||||
|
{{ value }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<i class="text-muted">{{ _('No values') }}</i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for attribute in attribute_filters %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<h6>{{ attribute.name }}</h6>
|
||||||
|
|
||||||
|
{% if values | len > 20 %}
|
||||||
|
<!-- show inline filter if values more than 20 -->
|
||||||
|
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if attribute.item_attribute_values %}
|
||||||
|
<div class="filter-options">
|
||||||
|
{% for attr_value in attribute.item_attribute_values %}
|
||||||
|
<div class="custom-control custom-checkbox" data-value="{{ value }}">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="product-filter attribute-filter custom-control-input"
|
||||||
|
id="{{attr_value.name}}"
|
||||||
|
data-attribute-name="{{ attribute.name }}"
|
||||||
|
data-attribute-value="{{ attr_value.attribute_value }}"
|
||||||
|
{% if attr_value.checked %} checked {% endif %}
|
||||||
|
>
|
||||||
|
<label class="custom-control-label" for="{{attr_value.name}}">
|
||||||
|
{{ attr_value.attribute_value }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<i class="text-muted">{{ _('No values') }}</i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$('.product-filter-filter').on('keydown', frappe.utils.debounce((e) => {
|
||||||
|
const $input = $(e.target);
|
||||||
|
const keyword = ($input.val() || '').toLowerCase();
|
||||||
|
const $filter_options = $input.next('.filter-options');
|
||||||
|
|
||||||
|
$filter_options.find('.custom-control').show();
|
||||||
|
$filter_options.find('.custom-control').each((i, el) => {
|
||||||
|
const $el = $(el);
|
||||||
|
const value = $el.data('value').toLowerCase();
|
||||||
|
if (!value.includes(keyword)) {
|
||||||
|
$el.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 300));
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
{% if frappe.form_dict.start|int > 0 %}
|
||||||
|
<button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if items|length >= page_length %}
|
||||||
|
<button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
frappe.ready(() => {
|
||||||
|
$('.btn-prev, .btn-next').click((e) => {
|
||||||
|
const $btn = $(e.target);
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
const start = $btn.data('start');
|
||||||
|
let query_params = frappe.utils.get_query_params();
|
||||||
|
query_params.start = start;
|
||||||
|
let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
|
||||||
|
window.location.href = path;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
161
erpnext/www/all-products/index.js
Normal file
161
erpnext/www/all-products/index.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
$(() => {
|
||||||
|
class ProductListing {
|
||||||
|
constructor() {
|
||||||
|
this.bind_filters();
|
||||||
|
this.bind_search();
|
||||||
|
this.restore_filters_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_filters() {
|
||||||
|
this.field_filters = {};
|
||||||
|
this.attribute_filters = {};
|
||||||
|
|
||||||
|
$('.product-filter').on('change', frappe.utils.debounce((e) => {
|
||||||
|
const $checkbox = $(e.target);
|
||||||
|
const is_checked = $checkbox.is(':checked');
|
||||||
|
|
||||||
|
if ($checkbox.is('.attribute-filter')) {
|
||||||
|
const {
|
||||||
|
attributeName: attribute_name,
|
||||||
|
attributeValue: attribute_value
|
||||||
|
} = $checkbox.data();
|
||||||
|
|
||||||
|
if (is_checked) {
|
||||||
|
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
|
||||||
|
this.attribute_filters[attribute_name].push(attribute_value);
|
||||||
|
} else {
|
||||||
|
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
|
||||||
|
this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attribute_filters[attribute_name].length === 0) {
|
||||||
|
delete this.attribute_filters[attribute_name];
|
||||||
|
}
|
||||||
|
} else if ($checkbox.is('.field-filter')) {
|
||||||
|
const {
|
||||||
|
filterName: filter_name,
|
||||||
|
filterValue: filter_value
|
||||||
|
} = $checkbox.data();
|
||||||
|
|
||||||
|
if (is_checked) {
|
||||||
|
this.field_filters[filter_name] = this.field_filters[filter_name] || [];
|
||||||
|
this.field_filters[filter_name].push(filter_value);
|
||||||
|
} else {
|
||||||
|
this.field_filters[filter_name] = this.field_filters[filter_name] || [];
|
||||||
|
this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.field_filters[filter_name].length === 0) {
|
||||||
|
delete this.field_filters[filter_name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const query_string = get_query_string({
|
||||||
|
field_filters: JSON.stringify(if_key_exists(this.field_filters)),
|
||||||
|
attribute_filters: JSON.stringify(if_key_exists(this.attribute_filters)),
|
||||||
|
});
|
||||||
|
window.history.pushState('filters', '', '/all-products?' + query_string);
|
||||||
|
|
||||||
|
$('.page_content input').prop('disabled', true);
|
||||||
|
this.get_items_with_filters()
|
||||||
|
.then(html => {
|
||||||
|
$('.products-list').html(html);
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
$('.page_content input').prop('disabled', false);
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
$('.page_content input').prop('disabled', false);
|
||||||
|
});
|
||||||
|
}, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
make_filters() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_search() {
|
||||||
|
$('input[type=search]').on('keydown', (e) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
// Enter
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value) {
|
||||||
|
window.location.search = 'search=' + e.target.value;
|
||||||
|
} else {
|
||||||
|
window.location.search = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_filters_state() {
|
||||||
|
const filters = frappe.utils.get_query_params();
|
||||||
|
let {field_filters, attribute_filters} = filters;
|
||||||
|
|
||||||
|
if (field_filters) {
|
||||||
|
field_filters = JSON.parse(field_filters);
|
||||||
|
for (let fieldname in field_filters) {
|
||||||
|
const values = field_filters[fieldname];
|
||||||
|
const selector = values.map(value => {
|
||||||
|
return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
|
||||||
|
}).join(',');
|
||||||
|
$(selector).prop('checked', true);
|
||||||
|
}
|
||||||
|
this.field_filters = field_filters;
|
||||||
|
}
|
||||||
|
if (attribute_filters) {
|
||||||
|
attribute_filters = JSON.parse(attribute_filters);
|
||||||
|
for (let attribute in attribute_filters) {
|
||||||
|
const values = attribute_filters[attribute];
|
||||||
|
const selector = values.map(value => {
|
||||||
|
return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
|
||||||
|
}).join(',');
|
||||||
|
$(selector).prop('checked', true);
|
||||||
|
}
|
||||||
|
this.attribute_filters = attribute_filters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_items_with_filters() {
|
||||||
|
const { attribute_filters, field_filters } = this;
|
||||||
|
const args = {
|
||||||
|
field_filters: if_key_exists(field_filters),
|
||||||
|
attribute_filters: if_key_exists(attribute_filters)
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
|
||||||
|
.then(r => {
|
||||||
|
if (r.exc) reject(r.exc);
|
||||||
|
else resolve(r.message);
|
||||||
|
})
|
||||||
|
.fail(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new ProductListing();
|
||||||
|
|
||||||
|
function get_query_string(object) {
|
||||||
|
const url = new URLSearchParams();
|
||||||
|
for (let key in object) {
|
||||||
|
const value = object[key];
|
||||||
|
if (value) {
|
||||||
|
url.append(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function if_key_exists(obj) {
|
||||||
|
let exists = false;
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key) && obj[key]) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exists ? obj : undefined;
|
||||||
|
}
|
||||||
|
});
|
26
erpnext/www/all-products/index.py
Normal file
26
erpnext/www/all-products/index.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import frappe
|
||||||
|
from erpnext.portal.product_configurator.utils import (get_products_for_website, get_product_settings,
|
||||||
|
get_field_filter_data, get_attribute_filter_data)
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
|
||||||
|
if frappe.form_dict:
|
||||||
|
search = frappe.form_dict.search
|
||||||
|
field_filters = frappe.parse_json(frappe.form_dict.field_filters)
|
||||||
|
attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
|
||||||
|
else:
|
||||||
|
search = field_filters = attribute_filters = None
|
||||||
|
|
||||||
|
context.items = get_products_for_website(field_filters, attribute_filters, search)
|
||||||
|
|
||||||
|
product_settings = get_product_settings()
|
||||||
|
context.field_filters = get_field_filter_data() \
|
||||||
|
if product_settings.enable_field_filters else []
|
||||||
|
|
||||||
|
context.attribute_filters = get_attribute_filter_data() \
|
||||||
|
if product_settings.enable_attribute_filters else []
|
||||||
|
|
||||||
|
context.product_settings = product_settings
|
||||||
|
context.page_length = product_settings.products_per_page
|
||||||
|
|
||||||
|
context.no_cache = 1
|
24
erpnext/www/all-products/item_row.html
Normal file
24
erpnext/www/all-products/item_row.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div class="card mb-3">
|
||||||
|
<div class="row no-gutters">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<a class="no-underline" href="{{ item.route }}">
|
||||||
|
<img class="website-image" src="{{ item.website_image or item.image or 'no-image.jpg' }}" alt="{{ item.item_name }}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
<a class="text-dark" href="{{ item.route }}">
|
||||||
|
{{ item.item_name or item.name }}
|
||||||
|
</a>
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
{{ item.website_description or item.description or '<i class="text-muted">No description</i>' }}
|
||||||
|
</p>
|
||||||
|
<a href="{{ item.route }}" class="btn btn-sm btn-light">{{ _('More details') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
1
erpnext/www/all-products/not_found.html
Normal file
1
erpnext/www/all-products/not_found.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="d-flex justify-content-center p-3 text-muted">{{ _('No products found') }}</div>
|
Loading…
Reference in New Issue
Block a user