diff --git a/VirtoCommerce.PricingModule.Web/Content/css/pricing.css b/VirtoCommerce.PricingModule.Web/Content/css/pricing.css
new file mode 100644
index 00000000..bae28c85
--- /dev/null
+++ b/VirtoCommerce.PricingModule.Web/Content/css/pricing.css
@@ -0,0 +1,20 @@
+.head-label {
+ font-size: 17px;
+ margin-bottom: 15px;
+}
+
+.head-select {
+ width: calc(100% - 120px);
+}
+
+button.btn.head-button {
+ background: #43b0e6;
+ color: #fff;
+ height: 30px;
+ line-height: 28px;
+ padding: 0 10px;
+}
+
+ button.btn.head-button:hover {
+ background: #43b0e6;
+ }
\ No newline at end of file
diff --git a/VirtoCommerce.PricingModule.Web/Controllers/Api/PricingModuleController.cs b/VirtoCommerce.PricingModule.Web/Controllers/Api/PricingModuleController.cs
index 79647773..c4204a2b 100644
--- a/VirtoCommerce.PricingModule.Web/Controllers/Api/PricingModuleController.cs
+++ b/VirtoCommerce.PricingModule.Web/Controllers/Api/PricingModuleController.cs
@@ -385,6 +385,21 @@ public IHttpActionResult DeleteProductPrices(string pricelistId, [FromUri]string
return StatusCode(HttpStatusCode.NoContent);
}
+ ///
+ /// Delete price by ids
+ ///
+ ///
+ ///
+ [HttpDelete]
+ [ResponseType(typeof(void))]
+ [Route("api/pricing/products/prices")]
+ [CheckPermission(Permission = PricingPredefinedPermissions.Delete)]
+ public IHttpActionResult DeleteProductPrice([FromUri]string[] priceIds)
+ {
+ _pricingService.DeletePrices(priceIds);
+ return StatusCode(HttpStatusCode.NoContent);
+ }
+
///
/// Delete pricelists
///
diff --git a/VirtoCommerce.PricingModule.Web/Localizations/en.VirtoCommerce.Pricing.json b/VirtoCommerce.PricingModule.Web/Localizations/en.VirtoCommerce.Pricing.json
index b980ccb5..d58e8d72 100644
--- a/VirtoCommerce.PricingModule.Web/Localizations/en.VirtoCommerce.Pricing.json
+++ b/VirtoCommerce.PricingModule.Web/Localizations/en.VirtoCommerce.Pricing.json
@@ -109,7 +109,20 @@
},
"no-assignments": "Catalog does not have assigned price lists. Please assign a price list by following this link ",
"manage-pricelists": "Manage pricing"
-
+ },
+ "item-prices": {
+ "title": "Prices for {{name}}",
+ "subtitle": "Edit prices",
+ "head-label": "Price list for new price",
+ "pricelist-select-placeholder": "Select a pricelist to add new price...",
+ "labels": {
+ "name": "Price List",
+ "currency": "Currency",
+ "catalog": "Catalog",
+ "list-price": "List price",
+ "sale-price": "Sale price",
+ "min-quantity": "Min. quantity"
+ }
}
},
"widgets": {
@@ -137,6 +150,10 @@
"title": "Delete confirmation",
"message": "Are you sure you want to delete ALL prices for selected Products in this Price list?"
},
+ "item-prices-delete": {
+ "title": "Delete confirmation",
+ "message": "Are you sure you want to delete selected Prices?"
+ },
"prices-save": {
"title": "Save changes",
"message": "The Prices has been modified. Do you want to save changes?"
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.js b/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.js
new file mode 100644
index 00000000..94b3c204
--- /dev/null
+++ b/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.js
@@ -0,0 +1,225 @@
+angular.module('virtoCommerce.pricingModule')
+.controller('virtoCommerce.pricingModule.itemPriceListController', ['$scope', 'platformWebApp.bladeNavigationService', 'uiGridConstants', 'virtoCommerce.pricingModule.prices', 'virtoCommerce.catalogModule.catalogs', 'platformWebApp.ui-grid.extension', 'platformWebApp.objCompareService', 'virtoCommerce.pricingModule.priceValidatorsService', 'platformWebApp.dialogService',
+ function ($scope, bladeNavigationService, uiGridConstants, prices, catalogs, gridOptionExtension, objCompareService, priceValidatorsService, dialogService) {
+ $scope.uiGridConstants = uiGridConstants;
+ var blade = $scope.blade;
+ blade.updatePermission = 'pricing:update';
+
+ blade.refresh = function () {
+ blade.isLoading = true;
+ prices.getProductPricelists({ id: blade.itemId }, function (pricelists) {
+ //Loading catalogs for assignments because they do not contains them
+ //Need to display name of catalog in assignments grid
+ catalogs.getCatalogs(function (catalogsList) {
+ $scope.catalogsList = catalogsList;
+
+ blade.origEntity = [];
+
+ //Collect all available pricelists
+ blade.pricelistList = _.map(pricelists, function (pricelist) {
+ return {
+ id: pricelist.id,
+ code: pricelist.name,
+ currency: pricelist.currency,
+ assignments: pricelist.assignments,
+ displayName: pricelist.name + ' - ' + pricelist.currency
+ };
+ });
+ blade.selectedPricelist = _.first(blade.pricelistList);
+
+ var pricelistsWithPrices = _.filter(pricelists, function (pricelist) { return pricelist.prices.length > 0; });
+ _.each(pricelistsWithPrices, function (pricelistWithPrices) {
+ var priceListData = {
+ name: pricelistWithPrices.name,
+ currency: pricelistWithPrices.currency
+ };
+
+ var catalogsId = _.pluck(pricelistWithPrices.assignments, 'catalogId');
+ var catalogsName = _.map(catalogsId, function (catalogId) {
+ return _.findWhere($scope.catalogsList, { id: catalogId }).name;
+ });
+ priceListData.catalog = catalogsName.join(', ');
+
+ _.each(pricelistWithPrices.prices, function (price) {
+ var priceData = angular.copy(priceListData);
+ priceData = angular.extend(price, priceData);
+ blade.origEntity.push(priceData);
+ });
+ });
+
+ priceValidatorsService.setAllPrices(blade.currentEntities);
+ blade.currentEntities = angular.copy(blade.origEntity);
+ blade.isLoading = false;
+ });
+ });
+ }
+
+ $scope.createNewPricelist = function () {
+ var newBlade = {
+ id: 'pricingList',
+ controller: 'virtoCommerce.pricingModule.pricelistListController',
+ template: 'Modules/$(VirtoCommerce.Pricing)/Scripts/blades/pricelist-list.tpl.html',
+ title: 'pricing.blades.pricing-main.menu.pricelist-list.title',
+ parentRefresh: blade.refresh
+ };
+
+ bladeNavigationService.showBlade(newBlade, blade);
+ };
+
+ $scope.selectPricelist = function (entity) {
+ var newBlade = {
+ id: 'listItemChild',
+ currentEntityId: entity.pricelistId,
+ title: entity.name,
+ controller: 'virtoCommerce.pricingModule.pricelistDetailController',
+ template: 'Modules/$(VirtoCommerce.Pricing)/Scripts/blades/pricelist-detail.tpl.html'
+ };
+
+ bladeNavigationService.showBlade(newBlade, blade);
+ };
+
+ blade.onClose = function (closeCallback) {
+ bladeNavigationService.showConfirmationIfNeeded(isDirty(), canSave(), blade, $scope.saveChanges, closeCallback, "pricing.dialogs.prices-save.title", "pricing.dialogs.prices-save.message");
+ };
+
+ function isDirty() {
+ return blade.currentEntities && !objCompareService.equal(blade.origEntity, blade.currentEntities) && blade.hasUpdatePermission()
+ }
+
+ function canSave() {
+ return isDirty() && $scope.isValid();
+ }
+
+ $scope.isValid = function () {
+ return $scope.formScope && $scope.formScope.$valid &&
+ _.all(blade.currentEntities, $scope.isListPriceValid) &&
+ _.all(blade.currentEntities, $scope.isSalePriceValid) &&
+ _.all(blade.currentEntities, $scope.isUniqueQtyForPricelist) &&
+ (blade.currentEntities.length == 0 || _.some(blade.currentEntities, function (x) { return x.minQuantity == 1; }));
+ }
+
+ $scope.saveChanges = function () {
+ blade.isLoading = true;
+
+ angular.copy(blade.currentEntities, blade.origEntity);
+ if (_.any(blade.currentEntities)) {
+ var productPrices = {
+ productId: blade.itemId,
+ product: blade.item,
+ prices: blade.currentEntities
+ };
+ prices.update({ id: blade.itemId }, productPrices, function (data) {
+ blade.refresh();
+ },
+ function (error) { bladeNavigationService.setError('Error ' + error.status, $scope.blade); });
+ }
+ };
+
+ $scope.setForm = function (form) { $scope.formScope = form; }
+
+ blade.toolbarCommands = [
+ {
+ name: "platform.commands.save",
+ icon: 'fa fa-save',
+ executeMethod: $scope.saveChanges,
+ canExecuteMethod: canSave,
+ permission: blade.updatePermission
+ },
+ {
+ name: "platform.commands.delete",
+ icon: 'fa fa-trash-o',
+ executeMethod: function () {
+ var selection = $scope.gridApi.selection.getSelectedRows();
+ var ids = _.map(selection, function (item) { return item.id; });
+
+ var dialog = {
+ id: "confirmDeleteItem",
+ title: "pricing.dialogs.item-prices-delete.title",
+ message: "pricing.dialogs.item-prices-delete.message",
+ callback: function (remove) {
+ if (remove) {
+ prices.removePrice({ priceIds: ids }, function () {
+ //blade.refresh();
+ angular.forEach(selection, function (listItem) {
+ blade.currentEntities.splice(blade.currentEntities.indexOf(listItem), 1);
+ });
+ }, function (error) {
+ bladeNavigationService.setError('Error ' + error.status, blade);
+ });
+ }
+ }
+ }
+ dialogService.showConfirmationDialog(dialog);
+ },
+ canExecuteMethod: function () {
+ return $scope.gridApi && _.any($scope.gridApi.selection.getSelectedRows());
+ },
+ permission: 'pricing:delete'
+ },
+ {
+ name: "platform.commands.refresh",
+ icon: 'fa fa-refresh',
+ executeMethod: blade.refresh,
+ canExecuteMethod: function () { return true; }
+ }
+ ];
+
+ blade.addNewPrice = function (targetPricelist) {
+ //populate prices data for correct work of validation service
+ priceValidatorsService.setAllPrices(blade.currentEntities);
+
+ var catalogsId = _.pluck(targetPricelist.assignments, 'catalogId');
+ var catalogsName = _.map(catalogsId, function (catalogId) {
+ return _.findWhere($scope.catalogsList, { id: catalogId }).name;
+ });
+
+ var newPrice = {
+ productId: blade.itemId,
+ list: '',
+ minQuantity: 1,
+ currency: targetPricelist.currency,
+ pricelistId: targetPricelist.id,
+ name: targetPricelist.code,
+ catalog: catalogsName.join(', ')
+ };
+ blade.currentEntities.push(newPrice);
+ $scope.validateGridData();
+ }
+
+ $scope.isListPriceValid = priceValidatorsService.isListPriceValid;
+ $scope.isSalePriceValid = priceValidatorsService.isSalePriceValid;
+ $scope.isUniqueQtyForPricelist = priceValidatorsService.isUniqueQtyForPricelist;
+
+ // ui-grid
+ $scope.setGridOptions = function (gridId, gridOptions) {
+ gridOptions.onRegisterApi = function (gridApi) {
+ $scope.gridApi = gridApi;
+
+ gridApi.edit.on.afterCellEdit($scope, function () {
+ //to process validation for all rows in grid.
+ //e.g. if we have two rows with the same count of min qty, both of this rows will be marked as error.
+ //when we change data to valid in one row, another one should became valid too.
+ //more info about ui-grid validation: https://github.com/angular-ui/ui-grid/issues/4152
+ $scope.validateGridData();
+ });
+
+ $scope.validateGridData();
+ };
+
+ $scope.gridOptions = gridOptions;
+ gridOptionExtension.tryExtendGridOptions(gridId, gridOptions);
+ return gridOptions;
+ };
+
+ $scope.validateGridData = function () {
+ if ($scope.gridApi) {
+ angular.forEach(blade.currentEntities, function (rowEntity) {
+ angular.forEach($scope.gridOptions.columnDefs, function (colDef) {
+ $scope.gridApi.grid.validate.runValidators(rowEntity, colDef, rowEntity[colDef.name], undefined, $scope.gridApi.grid)
+ });
+ });
+ }
+ };
+
+ blade.refresh();
+}]);
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.tpl.html b/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.tpl.html
new file mode 100644
index 00000000..c33a4f38
--- /dev/null
+++ b/VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.tpl.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
{{ 'platform.list.no-data' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/blades/pricelist-list.js b/VirtoCommerce.PricingModule.Web/Scripts/blades/pricelist-list.js
index 2f7fc1fe..dc489cad 100644
--- a/VirtoCommerce.PricingModule.Web/Scripts/blades/pricelist-list.js
+++ b/VirtoCommerce.PricingModule.Web/Scripts/blades/pricelist-list.js
@@ -4,7 +4,7 @@ function ($scope, pricelists, dialogService, uiGridHelper, bladeUtils) {
var blade = $scope.blade;
var bladeNavigationService = bladeUtils.bladeNavigationService;
- blade.refresh = function () {
+ blade.refresh = function (parentRefresh) {
blade.isLoading = true;
return pricelists.search({
sort: uiGridHelper.getSortExpression($scope),
@@ -14,6 +14,11 @@ function ($scope, pricelists, dialogService, uiGridHelper, bladeUtils) {
blade.isLoading = false;
blade.currentEntities = data.results;
$scope.pageSettings.totalItems = data.totalCount;
+
+ if (parentRefresh === true && blade.parentRefresh) {
+ blade.parentRefresh();
+ }
+
return data;
}).$promise;
};
@@ -33,7 +38,7 @@ function ($scope, pricelists, dialogService, uiGridHelper, bladeUtils) {
isNew: true,
saveCallback: function (newPricelist) {
newBlade.isNew = false;
- blade.refresh().then(function () {
+ blade.refresh(true).then(function () {
newBlade.currentEntityId = newPricelist.id;
bladeNavigationService.showBlade(newBlade, blade);
});
@@ -64,7 +69,7 @@ function ($scope, pricelists, dialogService, uiGridHelper, bladeUtils) {
if (remove) {
bladeNavigationService.closeChildrenBlades(blade, function () {
pricelists.remove({ ids: _.pluck(list, 'id') },
- blade.refresh,
+ blade.refresh(true),
function (error) { bladeNavigationService.setError('Error ' + error.status, blade); });
});
}
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/blades/prices-list.js b/VirtoCommerce.PricingModule.Web/Scripts/blades/prices-list.js
index efe6b670..66cd6cc2 100644
--- a/VirtoCommerce.PricingModule.Web/Scripts/blades/prices-list.js
+++ b/VirtoCommerce.PricingModule.Web/Scripts/blades/prices-list.js
@@ -142,6 +142,10 @@
},
isUniqueQty: function (data) {
return Math.round(data.minQuantity) > 0 && _.all(allPrices, function (x) { return x === data || Math.round(x.minQuantity) !== Math.round(data.minQuantity) });
+ },
+ isUniqueQtyForPricelist: function (data) {
+ var result = Math.round(data.minQuantity) > 0 && _.all(_.where(allPrices, { pricelistId: data.pricelistId }), function (x) { return x === data || Math.round(x.minQuantity) !== Math.round(data.minQuantity) });
+ return result;
}
};
}])
@@ -167,4 +171,10 @@
return priceValidatorsService.isUniqueQty(rowEntity);
};
}, function () { return 'Quantity value should be unique'; });
+
+ uiGridValidateService.setValidator('minQuantityForPricelistValidator', function () {
+ return function (oldValue, newValue, rowEntity, colDef) {
+ return priceValidatorsService.isUniqueQtyForPricelist(rowEntity);
+ };
+ }, function () { return 'Quantity value should be unique'; });
}]);
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/resources/pricing.js b/VirtoCommerce.PricingModule.Web/Scripts/resources/pricing.js
index 00a83986..b3102d4b 100644
--- a/VirtoCommerce.PricingModule.Web/Scripts/resources/pricing.js
+++ b/VirtoCommerce.PricingModule.Web/Scripts/resources/pricing.js
@@ -6,7 +6,8 @@
getProductPricesForWidget: { url: 'api/products/:id/:catalogId/pricesWidget', isArray: true },
getProductPricelists: { url: 'api/catalog/products/:id/pricelists', isArray: true },
update: { method: 'PUT' },
- remove: { method: 'DELETE', url: 'api/pricing/pricelists/:priceListId/products/prices' }
+ remove: { method: 'DELETE', url: 'api/pricing/pricelists/:priceListId/products/prices' },
+ removePrice: { method: 'DELETE', url: 'api/pricing/products/prices' }
});
}])
.factory('virtoCommerce.pricingModule.pricelists', ['$resource', function ($resource) {
diff --git a/VirtoCommerce.PricingModule.Web/Scripts/widgets/itemPricesWidget.js b/VirtoCommerce.PricingModule.Web/Scripts/widgets/itemPricesWidget.js
index 2479425b..cfe74fea 100644
--- a/VirtoCommerce.PricingModule.Web/Scripts/widgets/itemPricesWidget.js
+++ b/VirtoCommerce.PricingModule.Web/Scripts/widgets/itemPricesWidget.js
@@ -26,18 +26,17 @@
return;
var productPricelistsBlade = {
- id: "itemPricelists",
- itemId: blade.itemId,
- item: blade.item,
- parentWidgetRefresh: refresh,
- title: blade.title,
- subtitle: 'pricing.blades.item-pricelists-list.subtitle',
- controller: 'virtoCommerce.pricingModule.itemPricelistsListController',
- template: 'Modules/$(VirtoCommerce.Pricing)/Scripts/blades/item/item-pricelists-list.tpl.html'
+ id: "itemPrices",
+ itemId: blade.itemId,
+ item: blade.item,
+ parentWidgetRefresh: refresh,
+ title: 'pricing.blades.item-prices.title',
+ titleValues: { name: blade.item.name },
+ subtitle: 'pricing.blades.item-prices.subtitle',
+ controller: 'virtoCommerce.pricingModule.itemPriceListController',
+ template: 'Modules/$(VirtoCommerce.Pricing)/Scripts/blades/item/item-prices.tpl.html'
};
bladeNavigationService.showBlade(productPricelistsBlade, blade);
-
-
};
$scope.$watch("widget.blade.catalog.id", function (id) {
diff --git a/VirtoCommerce.PricingModule.Web/VirtoCommerce.PricingModule.Web.csproj b/VirtoCommerce.PricingModule.Web/VirtoCommerce.PricingModule.Web.csproj
index 2ea2f760..e9b4883c 100644
--- a/VirtoCommerce.PricingModule.Web/VirtoCommerce.PricingModule.Web.csproj
+++ b/VirtoCommerce.PricingModule.Web/VirtoCommerce.PricingModule.Web.csproj
@@ -139,6 +139,7 @@
+
@@ -159,6 +160,8 @@
+
+
diff --git a/VirtoCommerce.PricingModule.Web/module.manifest b/VirtoCommerce.PricingModule.Web/module.manifest
index c0995b5d..50100e1d 100644
--- a/VirtoCommerce.PricingModule.Web/module.manifest
+++ b/VirtoCommerce.PricingModule.Web/module.manifest
@@ -5,7 +5,7 @@
2.13.9
-
+
Pricing module
@@ -25,6 +25,9 @@
VirtoCommerce.PricingModule.Web.dll
VirtoCommerce.PricingModule.Web.Module, VirtoCommerce.PricingModule.Web
+
+
+