Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New blade with prices for product #5

Merged
merged 8 commits into from
Sep 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions VirtoCommerce.PricingModule.Web/Content/css/pricing.css
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,21 @@ public IHttpActionResult DeleteProductPrices(string pricelistId, [FromUri]string
return StatusCode(HttpStatusCode.NoContent);
}

/// <summary>
/// Delete price by ids
/// </summary>
/// <param name="priceIds"></param>
/// <returns></returns>
[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);
}

/// <summary>
/// Delete pricelists
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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?"
Expand Down
225 changes: 225 additions & 0 deletions VirtoCommerce.PricingModule.Web/Scripts/blades/item/item-prices.js
Original file line number Diff line number Diff line change
@@ -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();
}]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<div class="blade-static __expanded">
<div class="form-group">
<label class="form-label head-label">{{'pricing.blades.item-prices.head-label' | translate}} <a href="" ng-click="createNewPricelist()" class="form-edit" va-permission="pricing:read"><i class="form-ico fa fa-pencil"></i></a></label>

<div class="form-input">
<ui-select ng-model="blade.selectedPricelist" class="head-select" required>
<ui-select-match placeholder="{{'pricing.blades.item-prices.pricelist-select-placeholder' | translate}}">{{$select.selected.displayName}}</ui-select-match>
<ui-select-choices repeat="x in blade.pricelistList | filter: $select.search">
<span ng-bind-html="x.displayName | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>

<button class="btn head-button" type="button" ng-disabled="!blade.selectedPricelist" ng-click="blade.addNewPrice(blade.selectedPricelist)">Add new price</button>
</div>
</div>
</div>
<div class="blade-content __large-wide">
<div class="blade-inner">
<div class="inner-block">
<form name="formScope" ng-init="setGridOptions('item-prices-grid', {
data: 'blade.currentEntities',
rowHeight: 60,
rowTemplate: 'list.row.html',
enableCellEdit: false,
enableCellEditOnFocus: true,
editableCellTemplate: 'default-cellTextEditor',
columnDefs: [
{ name: 'name', displayName: 'pricing.blades.item-prices.labels.name', cellTooltip: true, cellTemplate: 'ui-grid/cellNameLinkTemplate', width: 180 },
{ name: 'currency', displayName: 'pricing.blades.item-prices.labels.currency', width: 60 },
{ name: 'catalog', displayName: 'pricing.blades.item-prices.labels.catalog', cellTooltip: true},
{ name: 'list', displayName: 'pricing.blades.item-prices.labels.list-price', editableCellTemplate: 'list-cellTextEditor', validators: {listValidator: true, required: true}, cellTemplate: 'priceCellTitleValidator', enableCellEdit: true, enableHiding: false },
{ name: 'sale', displayName: 'pricing.blades.item-prices.labels.sale-price', editableCellTemplate: 'sale-cellTextEditor', validators: {saleValidator: true}, cellTemplate: 'priceCellTitleValidator', enableCellEdit: true },
{ name: 'minQuantity', displayName: 'pricing.blades.item-prices.labels.min-quantity', editableCellTemplate: 'minQuantity-cellTextEditor', validators: {minQuantityForPricelistValidator: true, required: true}, cellTemplate: 'ui-grid/cellTitleValidator', enableCellEdit: true, enableHiding: false }
]})">
<div class="table-wrapper" ng-if="blade.currentEntities.length" ng-init="setForm(formScope);">
<div ui-grid="gridOptions" ui-grid-cellNav ui-grid-edit ui-grid-validate ui-grid-auto-resize ui-grid-save-state ui-grid-selection ui-grid-resize-columns ui-grid-move-columns ui-grid-height></div>
</div>
</form>
<div class="note" ng-if="!blade.currentEntities.length">{{ 'platform.list.no-data' | translate }}</div>
</div>
</div>
</div>


<script type="text/ng-template" id="ui-grid/cellNameLinkTemplate">
<div class="ui-grid-cell-contents">
<a class="form-input" ng-href='' ng-click='grid.appScope.selectPricelist(row.entity)'>{{row.entity.name}}</a>
</div>
</script>
<script type="text/ng-template" id="ui-grid/cellTitleValidator">
<div class="ui-grid-cell-contents" title="{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}">
<div class="form-editor form-input">
<input class="ng-valid form-input" ng-class="{'ng-invalid' : grid.validate.isInvalid(row.entity,col.colDef)}" value="{{COL_FIELD CUSTOM_FILTERS}}" placeholder="{{'platform.placeholders.n-a' | translate}}" readonly="readonly" />
</div>
</div>
</script>
<script type="text/ng-template" id="priceCellTitleValidator">
<div class="ui-grid-cell-contents" title="{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}">
<div class="form-editor form-input">
<input class="ng-valid form-input" money ng-model="MODEL_COL_FIELD" ng-class="{'ng-invalid' : grid.validate.isInvalid(row.entity,col.colDef)}" placeholder="{{'platform.placeholders.n-a' | translate}}" readonly="readonly" />
</div>
</div>
</script>

<script type="text/ng-template" id="default-cellTextEditor">
<div class="form-editor form-input">
<input class="form-input" ng-model="MODEL_COL_FIELD" ui-grid-editor placeholder="{{'platform.placeholders.n-a' | translate}}" />
</div>
</script>
<script type="text/ng-template" id="list-cellTextEditor">
<div class="form-editor form-input">
<input class="form-input" money required ng-model="MODEL_COL_FIELD" ui-grid-editor ng-class="{'ng-invalid': !grid.appScope.isListPriceValid(row.entity)}" />
</div>
</script>
<script type="text/ng-template" id="sale-cellTextEditor">
<div class="form-editor form-input">
<input class="form-input" money ng-model="MODEL_COL_FIELD" ui-grid-editor ng-class="{'ng-invalid': !grid.appScope.isSalePriceValid(row.entity)}" placeholder="{{'platform.placeholders.n-a' | translate}}">
</div>
</script>
<script type="text/ng-template" id="minQuantity-cellTextEditor">
<div class="form-editor form-input">
<input class="form-input" smart-float num-type="integer" required ng-model="MODEL_COL_FIELD" ui-grid-editor ng-class="{'ng-invalid': !grid.appScope.isUniqueQtyForPricelist(row.entity)}">
</div>
</script>
<script type="text/ng-template" id="list.row.html">
<div ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.uid" ui-grid-one-bind-id-grid="rowRenderIndex + '-' + col.uid + '-cell'" class="ui-grid-cell" ng-class="{'ui-grid-row-header-cell': col.isRowHeader }" role="{{col.isRowHeader ? 'rowheader' : 'gridcell' }}" ui-grid-cell></div>
</script>
Loading