Skip to content
This repository has been archived by the owner on Apr 22, 2022. It is now read-only.

Commit

Permalink
VDS-800: Integrate configurable products backend with theme (#173)
Browse files Browse the repository at this point in the history
* Integrate configurable backend with product page

* Revert change for storefrontApp variable

* Integrate configurable backend to checkout page

* Integrate configurable backend to order detail page

* Integrate configurable backend to configurable product card

* Add missing dependencies

* Fix custom part icon case

* Fix console error on empty part items

* Set default price 0 for unavailable configured product

* Fix wrong default price for configurables

* Hide panel if there's no parts
  • Loading branch information
ilyawzrd authored Feb 4, 2021
1 parent eee6568 commit e5c1551
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 80 deletions.
7 changes: 3 additions & 4 deletions assets/js/checkout/checkout-configurable-line-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ storefrontApp.component('vcCheckoutConfigurableLineItem', {
}, true);

function getConfiguredLineItems(item) {
_.each(item.parts, part => {
part.items = [item.items.find(x => x.id === part.selectedItemId)];
if (part.items[0].validationErrors && part.items[0].validationErrors.length) {
part.quantityError = _.find(part.items[0].validationErrors, error => error.errorCode === "QuantityError");
_.each(item.items, part => {
if (part.validationErrors && part.validationErrors.length) {
part.quantityError = _.find(part.validationErrors, error => error.errorCode === "QuantityError");
if (part.quantityError && part.quantityError.availableQuantity === 0) {
$scope.outOfStockError = true;
}
Expand Down
21 changes: 10 additions & 11 deletions assets/js/checkout/checkout-configurable-line-item.tpl.html.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,32 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="part in $ctrl.item.parts" class="border-bottom-light">
<td class="text-center">
<img alt="{% raw %}{{ part.items[0].name }}{% endraw %}" ng-src="{% raw %}{{ part.image.url }}{% endraw %}" fallback-src="{{ 'no-image.svg' | asset_url }}" />
<tr ng-repeat="part in $ctrl.item.items" class="border-bottom-light">
<td class="text-center col-xs-1">
<img class="img-responsive" alt="{% raw %}{{ part.name }}{% endraw %}" ng-src="{% raw %}{{ part.imageUrl }}{% endraw %}" fallback-src="{{ 'no-image.svg' | asset_url }}" />
</td>
<td class="d-flex flex-column">
<span class="text-bold" ng-bind="part.name"></span>
<a ng-href="{% raw %}{{ part.items[0].product.url.replace(regex, baseUrl) }}{% endraw %}" ng-bind="part.items[0].name" class="text-bold"></a>
<td>
<a ng-href="{% raw %}{{ part.product.url.replace(regex, baseUrl) }}{% endraw %}" ng-bind="part.name" class="text-bold"></a>
<p class="margin-0">
<span class="text-bold">{{ 'checkout.price' | t }}</span>
{% if settings.show_prices_with_taxes %}
<span ng-bind="part.items[0].placedPriceWithTax.formattedAmount" class="text-bold text-green"></span>
<span ng-bind="part.placedPriceWithTax.formattedAmount" class="text-bold text-green"></span>
{% else %}
<span ng-bind="part.items[0].placedPrice.formattedAmount" class="text-bold text-green"></span>
<span ng-bind="part.placedPrice.formattedAmount" class="text-bold text-green"></span>
{% endif %}
<span>/ each</span>
</p>
</td>
<td ng-if="!part.quantityError" ng-bind="part.items[0].quantity" class="text-center"></td>
<td ng-if="!part.quantityError" ng-bind="part.quantity" class="text-center"></td>
<td ng-if="part.quantityError && part.quantityError.availableQuantity === 0" class="text-center text-danger">
<span ng-bind="part.quantityError.availableQuantity"></span>
<small class="d-block">{{ 'checkout.out_of_stock' | t }}</small>
</td>
<td ng-if="!part.quantityError" class="text-bold text-green text-right">
{% if settings.show_prices_with_taxes %}
<span ng-bind="part.items[0].extendedPriceWithTax.formattedAmount"></span>
<span ng-bind="part.extendedPriceWithTax.formattedAmount"></span>
{% else %}
<span ng-bind="part.items[0].extendedPrice.formattedAmount"></span>
<span ng-bind="part.extendedPrice.formattedAmount"></span>
{% endif %}
</td>
<td ng-if="part.quantityError && part.quantityError.availableQuantity === 0" ng-bind="$ctrl.setOutOfStockPrice()" class="text-bold text-green text-right"></td>
Expand Down
3 changes: 0 additions & 3 deletions assets/js/common-components/lineItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ storefrontApp.component('vcLineItems', {
function getConfiguredLineItems(groups) {
_.each(groups, group => {
angular.extend(group, { showConfiguration: false });
_.each(group.parts, part => {
part.items = [group.items.find(x => x.id === part.selectedItemId)];
});
});
}

Expand Down
14 changes: 7 additions & 7 deletions assets/js/common-components/lineItems.tpl.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="part in group.parts" class="border-bottom-light">
<td class="text-center">
<img alt="{% raw %}{{ part.items[0].name }}{% endraw %}" ng-src="{% raw %}{{ part.image.url }}{% endraw %}" fallback-src="{{ 'no-image.svg' | asset_url }}" />
<tr ng-repeat="part in group.items" class="border-bottom-light">
<td class="text-center col-xs-2">
<img class="img-responsive" alt="{% raw %}{{ part.name }}{% endraw %}" ng-src="{% raw %}{{ part.imageUrl }}{% endraw %}" fallback-src="{{ 'no-image.svg' | asset_url }}" />
</td>
<td>
<a ng-href="{% raw %}{{ part.items[0].product.url.replace(regex, baseUrl) }}{% endraw %}" ng-bind="part.items[0].name"></a>
<a ng-href="{% raw %}{{ part.product.url.replace(regex, baseUrl) }}{% endraw %}" ng-bind="part.name"></a>
</td>
<td ng-bind="part.items[0].quantity" class="text-center"></td>
<td ng-bind="part.quantity" class="text-center"></td>
{% if settings.show_prices_with_taxes %}
<td ng-bind="part.items[0].extendedPriceWithTax.formattedAmount" class="text-bold text-right"></td>
<td ng-bind="part.extendedPriceWithTax.formattedAmount" class="text-bold text-right"></td>
{% else %}
<td ng-bind="part.items[0].extendedPrice.formattedAmount" class="text-bold text-right"></td>
<td ng-bind="part.extendedPrice.formattedAmount" class="text-bold text-right"></td>
{% endif %}
</tr>
</tbody>
Expand Down
36 changes: 27 additions & 9 deletions assets/js/controllers/product-card/configurable-product-card.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
var storefrontApp = angular.module('storefrontApp');

storefrontApp.controller('configurableProductCardController', ['$scope', 'catalogService', '$filter', 'roundHelper', 'storeCurrency',
function ($scope, catalogService, $filter, roundHelper, storeCurrency) {
storefrontApp.controller('configurableProductCardController', ['$scope', 'catalogService', '$filter', 'storeCurrency', 'pricingService',
function ($scope, catalogService, $filter, storeCurrency, pricingService) {
$scope.isProductUnavailable = false;

$scope.getDefaultPrice = function() {
return $filter('currency')($scope.defaultPrice, storeCurrency.symbol);
}

$scope.initProductConfiguration = function(productId) {
catalogService.getProductConfiguration(productId).then(function(response) {
$scope.productParts = response.data;
$scope.defaultProductParts = [];
_.each($scope.productParts, function (part) {
$scope.defaultProductParts.push(part.items.find(x => x.id === part.selectedItemId));
catalogService.getProduct([productId]).then(response => {
let defaultPartsTotalsObject = [];
$scope.productParts = response.data[0].parts;

if ($scope.productParts.length) {
_.each($scope.productParts, part => {
if (!part.items || !part.items.length) {
$scope.isProductUnavailable = true;
return;
}
defaultPartsTotalsObject.push({id: part.items.find(x => x.id === part.selectedItemId).id, quantity: 1});
});

$scope.defaultPrice = roundHelper.bankersRound($scope.defaultProductParts.reduce((prev, cur) => prev + cur.price.actualPrice.amount, 0));
});
if (!$scope.isProductUnavailable) {
pricingService.getProductsTotal(defaultPartsTotalsObject).then(result => {
$scope.defaultPrice = $scope.showPricesWithTaxes ? result.data.totalWithTax.amount : result.data.total.amount;
});
} else {
$scope.defaultPrice = 0;
}

} else {
$scope.defaultPrice = 0;
}

});
}

}]);
93 changes: 53 additions & 40 deletions assets/js/controllers/product/configurable-product.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
var storefrontApp = angular.module('storefrontApp');

storefrontApp.controller('configurableProductController', ['$rootScope', '$scope', '$window', 'dialogService', 'catalogService', 'cartService', '$filter', 'roundHelper', 'availabilityService', 'storeCurrency',
function ($rootScope, $scope, $window, dialogService, catalogService, cartService, $filter, roundHelper, availabilityService, storeCurrency) {
storefrontApp.controller('configurableProductController', ['$rootScope', '$scope', '$window', 'dialogService', 'catalogService', 'cartService', '$filter', 'roundHelper', 'availabilityService', 'storeCurrency', 'pricingService',
function ($rootScope, $scope, $window, dialogService, catalogService, cartService, $filter, roundHelper, availabilityService, storeCurrency, pricingService) {
$scope.configurationQty = 1;
$scope.isProductUnavailable = false;

$scope.addSelectedProductsToCart = function() {
var configuredProductId = $window.product.id;
var products = $scope.productParts.map(function(part) {
return part.items.find(function(item) {
return item.id === part.selectedItemId;
});
});
var inventoryError = products.some(product => {
return product.availableQuantity < $scope.configurationQty;
const configuredProductId = $window.product.id;
const products = $scope.productParts.map(part => {
return part.items.find(item => item.id === part.selectedItemId);
});
var dialogData = toDialogDataModel(products, $scope.configurationQty, inventoryError, configuredProductId);
const inventoryError = products.some(product => product.availableQuantity < $scope.configurationQty);
const dialogData = toDialogDataModel(products, $scope.configurationQty, inventoryError, configuredProductId);
dialogService.showDialog(dialogData, 'recentlyAddedCartItemDialogController', 'storefront.recently-added-cart-item-dialog.tpl', 'lg');

if (!inventoryError) {
var items = $scope.productParts.map(function(value) {
const items = $scope.productParts.map(value => {
return { id: value.selectedItemId, quantity: $scope.configurationQty, configuredProductId: configuredProductId };
});

cartService.addLineItems(items).then(function (response) {
var result = response.data;
cartService.addLineItems(items).then(response => {
const result = response.data;
if (result.isSuccess) {
$rootScope.$broadcast('cartItemsChanged');
}
Expand All @@ -32,8 +29,8 @@ storefrontApp.controller('configurableProductController', ['$rootScope', '$scope
}

$scope.changeGroupItem = function (productPart) {
var dialogInstance = dialogService.showDialog(productPart, 'changeConfigurationGroupItemDialogController', 'storefront.select-configuration-item-dialog.tpl');
dialogInstance.result.then(function (id) {
const dialogInstance = dialogService.showDialog(productPart, 'changeConfigurationGroupItemDialogController', 'storefront.select-configuration-item-dialog.tpl');
dialogInstance.result.then(id => {
const foundIndex = $scope.productParts.findIndex(x => x.name === productPart.name);
$scope.productParts[foundIndex].selectedItemId = id;
recalculateTotals();
Expand All @@ -46,7 +43,7 @@ storefrontApp.controller('configurableProductController', ['$rootScope', '$scope
}

$scope.getCurrentTotal = function() {
var total;
let total;

if ($scope.updatedTotal) {
total = roundHelper.bankersRound($scope.updatedTotal * $scope.configurationQty);
Expand Down Expand Up @@ -76,50 +73,66 @@ storefrontApp.controller('configurableProductController', ['$rootScope', '$scope
}

function toDialogDataModel(products, quantity, inventoryError, configuredProductId) {
let productIds = products.map(function(product) {
const productIds = products.map(product => {
return product.id;
});
let items = products.map(function(product) {
const items = products.map(product => {
return angular.extend({ }, product, { quantity: +quantity, inventoryError: product.availableQuantity < quantity, configuredProductId: configuredProductId })
});
return { productIds, items, inventoryError, configuredProductId, configurationQty: quantity };
}

function initialize() {
var product = $window.product;
let product = $window.product;
if (!product) {
return;
}
catalogService.getProduct([product.id]).then(function (response) {
catalogService.getProduct([product.id]).then(response => {
let defaultPartsTotalsObject = [];
product = response.data[0];
$scope.selectedVariation = product;
$scope.productParts = response.data[0].parts;
$scope.defaultProductParts = [];

if ($scope.productParts.length) {
_.each($scope.productParts, part => {
if (!part.items || !part.items.length) {
$scope.isProductUnavailable = true;
return;
}
$scope.defaultProductParts.push(part.items.find(x => x.id === part.selectedItemId));
defaultPartsTotalsObject.push({id: part.items.find(x => x.id === part.selectedItemId).id, quantity: 1});
});

if (!$scope.isProductUnavailable) {
pricingService.getProductsTotal(defaultPartsTotalsObject).then(result => {
$scope.defaultPrice = $scope.showPricesWithTaxes ? result.data.totalWithTax.amount : result.data.total.amount;
});
} else {
$scope.defaultPrice = 0;
}

} else {
$scope.defaultPrice = 0;
}

return availabilityService.getProductsAvailability([product.id]).then(function(res) {

return availabilityService.getProductsAvailability([product.id]).then(res => {
$scope.availability = _.object(_.pluck(res.data, 'productId'), res.data);
});
});
}

function recalculateTotals() {
$scope.selectedProductParts = [];
_.each($scope.productParts, function (part) {
$scope.selectedProductParts.push(part.items.find(x => x.id === part.selectedItemId));
let selectedProductParts = [];
_.each($scope.productParts, part => {
selectedProductParts.push({id: part.items.find(x => x.id === part.selectedItemId).id, quantity: 1});
});
$scope.updatedTotal = roundHelper.bankersRound($scope.selectedProductParts.reduce((prev, cur) => prev + cur.price.actualPrice.amount, 0));
$scope.totalDifference = roundHelper.bankersRound(Math.abs($scope.updatedTotal - $scope.defaultPrice));
$scope.differenceSign = ($scope.updatedTotal === $scope.defaultPrice) ? '' :
($scope.updatedTotal > $scope.defaultPrice) ? '+' : '-';
}

$scope.initProductConfiguration = function(productId) {
catalogService.getProductConfiguration(productId).then(function(response) {
$scope.productParts = response.data;
$scope.defaultProductParts = [];
_.each($scope.productParts, function (part) {
$scope.defaultProductParts.push(part.items.find(x => x.id === part.selectedItemId));
});

$scope.defaultPrice = roundHelper.bankersRound($scope.defaultProductParts.reduce((prev, cur) => prev + cur.price.actualPrice.amount, 0));
pricingService.getProductsTotal(selectedProductParts).then(result => {
$scope.updatedTotal = $scope.showPricesWithTaxes ? result.data.totalWithTax.amount : result.data.total.amount;
$scope.totalDifference = Math.abs($scope.updatedTotal - $scope.defaultPrice);
$scope.differenceSign = ($scope.updatedTotal === $scope.defaultPrice) ? '' :
($scope.updatedTotal > $scope.defaultPrice) ? '+' : '-';
});
}

Expand Down
6 changes: 3 additions & 3 deletions snippets/product/product-configuration-list.liquid
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% if product.product_type == "Configurable" %}
<div class="panel panel-default" ng-if="productParts">
<div class="panel panel-default" ng-if="productParts && productParts.length">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
Expand All @@ -8,8 +8,8 @@
</div>
</div>
<div ng-repeat="part in productParts" class="flex border-bottom-light panel-body align-items-center">
<div>
<img ng-if="part.image.url" alt="{% raw %}{{ part.name }}{% endraw %}" ng-src="{% raw %}{{ part.image.url }}{% endraw %}" />
<div class="col-md-2">
<img ng-if="part.image.url" class="img-responsive" alt="{% raw %}{{ part.name }}{% endraw %}" ng-src="{% raw %}{{ part.image.url }}{% endraw %}" />
<img ng-if="!part.image.url" alt="{% raw %}{{ part.name }}{% endraw %}" ng-src="{{ 'no-image.svg' | asset_url }}" />
</div>

Expand Down
2 changes: 1 addition & 1 deletion snippets/product/type/configured/item-price.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</span>
</dt>

<dd>
<dd ng-init="showPricesWithTaxes = {{settings.show_prices_with_taxes}}">
<span class="product-pull-right">
<strong ng-bind="getDefaultPrice()"></strong>
<!-- TODO: Replace with product unit -->
Expand Down
2 changes: 1 addition & 1 deletion snippets/product/type/configured/product.liquid
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% include 'product/type/common/product-title-info' %}
<div class="row flexible">
<div class="row flexible" ng-init="showPricesWithTaxes = {{settings.show_prices_with_taxes}}">
<div class="col-md-3">
{% include 'product/image' %}
{% include 'product/compare' %}
Expand Down
Loading

0 comments on commit e5c1551

Please sign in to comment.