mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-20 21:49:18 +00:00
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:
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -1,17 +0,0 @@
|
||||
<div class="card address-card h-100">
|
||||
<div class="btn btn-sm btn-default btn-change-address font-md" style="position: absolute; right: 0; top: 0;">
|
||||
{{ _('Change') }}
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="card-title">{{ address.title }}</div>
|
||||
<div class="card-text mb-2">
|
||||
{{ address.display }}
|
||||
</div>
|
||||
<a href="/addresses?name={{address.name}}" class="card-link">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-edit"></use>
|
||||
</svg>
|
||||
{{ _('Edit') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
<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.title }}</h5>
|
||||
<p class="card-text text-muted">
|
||||
{{ address.display }}
|
||||
</p>
|
||||
<a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,189 +0,0 @@
|
||||
{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
|
||||
|
||||
{% if addresses | length == 1%}
|
||||
{% set select_address = True %}
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3 frappe-card p-5" data-section="shipping-address">
|
||||
<div class="d-flex">
|
||||
<div class="col-6 address-header"><h6>{{ _("Shipping Address") }}</h6></div>
|
||||
<div class="col-6" style="padding: 0;">
|
||||
<a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{% for address in shipping_addresses %}
|
||||
{% if doc.shipping_address_name == address.name %}
|
||||
<div class="row no-gutters" data-fieldname="shipping_address_name">
|
||||
<div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="shipping" data-active>
|
||||
{% include "templates/includes/cart/address_card.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Billing Address -->
|
||||
<div class="checkbox ml-1 mb-2">
|
||||
<label for="input_same_billing">
|
||||
<input type="checkbox" class="product-filter" id="input_same_billing" checked style="width: 14px !important">
|
||||
<span class="label-area font-md">{{ _('Billing Address is same as Shipping Address') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{% if billing_addresses %}
|
||||
<div class="mb-3 frappe-card p-5" data-section="billing-address">
|
||||
<div class="d-flex">
|
||||
<div class="col-6 address-header"><h6>{{ _("Billing Address") }}</h6></div>
|
||||
<div class="col-6" style="padding: 0;">
|
||||
<a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{% for address in billing_addresses %}
|
||||
{% if doc.customer_address == address.name %}
|
||||
<div class="row no-gutters" data-fieldname="customer_address">
|
||||
<div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="billing" data-active>
|
||||
{% include "templates/includes/cart/address_card.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
frappe.boot = {{ boot }}
|
||||
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 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: __('Country'),
|
||||
fieldname: 'country',
|
||||
fieldtype: 'Link',
|
||||
options: 'Country',
|
||||
only_select: true,
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "column_break0",
|
||||
fieldtype: "Column Break",
|
||||
width: "50%"
|
||||
},
|
||||
{
|
||||
label: __('Address Type'),
|
||||
fieldname: 'address_type',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'Billing',
|
||||
'Shipping'
|
||||
],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __('Postal Code'),
|
||||
fieldname: 'pincode',
|
||||
fieldtype: 'Data'
|
||||
},
|
||||
{
|
||||
fieldname: "phone",
|
||||
fieldtype: "Data",
|
||||
label: "Phone",
|
||||
reqd: 1
|
||||
},
|
||||
],
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: (values) => {
|
||||
frappe.call('erpnext.e_commerce.shopping_cart.cart.add_new_address', { doc: values })
|
||||
.then(r => {
|
||||
frappe.call({
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
|
||||
args: {
|
||||
address_type: r.message.address_type,
|
||||
address_name: r.message.name
|
||||
},
|
||||
callback: function (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,3 +0,0 @@
|
||||
<div class="mb-3 frappe-card p-5" data-section="shipping-address">
|
||||
<h6>{{ _("Shipping Address") }}</h6>
|
||||
</div>
|
||||
@@ -1,27 +0,0 @@
|
||||
<div class="cart-dropdown-container">
|
||||
<div id="cart-error" class="alert alert-danger"
|
||||
style="display: none;"></div>
|
||||
<div class="row checkout-btn">
|
||||
<div class="col-sm-12 col-xs-12">
|
||||
<a href="/cart" class="btn btn-block btn-primary">{{ _("Checkout") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row cart-items-dropdown cart-item-header text-muted">
|
||||
<div class="col-sm-6 col-xs-6 h6 text-uppercase">
|
||||
{{ _("Item") }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-xs-6 text-right h6 text-uppercase">
|
||||
{{ _("Price") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if doc.items %}
|
||||
<div class="row cart-items-dropdown">
|
||||
<div class="col-sm-12 col-xs-12">
|
||||
{% include "templates/includes/cart/cart_items_dropdown.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>{{ _("Cart is Empty") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -1,113 +0,0 @@
|
||||
{% from "erpnext/templates/includes/macros.html" import product_image %}
|
||||
|
||||
{% macro item_subtotal(item) %}
|
||||
<div>
|
||||
{{ item.get_formatted('amount') }}
|
||||
</div>
|
||||
|
||||
{% if item.is_free_item %}
|
||||
<div class="text-success mt-4">
|
||||
<span class="free-tag">
|
||||
{{ _('FREE') }}
|
||||
</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="item-rate">
|
||||
{{ _('Rate:') }} {{ item.get_formatted('rate') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% for d in doc.items %}
|
||||
<tr data-name="{{ d.name }}">
|
||||
<td style="width: 60%;">
|
||||
<div class="d-flex">
|
||||
<div class="cart-item-image mr-4">
|
||||
{% if d.thumbnail %}
|
||||
{{ product_image(d.thumbnail, alt="d.web_item_name", no_border=True) }}
|
||||
{% else %}
|
||||
<div class = "no-image-cart-item">
|
||||
{{ frappe.utils.get_abbr(d.web_item_name) or "NA" }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex w-100" style="flex-direction: column;">
|
||||
<div class="item-title mb-1 mr-3">
|
||||
{{ d.get("web_item_name") or d.item_name }}
|
||||
</div>
|
||||
<div class="item-subtitle mr-2">
|
||||
{{ d.item_code }}
|
||||
</div>
|
||||
{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
|
||||
{% if variant_of %}
|
||||
<span class="item-subtitle mr-2">
|
||||
{{ _('Variant of') }}
|
||||
<a href="{{frappe.db.get_value('Website Item', {'item_code': variant_of}, 'route') or '#'}}">
|
||||
{{ variant_of }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2 notes">
|
||||
<textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">
|
||||
{{d.additional_notes or ''}}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Qty column -->
|
||||
<td class="text-right" style="width: 25%;">
|
||||
<div class="d-flex">
|
||||
{% set disabled = 'disabled' if d.is_free_item else '' %}
|
||||
<div class="input-group number-spinner mt-1 mb-4">
|
||||
<span class="input-group-prepend d-sm-inline-block">
|
||||
<button class="btn cart-btn" data-dir="dwn" {{ disabled }}>
|
||||
{{ '–' if not d.is_free_item else ''}}
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<input class="form-control text-center cart-qty" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}"
|
||||
style="max-width: 70px;" {{ disabled }}>
|
||||
|
||||
<span class="input-group-append d-sm-inline-block">
|
||||
<button class="btn cart-btn" data-dir="up" {{ disabled }}>
|
||||
{{ '+' if not d.is_free_item else ''}}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{% if not d.is_free_item %}
|
||||
<div class="remove-cart-item column-sm-view d-flex" data-item-code="{{ d.item_code }}">
|
||||
<span>
|
||||
<svg class="icon sm remove-cart-item-logo"
|
||||
width="18" height="18" viewBox="0 0 18 18"
|
||||
xmlns="http://www.w3.org/2000/svg" id="icon-close">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.146 11.217a.5.5 0 1 0 .708.708l3.182-3.182 3.181 3.182a.5.5 0 1 0 .708-.708l-3.182-3.18 3.182-3.182a.5.5 0 1 0-.708-.708l-3.18 3.181-3.183-3.182a.5.5 0 0 0-.708.708l3.182 3.182-3.182 3.181z" stroke-width="0"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Shown on mobile view, else hidden -->
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<div class="text-right sm-item-subtotal">
|
||||
{{ item_subtotal(d) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<!-- Subtotal column -->
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<td class="text-right item-subtotal column-sm-view w-100">
|
||||
{{ item_subtotal(d) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1,12 +0,0 @@
|
||||
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %}
|
||||
|
||||
{% for d in doc.items %}
|
||||
<div class="row cart-dropdown">
|
||||
<div class="col-sm-8 col-xs-8 col-name-description">
|
||||
{{ item_name_and_description_cart(d) }}
|
||||
</div>
|
||||
<div class="col-sm-4 col-xs-4 text-right col-amount">
|
||||
{{ d.get_formatted("amount") }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -1,10 +0,0 @@
|
||||
<!-- Total at the end of the cart items -->
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-left item-grand-total" colspan="1">
|
||||
{{ _("Total") }}
|
||||
</th>
|
||||
<th class="text-left item-grand-total totals" colspan="3">
|
||||
{{ doc.get_formatted("total") }}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -1,22 +0,0 @@
|
||||
{% macro show_address(address, doc, fieldname, select_address=False) %}
|
||||
{% set selected=address.name==doc.get(fieldname) %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-sm-10 address-title"
|
||||
data-address-name="{{ address.name }}">
|
||||
<strong>{{ address.name }}</strong></div>
|
||||
<div class="col-sm-2 text-right">
|
||||
<input type="checkbox"
|
||||
data-fieldname="{{ fieldname }}"
|
||||
data-address-name="{{ address.name}}"
|
||||
{{ "checked" if selected else "" }}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-collapse"
|
||||
data-address-name="{{ address.name }}">
|
||||
<div class="panel-body text-muted small">{{ address.display }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,84 +0,0 @@
|
||||
<!-- Payment -->
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<h6>
|
||||
{{ _("Payment Summary") }}
|
||||
</h6>
|
||||
{% endif %}
|
||||
|
||||
<div class="card h-100">
|
||||
<div class="card-body p-0">
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<table class="table w-100">
|
||||
<tr>
|
||||
{% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %}
|
||||
<td class="bill-label">{{ _("Net Total (") + total_items + _(" Items)") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("net_total") }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- taxes -->
|
||||
{% for d in doc.taxes %}
|
||||
{% if d.base_tax_amount %}
|
||||
<tr>
|
||||
<td class="bill-label">
|
||||
{{ d.description }}
|
||||
</td>
|
||||
<td class="bill-content text-right">
|
||||
{{ d.get_formatted("base_tax_amount") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<!-- TODO: Apply Coupon Dialog-->
|
||||
<!-- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
|
||||
{% if show_coupon_code %}
|
||||
<button class="btn btn-coupon-code w-100 text-left">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--gray-600)" 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>
|
||||
<span class="ml-2">Apply Coupon</span>
|
||||
</button>
|
||||
{% endif %} -->
|
||||
|
||||
<table class="table w-100 grand-total mt-6">
|
||||
<tr>
|
||||
<td class="bill-content net-total">{{ _("Grand Total") }}</td>
|
||||
<td class="bill-content net-total text-right">{{ doc.get_formatted("grand_total") }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if cart_settings.enable_checkout %}
|
||||
<button class="btn btn-primary btn-place-order font-md w-100" type="button">
|
||||
{{ _('Place Order') }}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary btn-request-for-quotation font-md w-100" type="button">
|
||||
{{ _('Request for Quote') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Apply Coupon Dialog-->
|
||||
<!-- <script>
|
||||
frappe.ready(() => {
|
||||
$('.btn-coupon-code').click((e) => {
|
||||
const $btn = $(e.currentTarget);
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Coupons'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'coupons_area',
|
||||
fieldtype: 'HTML'
|
||||
}
|
||||
]
|
||||
});
|
||||
d.show();
|
||||
});
|
||||
});
|
||||
</script> -->
|
||||
@@ -1,22 +0,0 @@
|
||||
{% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
|
||||
|
||||
{% block navbar_right_extension %}
|
||||
<li class="shopping-cart cart-icon hidden">
|
||||
<a class="nav-link" href="/cart">
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
<span class="badge badge-primary shopping-badge" id="cart-count"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %}
|
||||
<li class="wishlist wishlist-icon hidden">
|
||||
<a class="nav-link" href="/wishlist">
|
||||
<svg class="icon icon-lg">
|
||||
<use href="#icon-heart"></use>
|
||||
</svg>
|
||||
<span class="badge badge-primary shopping-badge" id="wish-count"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,52 +0,0 @@
|
||||
{% from "erpnext/templates/includes/macros.html" import product_image %}
|
||||
|
||||
{% macro item_name_and_description(d) %}
|
||||
<div class="row item_name_and_description">
|
||||
<div class="col-xs-4 col-sm-2 order-image-col">
|
||||
<div class="order-image h-100">
|
||||
{% if d.thumbnail or d.image %}
|
||||
{{ product_image(d.thumbnail or d.image, no_border=True) }}
|
||||
{% else %}
|
||||
<div class="no-image-cart-item" style="min-height: 100px;">
|
||||
{{ frappe.utils.get_abbr(d.item_name) or "NA" }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-8 col-sm-10">
|
||||
{{ d.item_code }}
|
||||
<div class="text-muted small item-description">
|
||||
{{ html2text(d.description) | truncate(140) }}
|
||||
</div>
|
||||
<span class="text-muted mt-2 d-l-n order-qty">
|
||||
{{ _("Qty ") }}({{ d.get_formatted("qty") }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro item_name_and_description_cart(d) %}
|
||||
<div class="row item_name_dropdown">
|
||||
<div class="col-xs-4 col-sm-4 order-image-col">
|
||||
<div class="order-image">
|
||||
{{ product_image_square(d.thumbnail or d.image) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-8 col-sm-8">
|
||||
{{ d.item_name|truncate(25) }}
|
||||
<div class="input-group number-spinner">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-light cart-btn" data-dir="dwn">
|
||||
–</button>
|
||||
</span>
|
||||
<input class="form-control text-right cart-qty"
|
||||
value = "{{ d.get_formatted('qty') }}"
|
||||
data-item-code="{{ d.item_code }}">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-light cart-btn" data-dir="up">
|
||||
+</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,217 +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.e_commerce.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) {
|
||||
let hide_add_to_cart = !r.message.product_info.price
|
||||
|| (!r.message.product_info.in_stock && !r.message.cart_settings.allow_items_not_in_stock);
|
||||
$(".item-cart, .item-price, .item-stock").toggleClass('hide', hide_add_to_cart);
|
||||
}
|
||||
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 && r.message.cart_settings.show_stock_availability) {
|
||||
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 = Number.parseInt(oldValue) + 1;
|
||||
} else if (btn.attr('data-dir') == 'dwn') {
|
||||
if (Number.parseInt(oldValue) > 1) {
|
||||
newVal = Number.parseInt(oldValue) - 1;
|
||||
}
|
||||
else {
|
||||
newVal = Number.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;
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} {{ _("Shopping Cart") }} {% endblock %}
|
||||
|
||||
{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Shopping Cart") }}</h1>{% endblock %}
|
||||
|
||||
{% block header_actions %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
{% from "templates/includes/macros.html" import item_name_and_description %}
|
||||
|
||||
{% if doc.items %}
|
||||
<div class="cart-container">
|
||||
<div class="row m-0">
|
||||
<!-- Left section -->
|
||||
<div class="col-md-8">
|
||||
<div class="frappe-card p-5 mb-4">
|
||||
<div id="cart-error" class="alert alert-danger" style="display: none;"></div>
|
||||
<div class="cart-items-header">
|
||||
{{ _('Items') }}
|
||||
</div>
|
||||
<table class="table mt-3 cart-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="item-column">{{ _('Item') }}</th>
|
||||
<th width="20%">{{ _('Quantity') }}</th>
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<th width="20" class="text-right column-sm-view">{{ _('Subtotal') }}</th>
|
||||
{% endif %}
|
||||
<th width="10%" class="column-sm-view"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="cart-items">
|
||||
{% include "templates/includes/cart/cart_items.html" %}
|
||||
</tbody>
|
||||
|
||||
{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
|
||||
<tfoot class="cart-tax-items">
|
||||
{% include "templates/includes/cart/cart_items_total.html" %}
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-3">
|
||||
{% if cart_settings.enable_checkout %}
|
||||
<a class="btn btn-primary-light font-md" href="/orders">
|
||||
{{ _('Past Orders') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="btn btn-primary-light font-md" href="/quotations">
|
||||
{{ _('Past Quotes') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-9">
|
||||
{% if doc.items %}
|
||||
<div class="place-order-container">
|
||||
<a class="btn btn-primary-light mr-2 font-md" href="/all-products">
|
||||
{{ _('Continue Shopping') }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terms and Conditions -->
|
||||
{% if doc.items %}
|
||||
{% if doc.terms %}
|
||||
<div class="t-and-c-container mt-4 frappe-card">
|
||||
<h5>{{ _("Terms and Conditions") }}</h5>
|
||||
<div class="t-and-c-terms mt-2">
|
||||
{{ doc.terms }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Right section -->
|
||||
<div class="col-md-4">
|
||||
<div class="cart-payment-addresses">
|
||||
<!-- Apply Coupon Code -->
|
||||
{% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
|
||||
{% if show_coupon_code == 1%}
|
||||
<div class="mb-3">
|
||||
<div class="row no-gutters">
|
||||
<input type="text" class="txtcoupon form-control mr-3 w-50 font-md" placeholder="Enter Coupon Code" name="txtcouponcode" ></input>
|
||||
<button class="btn btn-primary btn-sm bt-coupon font-md">{{ _("Apply Coupon Code") }}</button>
|
||||
<input type="hidden" class="txtreferral_sales_partner font-md" placeholder="Enter Sales Partner" name="txtreferral_sales_partner" type="text"></input>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3 frappe-card p-5 payment-summary">
|
||||
{% include "templates/includes/cart/cart_payment_summary.html" %}
|
||||
</div>
|
||||
|
||||
{% include "templates/includes/cart/cart_address.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="cart-empty frappe-card">
|
||||
<div class="cart-empty-state">
|
||||
<img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty State">
|
||||
</div>
|
||||
<div class="cart-empty-message mt-4">{{ _('Your cart is Empty') }}</p>
|
||||
{% if cart_settings.enable_checkout %}
|
||||
<a class="btn btn-outline-primary" href="/orders" style="font-size: 16px;">
|
||||
{{ _('See past orders') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a class="btn btn-outline-primary" href="/quotations" style="font-size: 16px;">
|
||||
{{ _('See past quotations') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block base_scripts %}
|
||||
<!-- js should be loaded in body! -->
|
||||
{{ include_script("frappe-web.bundle.js") }}
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{% endblock %}
|
||||
@@ -1,303 +0,0 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// JS exclusive to /cart page
|
||||
frappe.provide("erpnext.e_commerce.shopping_cart");
|
||||
var shopping_cart = erpnext.e_commerce.shopping_cart;
|
||||
|
||||
$.extend(shopping_cart, {
|
||||
show_error: function(title, text) {
|
||||
$("#cart-container").html('<div class="msg-box"><h4>' +
|
||||
title + '</h4><p class="text-muted">' + text + '</p></div>');
|
||||
},
|
||||
|
||||
bind_events: function() {
|
||||
shopping_cart.bind_address_picker_dialog();
|
||||
shopping_cart.bind_place_order();
|
||||
shopping_cart.bind_request_quotation();
|
||||
shopping_cart.bind_change_qty();
|
||||
shopping_cart.bind_remove_cart_item();
|
||||
shopping_cart.bind_change_notes();
|
||||
shopping_cart.bind_coupon_code();
|
||||
},
|
||||
|
||||
bind_address_picker_dialog: function() {
|
||||
const d = this.get_update_address_dialog();
|
||||
this.parent.find('.btn-change-address').on('click', (e) => {
|
||||
const type = $(e.currentTarget).parents('.address-container').attr('data-address-type');
|
||||
$(d.get_field('address_picker').wrapper).html(
|
||||
this.get_address_template(type)
|
||||
);
|
||||
d.show();
|
||||
});
|
||||
},
|
||||
|
||||
get_update_address_dialog() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: "Select Address",
|
||||
fields: [{
|
||||
'fieldtype': 'HTML',
|
||||
'fieldname': 'address_picker',
|
||||
}],
|
||||
primary_action_label: __('Set Address'),
|
||||
primary_action: () => {
|
||||
const $card = d.$wrapper.find('.address-card.active');
|
||||
const address_type = $card.closest('[data-address-type]').attr('data-address-type');
|
||||
const address_name = $card.closest('[data-address-name]').attr('data-address-name');
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
|
||||
freeze: true,
|
||||
args: {
|
||||
address_type,
|
||||
address_name
|
||||
},
|
||||
callback: function(r) {
|
||||
d.hide();
|
||||
if (!r.exc) {
|
||||
$(".cart-tax-items").html(r.message.total);
|
||||
shopping_cart.parent.find(
|
||||
`.address-container[data-address-type="${address_type}"]`
|
||||
).html(r.message.address);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return d;
|
||||
},
|
||||
|
||||
get_address_template(type) {
|
||||
return {
|
||||
shipping: `<div class="mb-3" data-section="shipping-address">
|
||||
<div class="row no-gutters" data-fieldname="shipping_address_name">
|
||||
{% for address in shipping_addresses %}
|
||||
<div class="mr-3 mb-3 w-100" data-address-name="{{address.name}}" data-address-type="shipping"
|
||||
{% if doc.shipping_address_name == address.name %} data-active {% endif %}>
|
||||
{% include "templates/includes/cart/address_picker_card.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>`,
|
||||
billing: `<div class="mb-3" data-section="billing-address">
|
||||
<div class="row no-gutters" data-fieldname="customer_address">
|
||||
{% for address in billing_addresses %}
|
||||
<div class="mr-3 mb-3 w-100" data-address-name="{{address.name}}" data-address-type="billing"
|
||||
{% if doc.shipping_address_name == address.name %} data-active {% endif %}>
|
||||
{% include "templates/includes/cart/address_picker_card.html" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>`,
|
||||
}[type];
|
||||
},
|
||||
|
||||
bind_place_order: function() {
|
||||
$(".btn-place-order").on("click", function() {
|
||||
shopping_cart.place_order(this);
|
||||
});
|
||||
},
|
||||
|
||||
bind_request_quotation: function() {
|
||||
$('.btn-request-for-quotation').on('click', function() {
|
||||
shopping_cart.request_quotation(this);
|
||||
});
|
||||
},
|
||||
|
||||
bind_change_qty: function() {
|
||||
// bind update button
|
||||
$(".cart-items").on("change", ".cart-qty", function() {
|
||||
var item_code = $(this).attr("data-item-code");
|
||||
var newVal = $(this).val();
|
||||
shopping_cart.shopping_cart_update({item_code, qty: newVal});
|
||||
});
|
||||
|
||||
$(".cart-items").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 (oldValue > 1) {
|
||||
newVal = parseInt(oldValue) - 1;
|
||||
}
|
||||
}
|
||||
input.val(newVal);
|
||||
|
||||
let notes = input.closest("td").siblings().find(".notes").text().trim();
|
||||
var item_code = input.attr("data-item-code");
|
||||
shopping_cart.shopping_cart_update({
|
||||
item_code,
|
||||
qty: newVal,
|
||||
additional_notes: notes
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bind_remove_cart_item: function() {
|
||||
$(".cart-items").on("click", ".remove-cart-item", (e) => {
|
||||
const $remove_cart_item_btn = $(e.currentTarget);
|
||||
var item_code = $remove_cart_item_btn.data("item-code");
|
||||
|
||||
shopping_cart.shopping_cart_update({
|
||||
item_code: item_code,
|
||||
qty: 0
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render_tax_row: function($cart_taxes, doc, shipping_rules) {
|
||||
var shipping_selector;
|
||||
if(shipping_rules) {
|
||||
shipping_selector = '<select class="form-control">' + $.map(shipping_rules, function(rule) {
|
||||
return '<option value="' + rule[0] + '">' + rule[1] + '</option>' }).join("\n") +
|
||||
'</select>';
|
||||
}
|
||||
|
||||
var $tax_row = $(repl('<div class="row">\
|
||||
<div class="col-md-9 col-sm-9">\
|
||||
<div class="row">\
|
||||
<div class="col-md-9 col-md-offset-3">' +
|
||||
(shipping_selector || '<p>%(description)s</p>') +
|
||||
'</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="col-md-3 col-sm-3 text-right">\
|
||||
<p' + (shipping_selector ? ' style="margin-top: 5px;"' : "") + '>%(formatted_tax_amount)s</p>\
|
||||
</div>\
|
||||
</div>', doc)).appendTo($cart_taxes);
|
||||
|
||||
if(shipping_selector) {
|
||||
$tax_row.find('select option').each(function(i, opt) {
|
||||
if($(opt).html() == doc.description) {
|
||||
$(opt).attr("selected", "selected");
|
||||
}
|
||||
});
|
||||
$tax_row.find('select').on("change", function() {
|
||||
shopping_cart.apply_shipping_rule($(this).val(), this);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
apply_shipping_rule: function(rule, btn) {
|
||||
return frappe.call({
|
||||
btn: btn,
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.apply_shipping_rule",
|
||||
args: { shipping_rule: rule },
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
shopping_cart.render(r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
place_order: function(btn) {
|
||||
shopping_cart.freeze();
|
||||
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.place_order",
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
shopping_cart.unfreeze();
|
||||
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 {
|
||||
$(btn).hide();
|
||||
window.location.href = '/orders/' + encodeURIComponent(r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
request_quotation: function(btn) {
|
||||
shopping_cart.freeze();
|
||||
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation",
|
||||
btn: btn,
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
shopping_cart.unfreeze();
|
||||
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 {
|
||||
$(btn).hide();
|
||||
window.location.href = '/quotations/' + encodeURIComponent(r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
bind_coupon_code: function() {
|
||||
$(".bt-coupon").on("click", function() {
|
||||
shopping_cart.apply_coupon_code(this);
|
||||
});
|
||||
},
|
||||
|
||||
apply_coupon_code: function(btn) {
|
||||
return frappe.call({
|
||||
type: "POST",
|
||||
method: "erpnext.e_commerce.shopping_cart.cart.apply_coupon_code",
|
||||
btn: btn,
|
||||
args : {
|
||||
applied_code : $('.txtcoupon').val(),
|
||||
applied_referral_sales_partner: $('.txtreferral_sales_partner').val()
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r && r.message){
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ready(function() {
|
||||
if (window.location.pathname === "/cart") {
|
||||
$(".cart-icon").hide();
|
||||
}
|
||||
shopping_cart.parent = $(".cart-container");
|
||||
shopping_cart.bind_events();
|
||||
});
|
||||
|
||||
function show_terms() {
|
||||
var html = $(".cart-terms").html();
|
||||
frappe.msgprint(html);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
no_cache = 1
|
||||
|
||||
from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.body_class = "product-page"
|
||||
context.update(get_cart_quotation())
|
||||
@@ -1,67 +0,0 @@
|
||||
{% extends "templates/web.html" %}
|
||||
{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %}
|
||||
|
||||
{% block title %} {{ _("Customer Reviews") }} {% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="product-container reviews-full-page col-md-12">
|
||||
{% if enable_reviews %}
|
||||
<!-- Title and Action -->
|
||||
<div class="w-100 mb-6 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="{{ web_item }}">
|
||||
{{ _("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 not reviews | len >= total_reviews %}
|
||||
<button class="btn btn-light btn-view-more mr-2 mt-4 mb-4 w-30"
|
||||
data-web-item="{{ web_item }}">
|
||||
{{ _("View More") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<h6 class="text-muted mt-6">
|
||||
{{ _("No Reviews") }}
|
||||
</h6>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- If reviews are disabled -->
|
||||
<div class="text-center">
|
||||
<h3 class="text-muted mt-8">
|
||||
{{ _("No Reviews") }}
|
||||
</h3>
|
||||
</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>
|
||||
<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 %}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import frappe
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||
get_shopping_cart_settings,
|
||||
)
|
||||
from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import check_if_user_is_customer
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.body_class = "product-page"
|
||||
context.no_cache = 1
|
||||
context.full_page = True
|
||||
context.reviews = None
|
||||
|
||||
if frappe.form_dict and frappe.form_dict.get("web_item"):
|
||||
context.web_item = frappe.form_dict.get("web_item")
|
||||
context.user_is_customer = check_if_user_is_customer()
|
||||
context.enable_reviews = get_shopping_cart_settings().enable_reviews
|
||||
|
||||
if context.enable_reviews:
|
||||
reviews_data = get_item_reviews(context.web_item)
|
||||
context.update(reviews_data)
|
||||
@@ -17,9 +17,6 @@
|
||||
<h3 class="d-block d-sm-none">{{ homepage.description }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<a href="{{ explore_link }}" class="mb-5 btn btn-primary">{{ _('Explore') }}</a>
|
||||
</div>
|
||||
</section>
|
||||
{% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
|
||||
<section class="hero-section">
|
||||
@@ -29,26 +26,6 @@
|
||||
{{ render_homepage_section(homepage.hero_section_doc) }}
|
||||
{% endif %}
|
||||
|
||||
{% if homepage.products %}
|
||||
<section class="container section-products my-5">
|
||||
<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">
|
||||
<img class="card-img-top website-image-extra-large" src="{{ item.image }}" loading="lazy" alt="{{ item.item_name }}"></img>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if blogs %}
|
||||
<section class="container my-5">
|
||||
<h3>{{ _('Publications') }}</h3>
|
||||
|
||||
@@ -10,11 +10,6 @@ no_cache = 1
|
||||
def get_context(context):
|
||||
homepage = frappe.get_cached_doc("Homepage")
|
||||
|
||||
for item in homepage.products:
|
||||
route = frappe.db.get_value("Website Item", {"item_code": item.item_code}, "route")
|
||||
if route:
|
||||
item.route = "/" + route
|
||||
|
||||
homepage.title = homepage.title or homepage.company
|
||||
context.title = homepage.title
|
||||
context.homepage = homepage
|
||||
@@ -52,5 +47,3 @@ def get_context(context):
|
||||
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"
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ready(function(){
|
||||
|
||||
var loyalty_points_input = document.getElementById("loyalty-point-to-redeem");
|
||||
var loyalty_points_status = document.getElementById("loyalty-points-status");
|
||||
if (loyalty_points_input) {
|
||||
loyalty_points_input.onblur = apply_loyalty_points;
|
||||
}
|
||||
|
||||
function apply_loyalty_points() {
|
||||
var loyalty_points = parseInt(loyalty_points_input.value);
|
||||
if (loyalty_points) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor",
|
||||
args: {
|
||||
"customer": doc_info.customer
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r) {
|
||||
var message = ""
|
||||
let loyalty_amount = flt(r.message*loyalty_points);
|
||||
if (doc_info.grand_total && doc_info.grand_total < loyalty_amount) {
|
||||
let redeemable_amount = parseInt(doc_info.grand_total/r.message);
|
||||
message = "You can only redeem max " + redeemable_amount + " points in this order.";
|
||||
frappe.msgprint(__(message));
|
||||
} else {
|
||||
message = loyalty_points + " Loyalty Points of amount "+ loyalty_amount + " is applied."
|
||||
frappe.msgprint(__(message));
|
||||
var remaining_amount = flt(doc_info.grand_total) - flt(loyalty_amount);
|
||||
var payment_button = document.getElementById("pay-for-order");
|
||||
payment_button.innerHTML = __("Pay Remaining");
|
||||
payment_button.href = "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn="+doc_info.doctype_name+"&dt="+doc_info.doctype+"&loyalty_points="+loyalty_points+"&submit_doc=1&order_type=Shopping Cart";
|
||||
}
|
||||
loyalty_points_status.innerHTML = message;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -4,8 +4,6 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
@@ -14,17 +12,12 @@ def get_context(context):
|
||||
if hasattr(context.doc, "set_indicator"):
|
||||
context.doc.set_indicator()
|
||||
|
||||
if show_attachments():
|
||||
context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name)
|
||||
|
||||
context.parents = frappe.form_dict.parents
|
||||
context.title = frappe.form_dict.name
|
||||
context.payment_ref = frappe.db.get_value(
|
||||
"Payment Request", {"reference_name": frappe.form_dict.name}, "name"
|
||||
)
|
||||
|
||||
context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout
|
||||
|
||||
default_print_format = frappe.db.get_value(
|
||||
"Property Setter",
|
||||
dict(property="default_print_format", doc_type=frappe.form_dict.doctype),
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} {{ _("Product Search") }} {% endblock %}
|
||||
|
||||
{% block header %}<h2>{{ _("Product Search") }}</h2>{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<script>{% include "templates/includes/product_list.js" %}</script>
|
||||
|
||||
<script>
|
||||
frappe.ready(function() {
|
||||
var txt = frappe.utils.get_url_arg("search");
|
||||
$(".search-results").html('{{ _("Search results for") + ": " + html2text(frappe.form_dict.search or "") | e | trim }}');
|
||||
window.search = txt;
|
||||
window.start = 0;
|
||||
window.get_product_list();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="product-search-content">
|
||||
<h3 class="search-results">{{ _("Search Results") }}</h3>
|
||||
<div id="search-list" class="row">
|
||||
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="more-btn"
|
||||
style="display: none; text-align: center;">
|
||||
<button class="btn btn-light">{{ _("More...") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,152 +0,0 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cint, cstr
|
||||
from redis.commands.search.query import Query
|
||||
|
||||
from erpnext.e_commerce.redisearch_utils import (
|
||||
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
|
||||
WEBSITE_ITEM_INDEX,
|
||||
WEBSITE_ITEM_NAME_AUTOCOMPLETE,
|
||||
is_redisearch_enabled,
|
||||
)
|
||||
from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
|
||||
|
||||
no_cache = 1
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.show_search = True
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_product_list(search=None, start=0, limit=12):
|
||||
data = get_product_data(search, start, limit)
|
||||
|
||||
for item in data:
|
||||
set_product_info_for_website(item)
|
||||
|
||||
return [get_item_for_list_in_html(r) for r in data]
|
||||
|
||||
|
||||
def get_product_data(search=None, start=0, limit=12):
|
||||
# limit = 12 because we show 12 items in the grid view
|
||||
# base query
|
||||
query = """
|
||||
SELECT
|
||||
web_item_name, item_name, item_code, brand, route,
|
||||
website_image, thumbnail, item_group,
|
||||
description, web_long_description as website_description,
|
||||
website_warehouse, ranking
|
||||
FROM `tabWebsite Item`
|
||||
WHERE published = 1
|
||||
"""
|
||||
|
||||
# search term condition
|
||||
if search:
|
||||
query += """ and (item_name like %(search)s
|
||||
or web_item_name like %(search)s
|
||||
or brand like %(search)s
|
||||
or web_long_description like %(search)s)"""
|
||||
search = "%" + cstr(search) + "%"
|
||||
|
||||
# order by
|
||||
query += """ ORDER BY ranking desc, modified desc limit %s offset %s""" % (
|
||||
cint(limit),
|
||||
cint(start),
|
||||
)
|
||||
|
||||
return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def search(query):
|
||||
product_results = product_search(query)
|
||||
category_results = get_category_suggestions(query)
|
||||
|
||||
return {
|
||||
"product_results": product_results.get("results") or [],
|
||||
"category_results": category_results.get("results") or [],
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def product_search(query, limit=10, fuzzy_search=True):
|
||||
search_results = {"from_redisearch": True, "results": []}
|
||||
|
||||
if not is_redisearch_enabled():
|
||||
# Redisearch module not enabled
|
||||
search_results["from_redisearch"] = False
|
||||
search_results["results"] = get_product_data(query, 0, limit)
|
||||
return search_results
|
||||
|
||||
if not query:
|
||||
return search_results
|
||||
|
||||
redis = frappe.cache()
|
||||
query = clean_up_query(query)
|
||||
|
||||
# TODO: Check perf/correctness with Suggestions & Query vs only Query
|
||||
# TODO: Use Levenshtein Distance in Query (max=3)
|
||||
redisearch = redis.ft(WEBSITE_ITEM_INDEX)
|
||||
suggestions = redisearch.sugget(
|
||||
WEBSITE_ITEM_NAME_AUTOCOMPLETE,
|
||||
query,
|
||||
num=limit,
|
||||
fuzzy=fuzzy_search and len(query) > 3,
|
||||
)
|
||||
|
||||
# Build a query
|
||||
query_string = query
|
||||
|
||||
for s in suggestions:
|
||||
query_string += f"|('{clean_up_query(s.string)}')"
|
||||
|
||||
q = Query(query_string)
|
||||
results = redisearch.search(q)
|
||||
|
||||
search_results["results"] = list(map(convert_to_dict, results.docs))
|
||||
search_results["results"] = sorted(
|
||||
search_results["results"], key=lambda k: frappe.utils.cint(k["ranking"]), reverse=True
|
||||
)
|
||||
|
||||
return search_results
|
||||
|
||||
|
||||
def clean_up_query(query):
|
||||
return "".join(c for c in query if c.isalnum() or c.isspace())
|
||||
|
||||
|
||||
def convert_to_dict(redis_search_doc):
|
||||
return redis_search_doc.__dict__
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_category_suggestions(query):
|
||||
search_results = {"results": []}
|
||||
|
||||
if not is_redisearch_enabled():
|
||||
# Redisearch module not enabled, query db
|
||||
categories = frappe.db.get_all(
|
||||
"Item Group",
|
||||
filters={"name": ["like", "%{0}%".format(query)], "show_in_website": 1},
|
||||
fields=["name", "route"],
|
||||
)
|
||||
search_results["results"] = categories
|
||||
return search_results
|
||||
|
||||
if not query:
|
||||
return search_results
|
||||
|
||||
ac = frappe.cache().ft()
|
||||
suggestions = ac.sugget(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, query, num=10, with_payloads=True)
|
||||
|
||||
results = [json.loads(s.payload) for s in suggestions]
|
||||
|
||||
search_results["results"] = results
|
||||
|
||||
return search_results
|
||||
@@ -1,28 +0,0 @@
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %} {{ _("Wishlist") }} {% endblock %}
|
||||
|
||||
{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Wishlist") }}</h1>{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
{% if items %}
|
||||
<div class="row">
|
||||
<div class="col-md-12 item-card-group-section">
|
||||
<div class="row products-list">
|
||||
{% from "erpnext/templates/includes/macros.html" import wishlist_card %}
|
||||
{% for item in items %}
|
||||
{{ wishlist_card(item, settings) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="cart-empty frappe-card">
|
||||
<div class="cart-empty-state">
|
||||
<img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
|
||||
</div>
|
||||
<div class="cart-empty-message mt-4">{{ _('Wishlist is empty!') }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,81 +0,0 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import frappe
|
||||
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
||||
get_shopping_cart_settings,
|
||||
)
|
||||
from erpnext.e_commerce.shopping_cart.cart import _set_price_list
|
||||
from erpnext.utilities.product import get_price
|
||||
|
||||
|
||||
def get_context(context):
|
||||
is_guest = frappe.session.user == "Guest"
|
||||
|
||||
settings = get_shopping_cart_settings()
|
||||
items = get_wishlist_items() if not is_guest else []
|
||||
selling_price_list = _set_price_list(settings) if not is_guest else None
|
||||
|
||||
items = set_stock_price_details(items, settings, selling_price_list)
|
||||
|
||||
context.body_class = "product-page"
|
||||
context.items = items
|
||||
context.settings = settings
|
||||
context.no_cache = 1
|
||||
|
||||
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
|
||||
if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1:
|
||||
warehouses = get_child_warehouses(warehouse)
|
||||
else:
|
||||
warehouses = [warehouse] if warehouse else []
|
||||
|
||||
stock_qty = 0.0
|
||||
for warehouse in warehouses:
|
||||
stock_qty += frappe.utils.flt(
|
||||
frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
|
||||
)
|
||||
|
||||
return bool(stock_qty)
|
||||
|
||||
|
||||
def get_wishlist_items():
|
||||
if not frappe.db.exists("Wishlist", frappe.session.user):
|
||||
return []
|
||||
|
||||
return frappe.db.get_all(
|
||||
"Wishlist Item",
|
||||
filters={"parent": frappe.session.user},
|
||||
fields=[
|
||||
"web_item_name",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"website_item",
|
||||
"warehouse",
|
||||
"image",
|
||||
"item_group",
|
||||
"route",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def set_stock_price_details(items, settings, selling_price_list):
|
||||
for item in items:
|
||||
if settings.show_stock_availability:
|
||||
item.available = get_stock_availability(item.item_code, item.get("warehouse"))
|
||||
|
||||
price_details = get_price(
|
||||
item.item_code, selling_price_list, settings.default_customer_group, settings.company
|
||||
)
|
||||
|
||||
if price_details:
|
||||
item.formatted_price = price_details.get("formatted_price")
|
||||
item.formatted_mrp = price_details.get("formatted_mrp")
|
||||
if item.formatted_mrp:
|
||||
item.discount = price_details.get("formatted_discount_percent") or price_details.get(
|
||||
"formatted_discount_rate"
|
||||
)
|
||||
|
||||
return items
|
||||
Reference in New Issue
Block a user