feat: shopping cart page
diff --git a/erpnext/public/images/ui-states/cart-empty-state.png b/erpnext/public/images/ui-states/cart-empty-state.png
new file mode 100644
index 0000000..e1ead0e
--- /dev/null
+++ b/erpnext/public/images/ui-states/cart-empty-state.png
Binary files differ
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 16a19ad..7bce6ab 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -5,6 +5,19 @@
background: var(--gray-50);
}
+
+.item-breadcrumbs {
+ .breadcrumb-container {
+ ol.breadcrumb {
+ background-color: var(--gray-50) !important;
+ }
+
+ a {
+ color: var(--gray-900);
+ }
+ }
+}
+
.carousel-control {
height: 42px;
width: 42px;
@@ -77,6 +90,18 @@
margin-top: 1.25rem;
}
+ .no-image {
+ @include flex(flex, center, center, null);
+ height: 200px;
+ margin: 0 auto;
+ margin-top: var(--margin-xl);
+ background: var(--gray-100);
+ width: 80%;
+ border-radius: var(--border-radius);
+ font-size: 2rem;
+ color: var(--gray-500);
+ }
+
.product-title {
font-size: 14px;
color: var(--gray-800);
@@ -98,7 +123,7 @@
.product-category {
font-size: 13px;
- color: var(--gray-600);
+ color: var(--text-muted);
margin: var(--margin-sm) 0;
}
@@ -159,6 +184,15 @@
@include card($padding: var(--padding-md));
min-height: 70vh;
+ .product-details {
+ max-width: 40%;
+ margin-left: -30px;
+
+ .btn-add-to-cart {
+ font-size: var(--text-base);
+ }
+ }
+
.product-title {
font-size: 24px;
font-weight: 600;
@@ -166,7 +200,7 @@
}
.product-code {
- color: var(--gray-600);
+ color: var(--text-muted);
font-size: 13px;
}
@@ -180,15 +214,15 @@
padding: 15px;
@include media-breakpoint-between(xs, md) {
- height: 320px;
- width: 320px;
+ height: 300px;
+ width: 300px;
}
@include media-breakpoint-up(lg) {
- height: 430px;
- width: 420px;
+ height: 350px;
+ width: 350px;
}
-
+
img {
object-fit: contain;
}
@@ -202,13 +236,13 @@
@include media-breakpoint-up(lg) {
max-height: 430px;
}
-
+
overflow: scroll;
}
.item-slideshow-image {
height: 4rem;
- width: 4rem;
+ width: 6rem;
object-fit: contain;
padding: 0.5rem;
border: 1px solid var(--table-border-color);
@@ -227,25 +261,57 @@
font-weight: 600;
.formatted-price {
- color: var(--gray-600);
- font-size: 14px;
+ color: var(--text-muted);
+ font-size: var(--text-base);
}
}
.no-stock {
- font-size: 14px;
+ font-size: var(--text-base);
}
}
+}
- .item-breadcrumbs {
- .breadcrumb-container {
- margin: 0px;
- padding: 0px;
+.item-configurator-dialog {
+ .modal-header {
+ padding: var(--padding-md) var(--padding-xl);
+ }
- a {
- color: $text-muted;
+ .modal-body {
+ padding: 0 var(--padding-xl);
+ padding-bottom: var(--padding-xl);
+
+ .status-area {
+ .alert {
+ padding: var(--padding-xs) var(--padding-sm);
+ font-size: var(--text-sm);
}
}
+
+ .form-layout {
+ max-height: 50vh;
+ overflow-y: auto;
+ }
+
+ .section-body {
+ .form-column {
+ .form-group {
+ .control-label {
+ font-size: var(--text-md);
+ color: var(--gray-700);
+ }
+
+ .help-box {
+ margin-top: 2px;
+ font-size: var(--text-sm);
+ }
+ }
+ }
+ }
+
+ svg {
+ display: none;
+ }
}
}
@@ -257,4 +323,168 @@
.carousel-inner.rounded-carousel {
border-radius: $card-border-radius;
}
-}
\ No newline at end of file
+}
+
+.cart-icon {
+ .cart-badge {
+ position: relative;
+ top: -10px;
+ left: -12px;
+ background: var(--red-600);
+ width: 16px;
+ align-items: center;
+ height: 16px;
+ font-size: 10px;
+ border-radius: 50%;
+ }
+}
+
+
+#page-cart {
+ .shopping-cart-header {
+ font-weight: bold;
+ }
+
+ .cart-container {
+ color: var(--text-color);
+
+ .frappe-card {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ }
+
+ .cart-items-header {
+ font-weight: 600;
+ }
+
+ .cart-table {
+ th, tr, td {
+ border-color: var(--border-color);
+ border-width: 1px;
+ }
+
+ th {
+ font-weight: normal;
+ font-size: 13px;
+ color: var(--text-muted);
+ padding: var(--padding-sm) 0;
+ }
+
+ td {
+ padding: var(--padding-sm) 0;
+ color: var(--text-color);
+ }
+
+ .cart-items {
+ .item-title {
+ font-size: var(--text-base);
+ font-weight: 500;
+ color: var(--text-color);
+ }
+
+ .item-subtitle {
+ color: var(--text-muted);
+ font-size: var(--text-md);
+ }
+
+ .item-subtotal {
+ font-size: var(--text-base);
+ font-weight: 500;
+ }
+
+ .item-rate {
+ font-size: var(--text-md);
+ color: var(--text-muted);
+ }
+
+ textarea {
+ width: 40%;
+ }
+ }
+
+ .cart-tax-items {
+ .item-grand-total {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-color);
+ }
+ }
+ }
+
+ .cart-addresses {
+ hr {
+ border-color: var(--border-color);
+ }
+ }
+
+ .number-spinner {
+ width: 75%;
+ .cart-btn {
+ border: none;
+ background: var(--gray-100);
+ box-shadow: none;
+ height: 28px;
+ align-items: center;
+ display: flex;
+ }
+
+ .cart-qty {
+ height: 28px;
+ font-size: var(--text-md);
+ }
+ }
+
+ .place-order-container {
+ .btn-place-order {
+ width: 62%;
+ }
+ }
+ }
+}
+
+.cart-empty.frappe-card {
+ min-height: 76vh;
+ @include flex(flex, center, center, column);
+
+ .cart-empty-message {
+ font-size: 18px;
+ color: var(--text-color);
+ font-weight: bold;
+ }
+}
+
+.address-card {
+ .card-title {
+ font-size: var(--text-base);
+ font-weight: 500;
+ }
+
+ .card-text {
+ font-size: var(--text-md);
+ color: var(--gray-700);
+ }
+
+ .card-link {
+ font-size: var(--text-md);
+
+ svg use {
+ stroke: var(--blue-500);
+ }
+ }
+
+ .btn-change-address {
+ color: var(--blue-500);
+ box-shadow: none;
+ border: 1px solid var(--blue-500);
+ }
+}
+
+.modal .address-card {
+ .card-body {
+ padding: var(--padding-sm);
+ border-radius: var(--border-radius);
+ border: 1px solid var(--dark-border-color);
+ }
+}
+
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index c2549fe..fa9dced 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -42,15 +42,31 @@
return {
"doc": decorate_quotation_doc(doc),
- "shipping_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
- for address in addresses if address.address_type == "Shipping"],
- "billing_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
- for address in addresses if address.address_type == "Billing"],
+ "shipping_addresses": get_shipping_addresses(party),
+ "billing_addresses": get_billing_addresses(party),
"shipping_rules": get_applicable_shipping_rules(party),
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
}
@frappe.whitelist()
+def get_shipping_addresses(party=None):
+ if not party:
+ party = get_party()
+ addresses = get_address_docs(party=party)
+ return [{"name": address.name, "title": address.address_title, "display": address.display}
+ for address in addresses if address.address_type == "Shipping"
+ ]
+
+@frappe.whitelist()
+def get_billing_addresses(party=None):
+ if not party:
+ party = get_party()
+ addresses = get_address_docs(party=party)
+ return [{"name": address.name, "title": address.address_title, "display": address.display}
+ for address in addresses if address.address_type == "Billing"
+ ]
+
+@frappe.whitelist()
def place_order():
quotation = _get_cart_quotation()
cart_settings = frappe.db.get_value("Shopping Cart Settings", None,
@@ -203,27 +219,33 @@
@frappe.whitelist()
def update_cart_address(address_type, address_name):
quotation = _get_cart_quotation()
- address_display = get_address_display(frappe.get_doc("Address", address_name).as_dict())
+ address_doc = frappe.get_doc("Address", address_name).as_dict()
+ address_display = get_address_display(address_doc)
if address_type.lower() == "billing":
quotation.customer_address = address_name
quotation.address_display = address_display
quotation.shipping_address_name == quotation.shipping_address_name or address_name
+ address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None)
elif address_type.lower() == "shipping":
quotation.shipping_address_name = address_name
quotation.shipping_address = address_display
quotation.customer_address == quotation.customer_address or address_name
-
+ address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None)
apply_cart_settings(quotation=quotation)
quotation.flags.ignore_permissions = True
quotation.save()
context = get_cart_quotation(quotation)
+ context['address'] = address_doc
+
return {
"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
context),
- }
+ "address": frappe.render_template("templates/includes/cart/address_card.html",
+ context)
+ }
def guess_territory():
territory = None
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index c6dfd35..0de0267 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -14,7 +14,7 @@
},
bind_events: function() {
- shopping_cart.bind_address_select();
+ shopping_cart.bind_address_picker_dialog();
shopping_cart.bind_place_order();
shopping_cart.bind_request_quotation();
shopping_cart.bind_change_qty();
@@ -23,28 +23,76 @@
shopping_cart.bind_coupon_code();
},
- bind_address_select: function() {
- $(".cart-addresses").on('click', '.address-card', function(e) {
- const $card = $(e.currentTarget);
- const address_type = $card.closest('[data-address-type]').attr('data-address-type');
- const address_name = $card.closest('[data-address-name]').attr('data-address-name');
- return frappe.call({
- type: "POST",
- method: "erpnext.shopping_cart.cart.update_cart_address",
- freeze: true,
- args: {
- address_type,
- address_name
- },
- callback: function(r) {
- if(!r.exc) {
- $(".cart-tax-items").html(r.message.taxes);
- }
- }
- });
+ 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() {
+ return 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.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.taxes);
+ shopping_cart.parent.find(
+ `.address-container[data-address-type="${address_type}"]`
+ ).html(r.message.address);
+ }
+ }
+ });
+ }
+ });
+ },
+
+ 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);
@@ -221,6 +269,7 @@
frappe.ready(function() {
$(".cart-icon").hide();
+ shopping_cart.parent = $(".cart-container");
shopping_cart.bind_events();
});
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
index 646210e..667144b 100644
--- a/erpnext/templates/includes/cart/address_card.html
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -1,12 +1,17 @@
<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 class="btn btn-sm btn-default btn-change-address" style="position: absolute; right: 0; top: 0;">
+ {{ _('Change') }}
</div>
- <div class="card-body">
- <h5 class="card-title">{{ address.title }}</h5>
- <p class="card-text text-muted">
+ <div class="card-body p-0">
+ <div class="card-title">{{ address.title }}</div>
+ <div class="card-text mb-2">
{{ address.display }}
- </p>
- <a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
+ </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>
diff --git a/erpnext/templates/includes/cart/address_picker_card.html b/erpnext/templates/includes/cart/address_picker_card.html
new file mode 100644
index 0000000..2334ea2
--- /dev/null
+++ b/erpnext/templates/includes/cart/address_picker_card.html
@@ -0,0 +1,12 @@
+<div class="card address-card h-100">
+ <div class="check" style="position: absolute; right: 15px; top: 15px;">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
+ </div>
+ <div class="card-body">
+ <h5 class="card-title">{{ address.title }}</h5>
+ <p class="card-text text-muted">
+ {{ address.display }}
+ </p>
+ <a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
+ </div>
+</div>
\ No newline at end of file
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index aa25c88..3c8267c 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -14,31 +14,39 @@
</div>
</div>
{% endif %}
-<div class="mb-3" data-section="shipping-address">
- <h6 class="text-uppercase">{{ _("Shipping Address") }}</h6>
+<div class="mb-3 frappe-card p-5" data-section="shipping-address">
+ <h6>{{ _("Shipping Address") }}</h6>
+ <hr>
+ {% for address in shipping_addresses %}
+ {% if doc.shipping_address_name == address.name %}
<div class="row no-gutters" data-fieldname="shipping_address_name">
- {% for address in shipping_addresses %}
- <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" data-address-type="shipping" {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
- {% include "templates/includes/cart/address_card.html" %}
- </div>
- {% endfor %}
+ <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>
-<div class="mb-3" data-section="billing-address">
- <h6 class="text-uppercase">{{ _("Billing Address") }}</h6>
- <div class="row no-gutters" data-fieldname="customer_address">
- {% for address in billing_addresses %}
- <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" data-address-type="billing" {% if doc.customer_address == address.name %} data-active {% endif %}>
- {% include "templates/includes/cart/address_card.html" %}
- </div>
- {% endfor %}
- </div>
+<div class="checkbox ml-1 mb-2">
+ <label for="input_same_billing">
+ <input type="checkbox" id="input_same_billing" checked>
+ <span class="label-area">{{ _('Billing Address is same as Shipping Address') }}</span>
+ </label>
</div>
-<div class="custom-control custom-checkbox">
- <input type="checkbox" class="custom-control-input" id="input_same_billing" checked>
- <label class="custom-control-label" for="input_same_billing">{{ _('Billing Address is same as Shipping Address') }}</label>
+<div class="mb-3 frappe-card p-5" data-section="billing-address">
+ <h6>{{ _("Billing Address") }}</h6>
+ <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>
-<button class="btn btn-outline-primary btn-sm mt-3 btn-new-address">{{ _("Add a new address") }}</button>
+<button class="btn btn-outline-primary btn-sm mt-1 btn-new-address bg-white">{{ _("Add a new address") }}</button>
<script>
frappe.ready(() => {
@@ -55,6 +63,7 @@
});
$('.btn-new-address').click(() => {
+ console.log('clicked');
const d = new frappe.ui.Dialog({
title: __('New Address'),
fields: [
@@ -168,5 +177,12 @@
function toggle_billing_address_section(flag) {
$('[data-section="billing-address"]').toggle(flag);
}
+
+ $('.btn-change-address').click(() => {
+ // const d = new frappe.ui.Dialog({
+ // })
+
+ // d.show();
+ });
});
</script>
diff --git a/erpnext/templates/includes/cart/cart_address_picker.html b/erpnext/templates/includes/cart/cart_address_picker.html
new file mode 100644
index 0000000..72cc5f5
--- /dev/null
+++ b/erpnext/templates/includes/cart/cart_address_picker.html
@@ -0,0 +1,4 @@
+<div class="mb-3 frappe-card p-5" data-section="shipping-address">
+ <h6>{{ _("Shipping Address") }}</h6>
+</div>
+
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
index ca5744b..75441c4 100644
--- a/erpnext/templates/includes/cart/cart_items.html
+++ b/erpnext/templates/includes/cart/cart_items.html
@@ -1,15 +1,15 @@
{% for d in doc.items %}
<tr data-name="{{ d.name }}">
<td>
- <div class="font-weight-bold">
+ <div class="item-title mb-1">
{{ d.item_name }}
</div>
- <div>
+ <div class="item-subtitle">
{{ d.item_code }}
</div>
{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
{% if variant_of %}
- <span class="text-muted">
+ <span class="item-subtitle">
{{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
</span>
{% endif %}
@@ -20,20 +20,20 @@
<td class="text-right">
<div class="input-group number-spinner">
<span class="input-group-prepend d-none d-sm-inline-block">
- <button class="btn btn-outline-secondary cart-btn" data-dir="dwn">–</button>
+ <button class="btn cart-btn" data-dir="dwn">–</button>
</span>
- <input class="form-control text-right cart-qty border-secondary" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
+ <input class="form-control text-center cart-qty" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
<span class="input-group-append d-none d-sm-inline-block">
- <button class="btn btn-outline-secondary cart-btn" data-dir="up">+</button>
+ <button class="btn cart-btn" data-dir="up">+</button>
</span>
</div>
</td>
{% if cart_settings.enable_checkout %}
- <td class="text-right">
+ <td class="text-right item-subtotal">
<div>
{{ d.get_formatted('amount') }}
</div>
- <span class="text-muted">
+ <span class="item-rate">
{{ _('Rate:') }} {{ d.get_formatted('rate') }}
</span>
</td>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 74c84ba..0adfbc9 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -90,6 +90,10 @@
<div class="card {{ align_items_class }}">
{% if image %}
<img class="card-img" src="{{ image }}" alt="{{ title }}">
+ {% else %}
+ <div class="card-img-top no-image">
+ {{ frappe.utils.get_abbr(title) }}
+ </div>
{% endif %}
{{ item_card_body(title, description, url, rate, category, is_featured, align) }}
</div>
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index 4daf0e7..133d99e 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -2,9 +2,11 @@
{% block navbar_right_extension %}
<li class="shopping-cart cart-icon hidden">
- <a href="/cart" class="nav-link">
- {{ _("Cart") }}
- <span class="badge badge-primary" id="cart-count"></span>
+ <a class="nav-link" href="/cart">
+ <svg class="icon icon-lg">
+ <use href="#icon-assets"></use>
+ </svg>
+ <span class="badge badge-primary cart-badge" id="cart-count"></span>
</a>
</li>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index ebec838..d2c458e 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -29,12 +29,12 @@
{{ _("Discount") }}
</th>
<th class="text-right tot_quotation_discount">
- {% set tot_quotation_discount = [] %}
- {%- for item in doc.items -%}
- {% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
- * item.discount_percentage) / 100)) %}{% endif %}
- {% endfor %}
- {{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
+ {% set tot_quotation_discount = [] %}
+ {%- for item in doc.items -%}
+ {% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
+ * item.discount_percentage) / 100)) %}{% endif %}
+ {% endfor %}
+ {{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
</th>
</tr>
{% endif %}
@@ -47,51 +47,52 @@
{{ _("Total Amount") }}
</th>
<th class="text-right">
- <span>
- {% set total_amount = [] %}
- {%- for item in doc.items -%}
- {% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %}
- {% endfor %}
- {{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }}
- </span>
+ <span>
+ {% set total_amount = [] %}
+ {%- for item in doc.items -%}
+ {% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %}
+ {% endfor %}
+ {{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }}
+ </span>
</th>
</tr>
<tr>
- <th class="text-right" colspan="2">
- {{ _("Applied Coupon Code") }}
- </th>
- <th class="text-right">
- <span>
- {%- for row in frappe.get_all(doctype="Coupon Code",
- fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
- <span>{{ row.coupon_code }}</span>
- {% endfor %}
- </span>
- </th>
+ <th class="text-right" colspan="2">
+ {{ _("Applied Coupon Code") }}
+ </th>
+ <th class="text-right">
+ <span>
+ {%- for row in frappe.get_all(doctype="Coupon Code",
+ fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
+ <span>{{ row.coupon_code }}</span>
+ {% endfor %}
+ </span>
+ </th>
</tr>
<tr>
- <th class="text-right" colspan="2">
- {{ _("Discount") }}
- </th>
- <th class="text-right">
- <span>
- {% set tot_SO_discount = [] %}
- {%- for item in doc.items -%}
- {% if tot_SO_discount.append((((item.price_list_rate * item.qty)
- * item.discount_percentage) / 100)) %}{% endif %}
- {% endfor %}
- {{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
- </span>
- </th>
+ <th class="text-right" colspan="2">
+ {{ _("Discount") }}
+ </th>
+ <th class="text-right">
+ <span>
+ {% set tot_SO_discount = [] %}
+ {%- for item in doc.items -%}
+ {% if tot_SO_discount.append((((item.price_list_rate * item.qty)
+ * item.discount_percentage) / 100)) %}{% endif %}
+ {% endfor %}
+ {{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
+ </span>
+ </th>
</tr>
{% endif %}
{% endif %}
<tr>
- <th class="text-right" colspan="2">
+ <th></th>
+ <th class="item-grand-total">
{{ _("Grand Total") }}
</th>
- <th class="text-right">
+ <th class="text-right item-grand-total">
{{ doc.get_formatted("grand_total") }}
</th>
</tr>
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index 3033d15..2cabf5a 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -2,7 +2,7 @@
{% block title %} {{ _("Shopping Cart") }} {% endblock %}
-{% block header %}<h1>{{ _("Shopping Cart") }}</h1>{% endblock %}
+{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Shopping Cart") }}</h1>{% endblock %}
<!--
{% block script %}
@@ -18,94 +18,119 @@
{% from "templates/includes/macros.html" import item_name_and_description %}
+{% if doc.items %}
<div class="cart-container">
- <div id="cart-error" class="alert alert-danger" style="display: none;"></div>
+ <div class="row m-0">
+ <div class="col-md-8 frappe-card p-5">
+ <div>
+ <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 width="60%">{{ _('Item') }}</th>
+ <th width="20%">{{ _('Quantity') }}</th>
+ {% if cart_settings.enable_checkout %}
+ <th width="20%" class="text-right">{{ _('Subtotal') }}</th>
+ {% endif %}
+ </tr>
+ </thead>
+ <tbody class="cart-items">
+ {% include "templates/includes/cart/cart_items.html" %}
+ </tbody>
+ {% if cart_settings.enable_checkout %}
+ <tfoot class="cart-tax-items">
+ {% include "templates/includes/order/order_taxes.html" %}
+ </tfoot>
+ {% endif %}
+ </table>
+ </div>
+ <div class="row">
+ <div class="col-4">
+ {% if cart_settings.enable_checkout %}
+ <a class="btn btn-outline-primary" href="/orders">
+ {{ _('See past orders') }}
+ </a>
+ {% else %}
+ <a class="btn btn-outline-primary" href="/quotations">
+ {{ _('See past quotations') }}
+ </a>
+ {% endif %}
+ </div>
+ <div class="col-8">
+ {% if doc.items %}
+ <div class="place-order-container">
+ {% if cart_settings.enable_checkout %}
+ <button class="btn btn-primary btn-place-order" type="button">
+ {{ _("Place Order") }}
+ </button>
+ {% else %}
+ <button class="btn btn-primary btn-request-for-quotation" type="button">
+ {{ _("Request for Quotation") }}
+ </button>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+ </div>
- {% if doc.items %}
- <table class="table table-bordered mt-3">
- <thead>
- <tr>
- <th width="60%">{{ _('Item') }}</th>
- <th width="20%" class="text-right">{{ _('Quantity') }}</th>
- {% if cart_settings.enable_checkout %}
- <th width="20%" class="text-right">{{ _('Subtotal') }}</th>
- {% endif %}
- </tr>
- </thead>
- <tbody class="cart-items">
- {% include "templates/includes/cart/cart_items.html" %}
- </tbody>
- {% if cart_settings.enable_checkout %}
- <tfoot class="cart-tax-items">
- {% include "templates/includes/order/order_taxes.html" %}
- </tfoot>
- {% endif %}
- </table>
- {% else %}
- <p class="text-muted">{{ _('Your cart is Empty') }}</p>
- {% endif %}
- {% if doc.items %}
- <div class="place-order-container">
- {% if cart_settings.enable_checkout %}
- <button class="btn btn-primary btn-place-order" type="button">
- {{ _("Place Order") }}
- </button>
- {% else %}
- <button class="btn btn-primary btn-request-for-quotation" type="button">
- {{ _("Request for Quotation") }}
- </button>
+ {% if doc.items %}
+ {% if doc.tc_name %}
+ <div class="terms-and-conditions-link">
+ <a href class="link-terms-and-conditions" data-terms-name="{{ doc.tc_name }}">
+ {{ _("Terms and Conditions") }}
+ </a>
+ <script>
+ frappe.ready(() => {
+ $('.link-terms-and-conditions').click((e) => {
+ e.preventDefault();
+ const $link = $(e.target);
+ const terms_name = $link.attr('data-terms-name');
+ show_terms_and_conditions(terms_name);
+ })
+ });
+ function show_terms_and_conditions(terms_name) {
+ frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
+ .then(r => {
+ frappe.msgprint({
+ title: terms_name,
+ message: r.message
+ });
+ });
+ }
+ </script>
+ </div>
{% endif %}
</div>
- {% endif %}
- {% if doc.items %}
- {% if doc.tc_name %}
- <div class="terms-and-conditions-link">
- <a href class="link-terms-and-conditions" data-terms-name="{{ doc.tc_name }}">
- {{ _("Terms and Conditions") }}
- </a>
- <script>
- frappe.ready(() => {
- $('.link-terms-and-conditions').click((e) => {
- e.preventDefault();
- const $link = $(e.target);
- const terms_name = $link.attr('data-terms-name');
- show_terms_and_conditions(terms_name);
- })
- });
- function show_terms_and_conditions(terms_name) {
- frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
- .then(r => {
- frappe.msgprint({
- title: terms_name,
- message: r.message
- });
- });
- }
- </script>
+ <div class="col-md-4">
+ <div class="cart-addresses">
+ {% include "templates/includes/cart/cart_address.html" %}
+ </div>
</div>
- {% endif %}
-
- <div class="cart-addresses mt-5">
- {% include "templates/includes/cart/cart_address.html" %}
+ {% endif %}
</div>
- {% endif %}
</div>
-
-<div class="row mt-5">
- <div class="col-12">
- {% if cart_settings.enable_checkout %}
- <a href="/orders">
+{% 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">
{{ _('See past orders') }}
</a>
{% else %}
- <a href="/quotations">
+ <a class="btn btn-outline-primary" href="/quotations">
{{ _('See past quotations') }}
</a>
- {% endif %}
- </div>
+ {% endif %}
</div>
+{% endif %}
{% endblock %}
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index 17e6d02..92c76ad 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -137,13 +137,15 @@
</script>
</div>
</div>
-<div class="row">
- <div class="col-12">
+<div class="row product-paging-area mt-5">
+ <div class="col-3">
+ </div>
+ <div class="col-9 text-right">
{% if frappe.form_dict.start|int > 0 %}
- <button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
+ <button class="btn btn-default btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
{% endif %}
{% if items|length >= page_length %}
- <button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
+ <button class="btn btn-default btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
{% endif %}
</div>
</div>