Merge pull request #14 from rbkmoney/fe/FT-38/multishop

FE-38: multishop
This commit is contained in:
Ildar Galeev 2016-09-29 19:05:11 +03:00 committed by GitHub
commit c86be18f0e
74 changed files with 922 additions and 230 deletions

View File

@ -1,5 +1,5 @@
.dashboard_stats_count {
font-size: 33px;
font-size: 27px;
line-height: 40px;
font-weight: 600;
}
@ -47,3 +47,7 @@
width: 100% !important;
font-size: 40px !important;
}
.custom-chevron {
margin-top: 3px;
}

2
app/bootstrap.js vendored
View File

@ -12,4 +12,6 @@ angular.element(document).ready(function () {
});
angular.bootstrap(document, ['app']);
});
// angular.bootstrap(document, ['app']);
});

View File

@ -0,0 +1,41 @@
dashboard.component('analytics', {
templateUrl: 'components/analytics/analytics.template.html',
$routeConfig: [
{path: '/:shopId/statistic', name: 'Dashboard', component: 'dashboard'},
{path: '/:shopId/finance', name: 'Finance', component: 'finance'}
],
bindings: {
$router: '<'
},
controller: function (Parties, $location) {
this.$routerOnActivate = () => {
Parties.get(party => {
this.shopsDetails = _.map(party.shops, shop => ({
name: shop.shopDetails.name,
key: shop.shopID
}));
this.selectedShopId = resolveShopId(party.shops);
this.onSelect();
});
};
function findParam(path, marker) {
const res = _.chain(path)
.split('/')
.reduce((a, c) => ((c === marker || a === marker) ? c : a), '')
.value();
return res !== marker ? res : null;
}
function resolveShopId(shops) {
const path = $location.path();
return shops.length > 0 ? shops[0].shopID : findParam(path, 'analytics');
}
this.onSelect = () => {
this.$router.navigate(['Dashboard', {shopId: this.selectedShopId}]);
};
this.isRouteActive = route => this.$router.isRouteActive(this.$router.generate(route));
}
});

View File

@ -0,0 +1 @@
const analytics = angular.module('analytics', ['dashboard']);

View File

@ -0,0 +1,18 @@
.row(ng-show="$ctrl.selectedShopId")
.col-md-12
.x_panel.tile
.x_content
form.form-horizontal
.form-group
label.control-label.col-md-1 Магазин
.col-md-5
select.select2_multiple.form-control.custom_select(
select2 ng-options="detail.key as detail.name for detail in $ctrl.shopsDetails"
ng-model="$ctrl.selectedShopId" ng-change="$ctrl.onSelect()")
ul.nav.nav-tabs.bar_tabs
li(ng-class="{active: $ctrl.isRouteActive(['\Dashboard\', {shopId: $ctrl.selectedShopId}])}")
a(ng-link="['\Dashboard\', {shopId: $ctrl.selectedShopId}]") Статистика
li(ng-class="{active: $ctrl.isRouteActive(['\Finance\', {shopId: $ctrl.selectedShopId}])}")
a(ng-link="['\Finance\', {shopId: $ctrl.selectedShopId}]") Финансы
.tab-content
ng-outlet

View File

@ -0,0 +1,80 @@
dashboard.component('dashboard', {
templateUrl: 'components/analytics/dashboard/dashboard.template.html',
controller: function (Payments, ChartDataConversion, Customers, Accounts) {
// this.fromTime = moment(this.toTime).subtract(1, 'M').hours(0).minutes(0).seconds(0).milliseconds(0).format();
this.toTime = moment().format();
this.fromTime = moment().hours(0).minutes(0).seconds(0).format();
this.loadData = () => {
this.chartFromTime = this.fromTime;
const customers = new Customers(this.shopID);
customers.rate({
fromTime: this.fromTime,
toTime: this.toTime
}, rateStat => {
this.uniqueCount = rateStat[0] ? rateStat[0].uniqueCount : 0;
});
const payments = new Payments(this.shopID);
payments.conversion({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'minute',
splitSize: 1
}, conversionStat => {
this.conversionChartData = ChartDataConversion.toConversionChartData(conversionStat);
const paymentCountInfo = ChartDataConversion.toPaymentCountInfo(conversionStat);
this.successfulCount = paymentCountInfo.successfulCount;
this.unfinishedCount = paymentCountInfo.unfinishedCount;
});
payments.revenue({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'minute',
splitSize: 1
}, revenueStat => {
this.revenueChartData = ChartDataConversion.toRevenueChartData(revenueStat);
this.profit = ChartDataConversion.toTotalProfit(revenueStat);
});
payments.geo({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'day',
splitSize: 1
}, geoStat => {
this.geoChartData = ChartDataConversion.toGeoChartData(geoStat);
});
Accounts.query({shopID: this.shopID}, shopAccounts => {
if (shopAccounts.length > 1) {
console.warn('shop accounts size > 1');
}
_.forEach(shopAccounts, item => {
const account = {};
Accounts.get({
shopID: this.shopID,
accountID: item.generalID
}, generalAccount => {
account.general = generalAccount;
});
Accounts.get({
shopID: this.shopID,
accountID: item.guaranteeID
}, guaranteeAccount => {
account.guarantee = guaranteeAccount;
});
this.account = account;
});
});
};
this.$routerOnActivate = route => {
this.shopID = route.params.shopId;
this.loadData();
};
}
});

View File

@ -0,0 +1,34 @@
.row
.col-md-12
form.form-inline.pull-right
.form-group
label.control-label Статистика с
datepicker(date="$ctrl.fromTime")
.form-group
label.control-label по
datepicker(date="$ctrl.toTime")
button.btn.btn-default(style="margin-bottom: 0; margin-right: 0" ng-click="$ctrl.loadData()") Показать
.row
info-panel(unique-count="$ctrl.uniqueCount" successful-count="$ctrl.successfulCount"
unfinished-count="$ctrl.unfinishedCount" profit="$ctrl.profit" account="$ctrl.account")
.row
.col-md-12
.x_panel.tile
.x_title
h4 Оборот
.x_content
revenue(from-time="$ctrl.chartFromTime" chart-data="$ctrl.revenueChartData")
.row
.col-md-6
.x_panel.tile
.x_title
h4 Конверсия оплат
.x_content
conversion(from-time="$ctrl.chartFromTime" chart-data="$ctrl.conversionChartData")
.col-md-6
.x_panel.tile
.x_title
h4 Геолокация
.x_content
geolocation(chart-data="$ctrl.geoChartData")

View File

@ -1,18 +1,20 @@
const infoPanel = angular.module('infoPanel', ['currency']);
infoPanel.component('infoPanel', {
templateUrl: 'components/dashboard/info-panel/info-panel.html',
templateUrl: 'components/analytics/dashboard/info-panel/info-panel.template.html',
bindings: {
uniqueCount: '<',
successfulCount: '<',
unfinishedCount: '<',
profit: '<'
profit: '<',
account: '<'
},
controller: function () {
this.profitLoading = true;
this.successfulCountLoading = true;
this.unfinishedCountLoading = true;
this.uniqueCountLoading = true;
this.accountsLoading = true;
this.$onChanges = () => {
if (_.isNumber(this.profit)) {
this.profitLoading = false;
@ -26,6 +28,9 @@ infoPanel.component('infoPanel', {
if(_.isNumber(this.uniqueCount)) {
this.uniqueCountLoading = false;
}
if (this.account) {
this.accountsLoading = false;
}
};
}
});

View File

@ -1,22 +1,34 @@
.tile_count
.col-md-3.col-sm-4.col-xs-6.tile_stats_count
.col-md-2.tile_stats_count
span.count_top Оборот
.dashboard_stats_count
loading(is-loading="$ctrl.profitLoading")
div {{$ctrl.profit | roubleCurrency}}
span.count_bottom Рублей
.col-md-3.col-sm-4.col-xs-6.tile_stats_count
.col-md-2.tile_stats_count
span.count_top Основной счет
.dashboard_stats_count
loading(is-loading="$ctrl.accountsLoading")
div {{$ctrl.account.general.availableAmount | roubleCurrency}}
span.count_bottom Рублей
.col-md-2.tile_stats_count
span.count_top Гарантийный счет
.dashboard_stats_count
loading(is-loading="$ctrl.accountsLoading")
div {{$ctrl.account.guarantee.availableAmount | roubleCurrency}}
span.count_bottom Рублей
.col-md-2.tile_stats_count
span.count_top Успешных платежей
.dashboard_stats_count
loading(is-loading="$ctrl.successfulCountLoading")
div {{$ctrl.successfulCount}}
.col-md-3.col-sm-4.col-xs-6.tile_stats_count
.col-md-2.tile_stats_count
span.count_top Незавершенных платежей
.dashboard_stats_count
loading(is-loading="$ctrl.unfinishedCountLoading")
div {{$ctrl.unfinishedCount}}
.col-md-3.col-sm-4.col-xs-6.tile_stats_count
.col-md-2.tile_stats_count
span.count_top Уникальных плательщиков
.dashboard_stats_count
loading(is-loading="$ctrl.uniqueCountLoading")
div {{$ctrl.uniqueCount}}
div {{$ctrl.uniqueCount}}

View File

@ -0,0 +1,34 @@
finance.component('finance', {
templateUrl: 'components/analytics/finance/finance.html',
controller: function (Invoices) {
this.searchParams = {
fromTime: moment().hours(0).minutes(0).seconds(0).format(),
toTime: moment().format(),
limit: 20,
offset: 0
};
this.isLoading = false;
this.isSearched = false;
this.$routerOnActivate = route => {
const shopID = route.params.shopId;
this.search = (offset = 0) => {
this.isLoading = true;
this.isSearched = true;
this.searchParams.offset = offset;
if (_.isEmpty(this.searchParams.invoiceID)) {
this.searchParams.invoiceID = null;
}
const invoices = new Invoices(shopID);
invoices.search(this.searchParams, result => {
this.searchedInvoices = result.invoices;
this.totalCount = result.totalCount;
this.isLoading = false;
});
};
this.search();
};
}
});

View File

@ -2,7 +2,7 @@
.col-md-12.col-sm-12.col-xs-12
.x_panel.tile
.x_title
h3 Поиск
h4 Поиск
.x_content
search-form(search-params="$ctrl.searchParams" on-search="$ctrl.search()")
.row

View File

@ -1,5 +1,5 @@
paginate.component('paginate', {
templateUrl: 'components/finance/paginate/paginate.html',
templateUrl: 'components/analytics/finance/paginate/paginate.html',
bindings: {
limit: '<',
size: '<',

View File

@ -10,8 +10,5 @@ customSelect.component('customSelect', {
this.options = _.map(PAYMENT_STATUSES, (name, key) => {
return {name, key}
});
// jQuery(".select2_multiple").select2({
// placeholder: ''
// });
}
});
});

View File

@ -1,6 +1,5 @@
datepicker.component('datepicker', {
template: `<input type="text" daterangepicker ng-model="$ctrl.date" class="form-control">
<span class="fa fa-calendar form-control-feedback right" aria-hidden="true"></span>`,
template: `<input type="text" daterangepicker ng-model="$ctrl.date" class="form-control">`,
bindings: {
date: '='
}

View File

@ -1,7 +1,7 @@
const searchForm = angular.module('searchForm', ['datepicker', 'customSelect']);
searchForm.component('searchForm', {
templateUrl: 'components/finance/search-form/search-form.html',
templateUrl: 'components/analytics/finance/search-form/search-form.html',
bindings: {
searchParams: '=',
onSearch: '&'

View File

@ -2,20 +2,20 @@ form.form-horizontal.form-label-left
.row
.col-md-6
.form-group
label.control-label.col-md-4 Дата с:
label.control-label.col-md-4 Дата с
.col-md-6
datepicker(date="$ctrl.searchParams.fromTime")
.form-group
label.control-label.col-md-4 Дата по:
label.control-label.col-md-4 Дата по
.col-md-6
datepicker(date="$ctrl.searchParams.toTime")
.col-md-6
//.form-group
// label.control-label.col-md-4 Статус платежа:
// .col-md-6
// custom-select(options="$ctrl.statuses" selected="$ctrl.searchParams.status")
.form-group
label.control-label.col-md-4 Номер:
label.control-label.col-md-4 Статус платежа
.col-md-6
custom-select(options="$ctrl.statuses" selected="$ctrl.searchParams.status")
.form-group
label.control-label.col-md-4 Номер
.col-md-6
input.form-control(type="text" ng-model="$ctrl.searchParams.invoiceID")
.form-group

View File

@ -1,5 +1,5 @@
searchResult.component('searchResult', {
templateUrl: 'components/finance/search-result/search-result.html',
templateUrl: 'components/analytics/finance/search-result/search-result.html',
bindings: {
searchedInvoices: '<'
}

View File

@ -1,4 +1,8 @@
// app.constant('appConfig', {
// capiUrl: 'http://macroserver1.msk1.rbkmoney.net:8080/v1/',
// shopID: 'THRIFT-SHOP'
// });
app.constant('appConfig', {
capiUrl: 'http://macroserver1.msk1.rbkmoney.net:8080/v1/',
shopID: 'THRIFT-SHOP'
capiUrl: 'http://localhost:9000/v1/'
});

View File

@ -1,8 +1,11 @@
app.component('app', {
templateUrl: 'components/app/app.html',
$routeConfig: [
{path: '/dashboard', name: 'Dashboard', component: 'dashboard', useAsDefault: true},
{path: '/finance', name: 'Finance', component: 'finance'}
{path: '/shops', name: 'Shops', component: 'shops'},
{path: '/shops/add', name: 'AddShop', component: 'addShop'},
{path: '/shops/edit/:shopId', name: 'EditShop', component: 'editShop'},
{path: '/analytics', name: 'Analytics', component: 'analytics', useAsDefault: true},
{path: '/analytics/...', component: 'analytics'}
]
});

View File

@ -1,6 +1,7 @@
const app = angular.module('app', [
'ngComponentRouter',
'dashboard',
'shops',
'analytics',
'finance',
'sidebar',
'topPanel',

View File

@ -1,58 +0,0 @@
dashboard.component('dashboard', {
templateUrl: 'components/dashboard/dashboard.html',
controller: function (Payments, ChartDataConversion, Customers) {
this.toTime = moment().format();
// this.fromTime = moment(this.toTime)
// .subtract(1, 'M')
// .hours(0)
// .minutes(0)
// .seconds(0)
// .milliseconds(0)
// .format();
this.fromTime = moment()
.hours(0)
.minutes(0)
.seconds(0)
.format();
Payments.conversion({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'minute',
splitSize: 1
}, conversionStat => {
this.conversionChartData = ChartDataConversion.toConversionChartData(conversionStat);
const paymentCountInfo = ChartDataConversion.toPaymentCountInfo(conversionStat);
this.successfulCount = paymentCountInfo.successfulCount;
this.unfinishedCount = paymentCountInfo.unfinishedCount;
});
Payments.revenue({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'minute',
splitSize: 1
}, revenueStat => {
this.revenueChartData = ChartDataConversion.toRevenueChartData(revenueStat);
this.profit = ChartDataConversion.toTotalProfit(revenueStat);
});
Payments.geo({
fromTime: this.fromTime,
toTime: this.toTime,
splitUnit: 'day',
splitSize: 1
}, geoStat => {
this.geoChartData = ChartDataConversion.toGeoChartData(geoStat);
});
Customers.rate({
fromTime: this.fromTime,
toTime: this.toTime
}, rateStat => {
this.uniqueCount = rateStat[0] ? rateStat[0].uniqueCount : 0;
});
}
});

View File

@ -1,24 +0,0 @@
.row
info-panel(unique-count="$ctrl.uniqueCount" successful-count="$ctrl.successfulCount"
unfinished-count="$ctrl.unfinishedCount" profit="$ctrl.profit")
.row
.col-md-12.col-sm-12.col-xs-12
.x_panel.tile
.x_title
h4 Оборот
.x_content
revenue(from-time="$ctrl.fromTime" chart-data="$ctrl.revenueChartData")
.row
.col-md-6.col-sm-6.col-xs-12
.x_panel.tile
.x_title
h4 Конверсия оплат
.x_content
conversion(from-time="$ctrl.fromTime" chart-data="$ctrl.conversionChartData")
.col-md-6.col-sm-6.col-xs-12
.x_panel.tile
.x_title
h4 Геолокация
.x_content
geolocation(chart-data="$ctrl.geoChartData")

View File

@ -1,29 +0,0 @@
finance.component('finance', {
templateUrl: 'components/finance/finance.html',
controller: function (Invoices) {
this.searchParams = {
fromTime: moment().hours(0).minutes(0).seconds(0).format(),
toTime: moment().format(),
limit: 20,
offset: 0
};
this.isLoading = false;
this.isSearched = false;
this.search = offset => {
this.isLoading = true;
this.isSearched = true;
this.searchParams.offset = offset ? offset : 0;
if (_.isEmpty(this.searchParams.invoiceID)) {
this.searchParams.invoiceID = null;
}
Invoices.search(this.searchParams, result => {
this.searchedInvoices = result.invoices;
this.totalCount = result.totalCount;
this.isLoading = false;
});
};
this.search();
}
});

View File

@ -0,0 +1,27 @@
shops.component('addShop', {
templateUrl: 'components/shops/add-shop/add-shop.template.html',
bindings: {
$router: '<'
},
controller: function (Shops, Categories) {
this.args = {};
this.isLoading = false;
this.categories = Categories.query();
const back = () => {
this.args = {};
this.isLoading = false;
this.$router.navigate(['Shops']);
};
this.createClaim = form => {
if (form.$valid) {
this.isLoading = true;
Shops.save(this.args, () => back());
}
};
this.hasError = field => field.$dirty && field.$invalid;
}
});

View File

@ -0,0 +1,39 @@
.row
.col-md-12
.x_panel.tile
.x_title
h4 Добавление нового магазина
.x_content
loading(is-loading="$ctrl.isLoading")
form.form-horizontal.form-label-left.css-form(novalidate name="form")
.form-group(ng-class="{'has-error': $ctrl.hasError(form.shopDetails)}")
label.control-label.col-md-3 Название магазина *
.col-md-6
input.form-control(type="text" ng-model="$ctrl.args.shopDetails.name" name="shopDetails" required)
.form-group
label.control-label.col-md-3 Описание магазина
.col-md-6
input.form-control(type="text" ng-model="$ctrl.args.shopDetails.description")
.form-group
label.control-label.col-md-3 Месторасположение
.col-md-6
input.form-control(type="text" ng-model="$ctrl.args.shopDetails.location")
.form-group(ng-class="{'has-error': $ctrl.hasError(form.contractor)}")
label.control-label.col-md-3 Наименование организации *
.col-md-6
input.form-control(type="text" ng-model="$ctrl.args.contractor.registeredName" name="contractor" required)
.form-group
label.control-label.col-md-3 Тип юридического лица
.col-md-6
input.form-control(type="text" ng-model="$ctrl.args.contractor.legalEntity")
.form-group
label.control-label.col-md-3 Категория *
.col-md-6
select.select2_multiple.form-control.custom_select(select2 ng-model="$ctrl.args.categoryRef" required
ng-options="category.categoryRef as category.name for category in $ctrl.categories")
.ln_solid
.form-group
.col-md-6.col-md-offset-3
button.btn.btn-primary(type="submit" ng-click="$ctrl.createClaim(form)" ng-disabled="form.$invalid") Создать заявку
a.btn.btn-default.pull-right(type="reset" ng-link="['\Shops\']") Отмена

View File

@ -0,0 +1,5 @@
shops.constant('CLAIM_STATUSES', {
pending: 'pending',
approved: 'approved',
denied: 'denied'
});

View File

@ -0,0 +1,19 @@
shops.component('claims', {
templateUrl: 'components/shops/claims/claims.template.html',
controller: function (Claims) {
this.showClaimInfo = false;
Claims.get({claimStatus: 'pending'}, claim => {
this.claimID = claim.id;
this.showClaimInfo = true;
this.changeset = claim.changeset;
});
this.revoke = () => {
const claims = new Claims();
claims.$revoke({claimID: this.claimID}, () => {
this.showClaimInfo = false;
});
};
}
});

View File

@ -0,0 +1,21 @@
.x_panel.tile(ng-if="$ctrl.showClaimInfo")
.x_title
h4 Активная заявка
.x_content
.row(ng-repeat="set in $ctrl.changeset")
shop-creation(ng-if="set.modificationType == 'ShopCreation'" changeset="set")
shop-modification(ng-if="set.modificationType == 'ShopModificationUnit'" changeset="set")
button.btn.btn-danger.pull-right(data-toggle="modal" data-target=".revoke-modal" data-backdrop="false") Отмена заявки
.modal.fade.revoke-modal(tabindex="-1" role="dialog")
.modal-dialog.modal-sm
.modal-content
.modal-header
h4.modal-title Подтверждение
.modal-body Отменить заявку?
.modal-footer
.btn-toolbar.pull-right
.btn-group.btn-group-sm
button.btn.btn-default(type="button" data-dismiss="modal") Назад
.btn-group.btn-group-sm
button.btn.btn-danger(type="button" data-dismiss="modal" ng-click="$ctrl.revoke()") Отменить заявку

View File

@ -0,0 +1,11 @@
shops.component('modificationDetail', {
template: `
<dl ng-show="$ctrl.value">
<dt>{{$ctrl.displayName}}</dt>
<dd>{{$ctrl.value}}</dd>
</dl>`,
bindings: {
displayName: '<',
value: '<'
}
});

View File

@ -0,0 +1,12 @@
shops.component('shopCreation', {
templateUrl: 'components/shops/claims/shop-creation/shop-creation.template.html',
bindings: {
changeset: '<'
},
controller: function () {
this.showPanel = false;
this.show = () => this.showPanel = !this.showPanel;
this.shop = this.changeset.shop;
}
});

View File

@ -0,0 +1,12 @@
.panel.panel-default
.panel-heading(ng-click="$ctrl.show()")
a(href="#") Создание магазина: {{$ctrl.shop.shopDetails.name}}
i.fa.pull-right.custom-chevron(ng-class="{'fa-chevron-down': $ctrl.showPanel, 'fa-chevron-right': !$ctrl.showPanel}")
.panel-body(ng-show="$ctrl.showPanel")
div(ng-show="$ctrl.shop.shopDetails")
modification-detail(display-name="'Название магазина'" value="$ctrl.shop.shopDetails.name")
modification-detail(display-name="'Описание'" value="$ctrl.shop.shopDetails.description")
modification-detail(display-name="'Месторасположение'" value="$ctrl.shop.shopDetails.location")
div(ng-show="$ctrl.shop.contractor")
modification-detail(display-name="'Наименование организации'" value="$ctrl.shop.contractor.registeredName")
modification-detail(display-name="'Тип юридического лица'" value="$ctrl.shop.contractor.legalEntity")

View File

@ -0,0 +1,13 @@
shops.component('shopModification', {
templateUrl: 'components/shops/claims/shop-modification/shop-modification.template.html',
bindings: {
changeset: '<'
},
controller: function () {
this.showPanel = false;
this.isModification = this.changeset.details.modificationType === 'ShopModification';
this.details = this.changeset.details.details;
this.show = () => this.showPanel = !this.showPanel;
}
});

View File

@ -0,0 +1,12 @@
.panel.panel-default
.panel-heading(ng-click="$ctrl.show()")
a(href="#") Изменение магазина
i.fa.pull-right.custom-chevron(ng-class="{'fa-chevron-down': $ctrl.showPanel, 'fa-chevron-right': !$ctrl.showPanel}")
.panel-body(ng-show="$ctrl.showPanel")
div(ng-show="$ctrl.details.shopDetails")
modification-detail(display-name="'Название магазина'" value="$ctrl.details.shopDetails.name")
modification-detail(display-name="'Описание'" value="$ctrl.details.shopDetails.description")
modification-detail(display-name="'Месторасположение'" value="$ctrl.details.shopDetails.location")
div(ng-show="$ctrl.details.contractor")
modification-detail(display-name="'Наименование организации'" value="$ctrl.details.contractor.registeredName")
modification-detail(display-name="'Тип юридического лица'" value="$ctrl.details.contractor.legalEntity")

View File

@ -0,0 +1,78 @@
shops.component('editShop', {
templateUrl: 'components/shops/edit-shop/edit-shop.template.html',
bindings: {
$router: '<'
},
controller: function (Shops, Parties, Categories) {
this.args = {};
this.isLoading = false;
this.categories = Categories.query();
this.$routerOnActivate = route => {
this.isLoading = true;
this.shopID = route.params.shopId;
Parties.get(party => {
this.isLoading = false;
const shop = _.find(party.shops, shop => shop.shopID === this.shopID);
this.shopDetails = shop.shopDetails;
this.contractor = shop.contractor;
this.categoryRef = shop.categoryRef;
});
};
const back = () => {
this.args = {};
this.isLoading = false;
this.$router.navigate(['Shops']);
};
this.createClaim = form => {
if (!form.$valid) {
return;
}
this.args = getArgs(form);
this.isLoading = true;
const shops = new Shops(this.args);
shops.$save({shopID: this.shopID}, () => back());
};
function getArgs(form) {
const args = {};
if (form.category.$dirty) {
args.categoryRef = form.category.$modelValue;
}
if (form.shopDetailsName.$dirty) {
if (!args.shopDetails) {
args.shopDetails = {};
}
args.shopDetails.name = form.shopDetailsName.$modelValue;
}
if (form.shopDetailsDescription.$dirty) {
if (!args.shopDetails) {
args.shopDetails = {};
}
args.shopDetails.description = form.shopDetailsDescription.$modelValue;
}
if (form.shopDetailsLocation.$dirty) {
if (!args.shopDetails) {
args.shopDetails = {};
}
args.shopDetails.location = form.shopDetailsLocation.$modelValue;
}
if (form.contractorRegisteredName.$dirty) {
if (!args.contractor) {
args.contractor = {};
}
args.contractor.registeredName = form.contractorRegisteredName.$modelValue;
}
if (form.contractorLegalEntity.$dirty) {
if (!args.contractor) {
args.contractor = {};
}
args.contractor.legalEntity = form.contractorLegalEntity.$modelValue;
}
return args;
}
}
});

View File

@ -0,0 +1,41 @@
.row
.col-md-12
.x_panel.tile
.x_title
h4 Изменение данных магазина
.x_content
loading(is-loading="$ctrl.isLoading")
form.form-horizontal.form-label-left.css-form(novalidate name="form")
.form-group
label.control-label.col-md-3 Название магазина *
.col-md-6
input.form-control(type="text" ng-model="$ctrl.shopDetails.name" name="shopDetailsName" required)
.form-group
label.control-label.col-md-3 Описание магазина
.col-md-6
input.form-control(type="text" ng-model="$ctrl.shopDetails.description" name="shopDetailsDescription")
.form-group
label.control-label.col-md-3 Месторасположение
.col-md-6
input.form-control(type="text" ng-model="$ctrl.shopDetails.location" name="shopDetailsLocation")
.form-group
label.control-label.col-md-3 Наименование организации *
.col-md-6
input.form-control(type="text" ng-model="$ctrl.contractor.registeredName" name="contractorRegisteredName" required)
.form-group
label.control-label.col-md-3 Тип юридического лица
.col-md-6
input.form-control(type="text" ng-model="$ctrl.contractor.legalEntity" name="contractorLegalEntity")
.form-group
label.control-label.col-md-3 Категория *
.col-md-6
select.select2_multiple.form-control.custom_select(select2 ng-model="$ctrl.categoryRef" required name="category"
ng-options="category.categoryRef as category.name for category in $ctrl.categories")
.ln_solid
.form-group
.col-md-6.col-md-offset-3
button.btn.btn-primary(type="submit" ng-click="$ctrl.createClaim(form)" ng-disabled="form.$pristine") Создать заявку
a.btn.btn-default.pull-right(type="reset" ng-link="['\Shops\']") Отмена

View File

@ -0,0 +1,25 @@
shops.component('shops', {
templateUrl: 'components/shops/shops.template.html',
controller: function (Parties, Shops, Categories) {
this.categories = Categories.query();
this.getCategory = ref => _.find(this.categories, category => category.categoryRef === ref);
const getParty = () => {
this.isLoading = true;
Parties.get(party => {
this.isLoading = false;
this.shops = party.shops;
});
};
this.$routerOnActivate = () => getParty();
this.activate = shop => {
this.isLoading = true;
const shops = new Shops();
shops.$activate({shopID: shop.shopID}, () => getParty());
};
}
});

View File

@ -0,0 +1 @@
const shops = angular.module('shops', []);

View File

@ -0,0 +1,27 @@
claims
.x_panel.tile
.x_title
h4 Магазины
.x_content
loading(is-loading="$ctrl.isLoading")
table.table.table-hover.table-striped
thead
tr
th Название магазина
th Описание
th Месторасположение
th Категория
th
tbody
tr(ng-repeat="shop in $ctrl.shops" ng-class="{'success': shop.isSuspended, 'danger': shop.isBlocked}")
td {{shop.shopDetails.name}}
td {{shop.shopDetails.description}}
td {{shop.shopDetails.location}}
td {{$ctrl.getCategory(shop.categoryRef).name}}
td(ng-if="shop.isSuspended && !shop.isBlocked")
button.btn.btn-success.btn-xs.pull-right(ng-click="$ctrl.activate(shop)") Активировать
td(ng-if="!shop.isSuspended && !shop.isBlocked")
a.btn.btn-default.btn-xs.pull-right(ng-link="['\EditShop\', {shopId: shop.shopID}]") Изменить данные
td(ng-if="shop.isBlocked")
span.pull-right Заблокирован
a.btn.btn-primary.pull-right(ng-link="['\AddShop\']") Добавить новый магазин

View File

@ -3,6 +3,11 @@ const sidebar = angular.module('sidebar', []);
sidebar.component('sidebar', {
templateUrl: 'components/sidebar/sidebar.html',
controller: function ($location) {
this.isActive = location => location === $location.path();
this.$routerOnActivate = () => {
const path = $location.path();
console.log(path);
};
this.isActive = location => _.includes($location.path(), location);
}
});

View File

@ -7,11 +7,11 @@
#sidebar-menu.main_menu_side.hidden-print.main_menu
.menu_section
ul.nav.side-menu
li(ng-class="{active: $ctrl.isActive('/dashboard')}")
a(ng-link="[\'Dashboard\']")
i.fa.fa-home
| Обзор
li(ng-class="{active: $ctrl.isActive('/finance')}")
a(ng-link="[\'Finance\']")
li(ng-class="{active: $ctrl.isActive('/analytics')}")
a(ng-link="['\Analytics\']")
i.fa.fa-bar-chart-o
| Финансы
| Аналитика
li(ng-class="{active: $ctrl.isActive('/shops')}")
a(ng-link="['\Shops\']")
i.fa.fa-shopping-cart
| Мои магазины

View File

@ -4,9 +4,8 @@
ul.nav.navbar-nav.navbar-right
li
a.user-profile.dropdown-toggle(data-toggle="dropdown", aria-expanded="false")
img(src="img.png" alt="")
| {{$ctrl.profileName}}&nbsp
span.fa.fa-angle-down()
span {{$ctrl.profileName}}&nbsp
span.fa.fa-angle-down
ul.dropdown-menu.dropdown-usermenu.pull-right
li(data-toggle="modal" data-target=".bs-example-modal-lg")
a(href="")

View File

@ -1,7 +1,7 @@
{
"realm" : "external",
"realm-public-key" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2KZPnAJs0SS6/w39mDTrWYRjM86IFteU/dPGpGQdOPwNe85Ep2leVN3/FBKVUMHsFTtFkVsg/VcCEfYBj22B0mZ8zV2hQUCNq1NV2b2LnYYrDmThmFOOTnpbBhEOMS8Wrzj3Yk7mcDtKlMzoBNIQ/Z54ffymkyiKX8XOw45K9Cx1Bp/SVjOnJlm0Qu/+zE40/XVpzgjbaqSc9+8B3tur2E03EVemOa6EFhu7ocKsbSR7/fG1nYOGKjACS1Z+VYQTMcRqxZlLw7kv3fxUaMDcK5p/16YWpWggflVy5w26IIJeDcsXG+X3LV6f6dyo9ZfOEvGqQZmPCohaSOgi+IRwAQIDAQAB",
"auth-server-url" : "http://macroserver1.msk1.rbkmoney.net:81/auth/",
"auth-server-url" : "http://localhost:31245/auth/",
"ssl-required" : "external",
"resource" : "koffing",
"public-client" : true

View File

@ -0,0 +1,6 @@
resources.factory('Accounts', function ($resource, appConfig) {
return $resource(`${appConfig.capiUrl}processing/shops/:shopID/accounts/:accountID`, {
shopID: '@shopID',
accountID: '@accountID'
});
});

View File

@ -0,0 +1,5 @@
resources.factory('Categories', function ($resource, appConfig) {
return $resource(`${appConfig.capiUrl}processing/categories/:categoryRef`, {
categoryRef: '@categoryRef'
});
});

View File

@ -0,0 +1,7 @@
resources.factory('Claims', function ($resource, appConfig) {
return $resource(`${appConfig.capiUrl}processing/claims/:claimID/:action`, {
claimID: '@claimID'
}, {
revoke: {method: 'POST', params: {action: 'revoke'}}
});
});

View File

@ -1,16 +1,18 @@
resources.factory('Customers', function ($resource, appConfig) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/customers/stats/:statsType', {
shopID: appConfig.shopID
}, {
/**
* @typedef {Object} Parameters
* @property {dateTime} fromTime
* @property {dateTime} toTime
*/
return function (shopID) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/customers/stats/:statsType', {
shopID: shopID
}, {
/**
* @typedef {Object} Parameters
* @property {dateTime} fromTime
* @property {dateTime} toTime
*/
/**
* @returns {PaymentRateStat}
*/
rate: {method: 'GET', isArray: true, params: {statsType: 'rate'}}
});
/**
* @returns {PaymentRateStat}
*/
rate: {method: 'GET', isArray: true, params: {statsType: 'rate'}}
});
}
});

View File

@ -1,20 +1,22 @@
resources.factory('Invoices', function ($resource, appConfig) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/invoices', {
shopID: appConfig.shopID
}, {
/**
* @typedef {Object} Parameters
* @property {int} limit
* @property {int} offset
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} status
* @property {string} invoiceID
*/
return function (shopID) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/invoices', {
shopID: shopID
}, {
/**
* @typedef {Object} Parameters
* @property {int} limit
* @property {int} offset
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} status
* @property {string} invoiceID
*/
/**
* @returns {Invoices}
*/
search: {method: 'GET'}
});
});
/**
* @returns {Invoices}
*/
search: {method: 'GET'}
});
}
});

View File

@ -0,0 +1,3 @@
resources.factory('Parties', function ($resource, appConfig) {
return $resource(appConfig.capiUrl + 'processing/me');
});

View File

@ -1,47 +1,49 @@
resources.factory('Payments', function ($resource, appConfig) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/payments/stats/:statsType', {
shopID: appConfig.shopID
}, {
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
return function (shopID) {
return $resource(appConfig.capiUrl + 'analytics/shops/:shopID/payments/stats/:statsType', {
shopID: shopID
}, {
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
/**
* @returns {Array.<PaymentRevenueStat>}
*/
revenue: {method: 'GET', isArray: true, params: {statsType: 'revenue'}},
/**
* @returns {Array.<PaymentRevenueStat>}
*/
revenue: {method: 'GET', isArray: true, params: {statsType: 'revenue'}},
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
/**
* @returns {Array.<PaymentConversionStat>}
*/
conversion: {method: 'GET', isArray: true, params: {statsType: 'conversion'}},
/**
* @returns {Array.<PaymentConversionStat>}
*/
conversion: {method: 'GET', isArray: true, params: {statsType: 'conversion'}},
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
/**
* @typedef {Object} Parameters
* @property {string} invoiceID
* @property {dateTime} fromTime
* @property {dateTime} toTime
* @property {string} splitUnit
* @property {int} splitSize
*/
/**
* @returns {Array.<PaymentGeoStat>}
*/
geo: {method: 'GET', isArray: true, params: {statsType: 'geo'}},
});
/**
* @returns {Array.<PaymentGeoStat>}
*/
geo: {method: 'GET', isArray: true, params: {statsType: 'geo'}},
});
}
});

View File

@ -0,0 +1,7 @@
resources.factory('Shops', function ($resource, appConfig) {
return $resource(appConfig.capiUrl + 'processing/shops/:shopID/:action', {
shopID: '@shopID'
}, {
activate: {method: 'PUT', params: {action: 'activate'}}
});
});

11
capi-mock/accounts.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = [{
id: '1',
ownAmount: 1000000,
availableAmount: 1100000,
currency: 'rub'
}, {
id: '2',
ownAmount: 2000000,
availableAmount: 2100000,
currency: 'rub'
}];

View File

@ -9,23 +9,55 @@ const revenue = require('./revenue');
const conversion = require('./conversion');
const geo = require('./geo');
const rate = require('./rate');
const me = require('./me');
const claims = require('./claims');
const categories = require('./categories');
const shopAccounts = require('./shopAccounts');
const accounts = require('./accounts');
app.use((req, res, next) => {
res.header('Access-Control-Allow-Methods', 'PUT');
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Request-ID');
next();
});
router.route('/invoices').get((req, res) => setTimeout(() => res.json(invoices), 300));
router.route('/processing/me').get((req, res) => setTimeout(() => res.json(me), 600));
router.route('/payments/stats/revenue').get((req, res) => setTimeout(() => res.json(revenue), 0));
router.route('/processing/shops').post((req, res) => setTimeout(() => res.json("1"), 600));
router.route('/payments/stats/conversion').get((req, res) => setTimeout(() => res.json(conversion), 0));
router.route('/processing/shops/THRIFT-SHOP').post((req, res) => setTimeout(() => res.json("2"), 600));
router.route('/payments/stats/geo').get((req, res) => setTimeout(() => res.json(geo), 0));
router.route('/processing/shops/THRIFT-SHOP2/activate').put((req, res) => setTimeout(() => res.json("1"), 600));
router.route('/customers/stats/rate').get((req, res) => setTimeout(() => res.json(rate), 0));
router.route('/processing/shops/THRIFT-SHOP/accounts').get((req, res) => setTimeout(() => res.json(shopAccounts), 300));
app.use('/v1/analytics/shops/THRIFT-SHOP', router);
router.route('/processing/shops/THRIFT-SHOP/accounts/1').get((req, res) => setTimeout(() => res.json(accounts[0]), 600));
router.route('/processing/shops/THRIFT-SHOP/accounts/2').get((req, res) => setTimeout(() => res.json(accounts[1]), 300));
router.route('/analytics/shops/THRIFT-SHOP/invoices').get((req, res) => setTimeout(() => res.json(invoices), 300));
router.route('/analytics/shops/THRIFT-SHOP/payments/stats/revenue').get((req, res) => setTimeout(() => res.json(revenue), 0));
router.route('/analytics/shops/THRIFT-SHOP/payments/stats/conversion').get((req, res) => setTimeout(() => res.json(conversion), 0));
router.route('/analytics/shops/THRIFT-SHOP/payments/stats/geo').get((req, res) => setTimeout(() => res.json(geo), 0));
router.route('/analytics/shops/THRIFT-SHOP/customers/stats/rate').get((req, res) => setTimeout(() => res.json(rate), 0));
router.route('/processing/claims').get((req, res) => setTimeout(() => res.json(claims), 600));
// router.route('/processing/claims').get((req, res) => res.status(404).send('Not found'));
router.route('/processing/claims/1/revoke').post((req, res) => setTimeout(() => res.json(), 600));
router.route('/processing/categories').get((req, res) => setTimeout(() => res.json(categories), 300));
router.route('/processing/categories/1').get((req, res) => setTimeout(() => res.json(categories[0]), 300));
router.route('/processing/categories/1').get((req, res) => setTimeout(() => res.json(categories[0]), 300));
app.use('/v1', router);
app.listen(9000);

27
capi-mock/categories.js Normal file
View File

@ -0,0 +1,27 @@
module.exports = [
{
name: 'Test category 1',
categoryRef: 1,
description: 'description'
},
{
name: 'Test category 2',
categoryRef: 2,
description: 'description'
},
{
name: 'Test category 3',
categoryRef: 3,
description: 'description'
},
{
name: 'Test category 4',
categoryRef: 4,
description: 'description'
},
{
name: 'Test category 5',
categoryRef: 5,
description: 'description'
}
];

36
capi-mock/claims.js Normal file
View File

@ -0,0 +1,36 @@
const shops = require('./shops');
var claimStatus = {
status: 'pending'
};
var shopCreation = {
modificationType: 'ShopCreation',
shop: shops[0]
};
var shopModification = {
modificationType: 'ShopModificationUnit',
shopID: 'THRIFT-SHOP',
details: {
modificationType: 'ShopModification',
details: {
shopDetails : {
name: 'Test name',
description: 'Test description',
location: 'Moscow'
},
contractor: {
registeredName: 'Registered test name',
legalEntity: 'Legal entity'
},
categoryRef: '1'
}
}
};
module.exports = {
id: '1',
status: claimStatus,
changeset: [shopCreation, shopModification]
};

8
capi-mock/me.js Normal file
View File

@ -0,0 +1,8 @@
const shops = require('./shops');
module.exports = {
partyID: '1',
isBlocked: false,
isSuspended: false,
shops: shops
};

View File

@ -0,0 +1,6 @@
module.exports = [
{
generalID: '1',
guaranteeID: '2'
}
];

67
capi-mock/shops.js Normal file
View File

@ -0,0 +1,67 @@
module.exports = [{
shopID: 'THRIFT-SHOP',
isBlocked: false,
isSuspended: false,
categoryRef: 1,
shopDetails: {
name: 'Shop 1',
description: 'shop 1 description',
location: 'Moscow'
},
contractor: {
registeredName: 'Registered name',
legalEntity: 'legalEntity'
},
contract: {
number: '2341',
systemContractorRef: '34564',
concludedAt: 'concludedAt',
validSince: 'validSince',
validUntil: 'validUntil',
terminatedAt: 'terminatedAt'
}
}, {
shopID: 'THRIFT-SHOP2',
isBlocked: false,
isSuspended: true,
categoryRef: 2,
shopDetails: {
name: 'Shop 2',
description: 'shop 2 description',
location: 'Moscow'
},
contractor: {
registeredName: 'Registered name',
legalEntity: 'legalEntity'
},
contract: {
number: '4532',
systemContractorRef: '33422',
concludedAt: 'concludedAt',
validSince: 'validSince',
validUntil: 'validUntil',
terminatedAt: 'terminatedAt'
}
}, {
shopID: 'THRIFT-SHOP3',
isBlocked: true,
isSuspended: true,
categoryRef: 3,
shopDetails: {
name: 'Shop 3',
description: 'shop 3 description',
location: 'Moscow'
},
contractor: {
registeredName: 'Registered name',
legalEntity: 'legalEntity'
},
contract: {
number: '2345',
systemContractorRef: '23435',
concludedAt: 'concludedAt',
validSince: 'validSince',
validUntil: 'validUntil',
terminatedAt: 'terminatedAt'
}
}];