refactor!: drop ecommerce in favor of webshop (#33265)

* refactor!: remove ecommerce item group field check

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove `e_commerce` directory

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove `get_context` from `item_group`

https://frappeframework.com/docs/v14/user/en/guides/portal-development/context

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove related `./templates`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(navbar): remove wishlist (ecommerce)

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(js): remove js from scripts

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove `www/all-products`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove pages and js

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove js/customer_reviews

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(portal utils): remove shopping cart debtor account

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove e_commerce events from hooks

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(web): remove e_commerce js from bundle

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(setup): remove shopping cart setup

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove pages

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor(item): remove website item button

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(payment request): remove `on_payment_authorized`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: @staticmethod `get_gateway_details`

to avoid monkey patching, in custom apps
https://discuss.erpnext.com/t/how-to-override-method-in-frappe/28786/36

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(pages): remove product page

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(homepage): do not setup website items

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor(workspace): remove link to ecommerce settings

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(www): remove shop-by-category

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(homepage): remove featured product

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor: remove products in homepage

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor(homepage): remove explore button

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor: remove products fields from homepage

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* Revert "refactor!: @staticmethod `get_gateway_details`"

This reverts commit 561bcd96680a930bb92627869502d9346b10611b.

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: remove payment gateway e_commerce import

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* chore: pre-commit

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!: pass `party` into `get_price`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor: move `get_item_codes_by_attributes` to `utilities/product`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* refactor!(quotation): input customer group

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* chore: pre-commit

* refactor: remove custom `navbar_items.html`

* refactor!(item): remove `published_in_website`

* refactor: move `validate_duplicate_website_item` before rename

* test: remove `test_shopping_cart_without_website_item`

* chore: add doctype drop patch

* refactor: removed website item related code

* refactor: removed shopping_cart code

* refactor: removed e-commerce related patches

* refactor: removed website related fields from item group

* fix: patch create_asset_depreciation_schedules_from_assets, KeyError: '0K BU64 AUY'

---------

Signed-off-by: Sabu Siyad <hello@ssiyad.com>
Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
This commit is contained in:
Sabu Siyad
2023-10-17 17:05:44 +05:30
committed by GitHub
parent 9d687ca6e9
commit f900a78995
160 changed files with 630 additions and 15222 deletions

View File

@@ -1,80 +0,0 @@
{% extends "templates/web.html" %}
{% from "erpnext/templates/includes/macros.html" import recommended_item_row %}
{% block title %} {{ title }} {% endblock %}
{% block breadcrumbs %}
<div class="item-breadcrumbs small text-muted">
{% include "templates/includes/breadcrumbs.html" %}
</div>
{% endblock %}
{% block page_content %}
<div class="product-container item-main">
{% from "erpnext/templates/includes/macros.html" import product_image %}
<div class="item-content">
<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
<!-- Image, Description, Add to Cart -->
<div class="row mb-5">
{% include "templates/generators/item/item_image.html" %}
{% include "templates/generators/item/item_details.html" %}
</div>
</div>
</div>
</div>
<!-- Additional Info/Reviews, Recommendations -->
<div class="d-flex">
{% set show_recommended_items = recommended_items and shopping_cart.cart_settings.enable_recommendations %}
{% set info_col = 'col-9' if show_recommended_items else 'col-12' %}
{% set padding_top = 'pt-0' if (show_tabs and tabs) else '' %}
<div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
<div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
<div class="product-page-content">
<!-- Product Specifications Table Section -->
{% if show_tabs and tabs %}
<div class="category-tabs">
<!-- tabs -->
{{ web_block("Section with Tabs", values=tabs, add_container=0,
add_top_padding=0, add_bottom_padding=0)
}}
</div>
{% elif website_specifications %}
{% include "templates/generators/item/item_specifications.html"%}
{% endif %}
<!-- Advanced Custom Website Content -->
{{ doc.website_content or '' }}
<!-- Reviews and Comments -->
{% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %}
{% include "templates/generators/item/item_reviews.html"%}
{% endif %}
</div>
</div>
</div>
<!-- Recommended Items -->
{% if show_recommended_items %}
<div class="mt-4 col-3 recommended-item-section">
<span class="recommendation-header">Recommended</span>
<div class="product-container mt-2 recommendation-container">
{% for item in recommended_items %}
{{ recommended_item_row(item) }}
{% endfor %}
</div>
</div>
{% endif %}
</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>
{{ include_script("frappe-web.bundle.js") }}
{{ include_script("controls.bundle.js") }}
{{ include_script("dialog.bundle.js") }}
{% endblock %}

View File

@@ -1,180 +0,0 @@
{% 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">
<!-- Price and Availability -->
{% if cart_settings.show_price and product_info.price %}
{% set price_info = product_info.price %}
<div class="product-price">
<!-- Final Price -->
<span itemprop="offers" itemscope itemtype="https://schema.org/Offer">
<span itemprop="price" content="{{ price_info.price_list_rate }}">{{ price_info.formatted_price_sales_uom }}</span>
<span style="display:none;" itemprop="priceCurrency" content="{{ price_info.currency }}">{{ price_info.currency }}</span>
</span>
<!-- Striked Price and Discount -->
{% if price_info.formatted_mrp %}
<small class="formatted-price">
<s>MRP {{ price_info.formatted_mrp }}</s>
</small>
<small class="ml-1 formatted-price in-green">
-{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}}
</small>
{% endif %}
<!-- Price per UOM -->
<small class="formatted-price ml-2">
({{ price_info.formatted_price }} / {{ product_info.uom }})
</small>
</div>
{% else %}
{{ _("UOM") }} : {{ product_info.uom }}
{% endif %}
{% if cart_settings.show_stock_availability %}
<div class="mt-2">
{% if product_info.get("on_backorder") %}
<span class="no-stock out-of-stock" style="color: var(--primary-color);">
{{ _('Available on backorder') }}
</span>
{% elif product_info.in_stock == 0 %}
<span class="no-stock out-of-stock">
{{ _('Out of stock') }}
</span>
{% elif product_info.in_stock == 1 %}
<span class="in-green has-stock">
{{ _('In stock') }}
{% if product_info.show_stock_qty and product_info.stock_qty %}
({{ product_info.stock_qty }})
{% endif %}
</span>
{% endif %}
</div>
{% endif %}
<!-- Offers -->
{% if doc.offers %}
<br>
<div class="offers-heading mb-4">
<span class="mr-1 tag-icon">
<svg class="icon icon-lg"><use href="#icon-tag"></use></svg>
</span>
<b>Available Offers</b>
</div>
<div class="offer-container">
{% for offer in doc.offers %}
<div class="mt-2 d-flex">
<div class="mr-2" >
<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--yellow-500)" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<p class="mr-1 mb-1">
{{ _(offer.offer_title) }}:
{{ _(offer.offer_subtitle) if offer.offer_subtitle else '' }}
<a class="offer-details" href="#"
data-offer-title="{{ offer.offer_title }}" data-offer-id="{{ offer.name }}"
role="button">
{{ _("More") }}
</a>
</p>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Add to Cart / View in Cart, Contact Us -->
<div class="mt-6 mb-5">
<div class="mb-4 d-flex">
<!-- Add to Cart -->
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
<a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md"
role="button">
{{ _("View in Cart") if cart_settings.enable_checkout else _("View in Quote") }}
</a>
<button
data-item-code="{{item_code}}"
class="btn btn-primary btn-add-to-cart mr-2 w-30-40"
>
<span class="mr-2">
<svg class="icon icon-md">
<use href="#icon-assets"></use>
</svg>
</span>
{{ _("Add to Cart") if cart_settings.enable_checkout else _("Add to Quote") }}
</button>
{% endif %}
<!-- Contact Us -->
{% if cart_settings.show_contact_us_button %}
{% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
</div>
</div>
</div>
<script>
frappe.ready(() => {
$('.page_content').on('click', '.btn-add-to-cart', (e) => {
// Bind action on add to cart button
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
const item_code = $btn.data('item-code');
erpnext.e_commerce.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');
}
}
});
});
$('.page_content').on('click', '.offer-details', (e) => {
// Bind action on More link in Offers
const $btn = $(e.currentTarget);
$btn.prop('disabled', true);
var d = new frappe.ui.Dialog({
title: __($btn.data('offer-title')),
fields: [
{
fieldname: 'offer_details',
fieldtype: 'HTML'
},
{
fieldname: 'section_break',
fieldtype: 'Section Break'
}
]
});
frappe.call({
method: 'erpnext.e_commerce.doctype.website_offer.website_offer.get_offer_details',
args: {
offer_id: $btn.data('offer-id')
},
callback: (value) => {
d.set_value("offer_details", value.message);
d.show();
$btn.prop('disabled', false);
}
})
});
});
</script>
{% endif %}

View File

@@ -1,20 +0,0 @@
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
{% set cart_settings = shopping_cart.cart_settings %}
<div class="mt-5 mb-6">
{% if cart_settings.enable_variants | int %}
<button class="btn btn-primary-light btn-configure font-md mr-2"
data-item-code="{{ doc.item_code }}"
data-item-name="{{ doc.web_item_name }}"
>
{{ _('Select Variant') }}
</button>
{% endif %}
{% if cart_settings.show_contact_us_button %}
{% include "templates/generators/item/item_inquiry.html" %}
{% endif %}
</div>
<script>
{% include "templates/generators/item/item_configure.js" %}
</script>
{% endif %}

View File

@@ -1,343 +0,0 @@
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: __('Select Variant for {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, available_qty, settings }) {
const one_item = exact_match.length === 1
? exact_match[0]
: filtered_items_count === 1
? filtered_items[0]
: '';
let item_add_to_cart = one_item ? `
<button data-item-code="${one_item}"
class="btn btn-primary btn-add-to-cart w-100"
data-action="btn_add_to_cart"
>
<span class="mr-2">
${frappe.utils.icon('assets', 'md')}
</span>
${__("Add to Cart")}
</button>
` : '';
const items_found = filtered_items_count === 1 ?
__('{0} item found.', [filtered_items_count]) :
__('{0} items found.', [filtered_items_count]);
/* eslint-disable indent */
const item_found_status = exact_match.length === 1
? `<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
<div><div>
${one_item}
${product_info && product_info.price && !$.isEmptyObject(product_info.price)
? '(' + product_info.price.formatted_price_sales_uom + ')'
: ''
}
${available_qty === 0 && product_info && product_info?.is_stock_item
? '<span class="text-danger">(' + __('Out of Stock') + ')</span>' : ''}
</div></div>
<a href data-action="btn_clear_values" data-item-code="${one_item}">
${__('Clear Values')}
</a>
</div>`
: `<div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
<span>
${items_found}
</span>
<a href data-action="btn_clear_values">
${__('Clear values')}
</a>
</div>`;
/* eslint-disable indent */
if (!product_info?.allow_items_not_in_stock && available_qty === 0
&& product_info && product_info?.is_stock_item) {
item_add_to_cart = '';
}
return `
${item_found_status}
${item_add_to_cart}
`;
}
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.e_commerce.shopping_cart.update_cart({
item_code,
additional_notes,
qty: 1
});
this.dialog.hide();
}
btn_clear_values() {
this.dialog.fields_list.forEach(f => {
if (f.df?.options) {
f.df.options = f.df.options.map(option => {
option.disabled = false;
return option;
});
}
});
this.dialog.clear();
this.dialog.$status_area.empty();
this.on_attribute_selection();
}
append_status_area() {
this.dialog.$status_area = $('<div class="status-area mt-5">');
this.dialog.$wrapper.find('.modal-body').append(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.$wrapper.addClass('item-configurator-dialog');
}
get_next_attribute_and_values(selected_attributes) {
return this.call('erpnext.e_commerce.variant_selector.utils.get_next_attribute_and_values', {
item_code: this.item_code,
selected_attributes
});
}
get_attributes_and_values() {
return this.call('erpnext.e_commerce.variant_selector.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 Selection'));
} else {
$btn_configure.text(__('Select Variant'));
}
}
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);
});
});

View File

@@ -1,63 +0,0 @@
{% set width_class = "expand" if not slides else "" %}
{% set cart_settings = shopping_cart.cart_settings %}
{% set product_info = shopping_cart.product_info %}
{% set price_info = product_info.get('price') or {} %}
<div class="col-md-7 product-details {{ width_class }}">
<div class="d-flex">
<!-- title -->
<div class="product-title col-11" itemprop="name">
{{ doc.web_item_name }}
</div>
<!-- Wishlist -->
{% if cart_settings.enable_wishlist %}
<div class="like-action-item-fp like-action {{ 'like-action-wished' if wished else ''}} ml-2"
data-item-code="{{ doc.item_code }}">
<svg class="icon sm">
<use class="{{ 'wished' if wished else 'not-wished' }} wish-icon" href="#icon-heart"></use>
</svg>
</div>
{% endif %}
</div>
<p class="product-code">
<span class="product-item-group">
{{ _(doc.item_group) }}
</span>
<span class="product-item-code">
{{ _("Item Code") }}:
</span>
<span itemprop="productID">{{ doc.item_code }}</span>
</p>
{% 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 %}
<!-- description -->
<div class="product-description" itemprop="description">
{% if frappe.utils.strip_html(doc.web_long_description or '') %}
{{ doc.web_long_description | safe }}
{% elif frappe.utils.strip_html(doc.description or '') %}
{{ doc.description | safe }}
{% else %}
{{ "" }}
{% endif %}
</div>
</div>
{% block base_scripts %}
<!-- js should be loaded in body! -->
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
{% endblock %}
<script>
$('.page_content').on('click', '.like-action-item-fp', (e) => {
// Bind action on wishlist button
const $btn = $(e.currentTarget);
erpnext.e_commerce.wishlist.wishlist_action($btn);
});
</script>

View File

@@ -1,108 +0,0 @@
{% set column_size = 5 if slides else 4 %}
<div class="col-md-{{ column_size }} h-100 d-flex mb-4">
{% if slides %}
<div class="item-slideshow d-flex flex-column mr-3">
{% for item in slides %}
<img class="item-slideshow-image mb-2 {% if loop.first %}active{% endif %}"
src="{{ item.image }}" alt="{{ item.heading }}">
{% endfor %}
</div>
{{ product_image(slides[0].image, 'product-image') }}
<!-- 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(doc.website_image, alt=doc.website_image_alt or doc.item_name) }}
{% 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>

View File

@@ -1,11 +0,0 @@
{% if shopping_cart and shopping_cart.cart_settings.enabled %}
{% set cart_settings = shopping_cart.cart_settings %}
{% if cart_settings.show_contact_us_button | int %}
<button class="btn btn-inquiry font-md w-30-40" data-item-code="{{ doc.name }}">
{{ _('Contact Us') }}
</button>
{% endif %}
<script>
{% include "templates/generators/item/item_inquiry.js" %}
</script>
{% endif %}

View File

@@ -1,77 +0,0 @@
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: __('Phone Number'),
fieldname: 'phone',
options: 'Phone',
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.e_commerce.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();
});
});

View File

@@ -1,88 +0,0 @@
{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %}
<div class="mt-4 ratings-reviews-section">
<!-- Title and Action -->
<div class="w-100 mt-4 mb-2 d-flex">
<div class="reviews-header col-9">
{{ _("Customer Reviews") }}
</div>
<div class="write-a-review-btn col-3">
<!-- Write a Review for legitimate users -->
{% if frappe.session.user != "Guest" and user_is_customer %}
<button class="btn btn-write-review"
data-web-item="{{ doc.name }}">
{{ _("Write a Review") }}
</button>
{% endif %}
</div>
</div>
<!-- Summary -->
{{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
<!-- Reviews and Comments -->
<div class="mt-8">
{% if reviews %}
{{ user_review(reviews) }}
{% if total_reviews > 4 %}
<div class="mt-6 mb-6"style="color: var(--primary);">
<a href="/customer_reviews?web_item={{ doc.name }}">{{ _("View all reviews") }}</a>
</div>
{% endif %}
{% else %}
<h6 class="text-muted mt-6">
{{ _("No Reviews") }}
</h6>
{% endif %}
</div>
</div>
<script>
frappe.ready(() => {
$('.page_content').on('click', '.btn-write-review', (e) => {
// Bind action on write a review button
const $btn = $(e.currentTarget);
let d = new frappe.ui.Dialog({
title: __("Write a Review"),
fields: [
{fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1},
{fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1},
{fieldtype: "Section Break"},
{fieldname: "comment", fieldtype: "Small Text", label: "Your Review"}
],
primary_action: function() {
var data = d.get_values();
frappe.call({
method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review",
args: {
web_item: "{{ doc.name }}",
title: data.title,
rating: data.rating,
comment: data.comment
},
freeze: true,
freeze_message: __("Submitting Review ..."),
callback: function(r) {
if(!r.exc) {
frappe.msgprint({
message: __("Thank you for the review"),
title: __("Review Submitted"),
indicator: "green"
});
d.hide();
location.reload();
}
}
});
},
primary_action_label: __('Submit')
});
d.show();
});
});
</script>

View File

@@ -1,20 +0,0 @@
<!-- Is reused to render within tabs as well as independently -->
{% if website_specifications %}
<div class="{{ 'mt-2' if not show_tabs else 'mt-5'}} item-website-specification">
<div class="col-md-11">
{% if not show_tabs %}
<div class="product-title mb-5 mt-4">
Product Details
</div>
{% endif %}
<table class="table">
{% for d in website_specifications -%}
<tr>
<td class="spec-label">{{ d.label }}</td>
<td class="spec-content">{{ d.description }}</td>
</tr>
{%- endfor %}
</table>
</div>
</div>
{% endif %}

View File

@@ -1,72 +0,0 @@
{% from "erpnext/templates/includes/macros.html" import field_filter_section, attribute_filter_section, discount_range_filters %}
{% extends "templates/web.html" %}
{% block header %}
<div class="mb-6">{{ _(item_group_name) }}</div>
{% endblock header %}
{% block script %}
<script type="text/javascript" src="/all-products/index.js"></script>
{% endblock %}
{% block breadcrumbs %}
<div class="item-breadcrumbs small text-muted">
{% include "templates/includes/breadcrumbs.html" %}
</div>
{% endblock %}
{% block page_content %}
<div class="item-group-content" itemscope itemtype="http://schema.org/Product"
data-item-group="{{ name }}">
<div class="item-group-slideshow">
{% if slideshow %} <!-- slideshow -->
{{ web_block(
"Hero Slider",
values=slideshow,
add_container=0,
add_top_padding=0,
add_bottom_padding=0,
) }}
{% endif %}
{% if description %} <!-- description -->
<div class="item-group-description text-muted mb-5" itemprop="description">{{ description or ""}}</div>
{% endif %}
</div>
<div class="row">
<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
<!-- Products Rendered in all-products/index.js-->
</div>
<div class="col-12 order-1 col-md-3 order-md-1">
<div class="collapse d-md-block mr-4 filters-section" id="product-filters">
<div class="d-flex justify-content-between align-items-center mb-5 title-section">
<div class="mb-4 filters-title" > {{ _('Filters') }} </div>
<a class="mb-4 clear-filters" href="/{{ doc.route }}">{{ _('Clear All') }}</a>
</div>
<!-- field filters -->
{{ field_filter_section(field_filters) }}
<!-- attribute filters -->
{{ attribute_filter_section(attribute_filters) }}
</div>
</div>
</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 %}