TT#28106 PBXConfig: As a Customer, I want to add new PBX Groups

Change-Id: I44f20dc9c9ee6837e16210495bfe84b4d471489b
changes/34/17834/9
Hans-Peter Herzog 8 years ago
parent 4ff24c087d
commit d2301fda1c

@ -1,7 +1,8 @@
import Vue from 'vue';
import { getJsonBody } from './utils';
import { getNumbers } from './user';
import { getNumbers, assignNumber, assignNumbers } from './user';
import { createSubscriber } from './subscriber';
var assumedRows = 1000;
@ -12,7 +13,7 @@ export function getAllPbxSubscribers() {
return Vue.http.get('/api/subscribers', {
params: _.assign(params, {
page: 1,
rows: assumedRows,
rows: assumedRows
})
});
}).then((res)=>{
@ -33,11 +34,11 @@ export function getAllPbxSubscribers() {
let seats = [];
let groups = [];
subscribers.forEach((subscriber)=>{
if(subscriber.is_pbx_group) {
groups.push(subscriber);
} else if(subscriber.is_pbx_pilot) {
if(_.has(subscriber, 'is_pbx_pilot') && subscriber.is_pbx_pilot === true) {
pilot = subscriber;
} else {
} else if(_.has(subscriber, 'is_pbx_group') && subscriber.is_pbx_group === true) {
groups.push(subscriber);
} else if (_.has(subscriber, 'pbx_extension') && subscriber.pbx_extension !== null) {
seats.push(subscriber);
}
});
@ -69,3 +70,35 @@ export function getPbxConfiguration() {
});
});
}
export function addGroup(group) {
return new Promise((resolve, reject)=>{
let randomToken = ()=>{ return 'd' + Date.now() + "r" + (Math.round(Math.random() * 1000000) + 1000000); };
Promise.resolve().then(()=>{
return createSubscriber({
customer_id: group.customerId,
domain_id: group.domainId,
username: randomToken(),
password: randomToken(),
display_name: group.name,
is_pbx_group: true,
pbx_extension: group.extension,
pbx_hunt_policy: group.huntPolicy,
pbx_hunt_timeout: group.huntTimeout,
pbx_groupmember_ids: group.seats
});
}).then((subscriberId)=>{
return assignNumbers(group.aliasNumbers, subscriberId);
}).then(()=>{
resolve();
}).catch((err)=>{
reject(err);
});
});
}
export function deleteGroup(id) {
return new Promise((resolve, reject)=>{
});
}

@ -179,3 +179,36 @@ export function enablePrivacy(id) {
export function disablePrivacy(id) {
return setPrivacy(id, false);
}
export function createSubscriber(subscriber) {
return new Promise((resolve, reject)=>{
Vue.http.post('/api/subscribers/', subscriber, {
params: {
customer_id: subscriber.customer_id
}
}).then((res)=>{
resolve(_.last(_.split(res.headers.get('Location'), '/')));
}).catch((err)=>{
if(err.status === 422) {
reject(new Error(err.body.message));
} else {
reject(err);
}
});
});
}
// export function deleteSubscriber(id) {
// return new Promise((resolve, reject)=>{
// Vue.http.delete('/api/subscribers/', null, {
// }).then((res)=>{
// resolve(_.last(_.split(res.headers.get('Location'), '/')));
// }).catch((err)=>{
// if(err.status === 422) {
// reject(new Error(err.body.message));
// } else {
// reject(err);
// }
// });
// });
// }

@ -68,6 +68,44 @@ export function getCapabilities() {
});
}
export function assignNumber(numberId, subscriberId) {
return new Promise((resolve, reject)=>{
var headers = {};
headers['Content-Type'] = 'application/json-patch+json';
Promise.resolve().then((result)=>{
return Vue.http.patch('/api/numbers/' + numberId, [{
op: 'replace',
path: '/subscriber_id',
value: subscriberId
}], {
headers: headers
});
}).then(()=>{
resolve();
}).catch((err)=>{
reject(err);
});
});
}
export function assignNumbers(numberIds, subscriberId) {
return new Promise((resolve, reject)=>{
if(_.isArray(numberIds) && numberIds.length > 0) {
let assignNumberRequests = [];
numberIds.forEach((numberId)=>{
assignNumberRequests.push(assignNumber(numberId, subscriberId));
});
Promise.all(assignNumberRequests).then(()=>{
resolve();
}).catch((err)=>{
reject(err);
});
} else {
reject(new Error('No numberIds given'));
}
});
}
export function getNumbers() {
return new Promise((resolve, reject)=>{
let params = {};

@ -1,5 +1,5 @@
<template>
<q-card class="csc-card-collapsible csc-pbx-group">
<q-card class="csc-pbx-group">
<q-card-title>
<q-icon name="group" color="primary" size="22px"/>
<span class="csc-pbx-group-title">{{ group.display_name }}</span>
@ -9,7 +9,7 @@
</q-card-title>
<q-card-main>
<q-field :label="$t('pbxConfig.groupName')">
<q-input v-model="name" readonly/>
<q-input v-model="name" readonly />
</q-field>
<q-field :label="$t('pbxConfig.extension')">
<q-input v-model="extension" readonly />
@ -18,26 +18,25 @@
<q-select v-model="huntPolicy" :options="huntPolicyOptions" readonly radio />
</q-field>
<q-field :label="$t('pbxConfig.huntTimeout')">
<q-input v-model="huntTimeout" readonly suffix="s" readonly/>
<q-input v-model="huntTimeout" readonly suffix="seconds" readonly min="0" />
</q-field>
<q-field :label="$t('pbxConfig.primaryNumber')">
<q-input v-model="primaryNumber" readonly disabled />
</q-field>
<q-field :label="$t('pbxConfig.aliasNumbers')">
<q-select v-model="aliasNumbers" :options="allAliasNumbers" multiple chips readonly clearable />
<q-select v-model="aliasNumbers" :options="aliasNumberOptions" multiple chips readonly clearable />
</q-field>
<q-field :label="$t('pbxConfig.seats')">
<q-select v-model="assignedSeats" :options="allSeats" multiple chips readonly clearable />
<q-select v-model="seats" :options="seatOptions" multiple chips readonly clearable />
</q-field>
</q-card-main>
<q-card-actions>
</q-card-actions>
</q-card>
</template>
<script>
import CscNumberChip from '../../card/CscNumberChip'
import _ from 'lodash';
import numberFilter from '../../../filters/number'
import {
QCard,
@ -52,7 +51,11 @@
} from 'quasar-framework'
export default {
name: 'csc-pbx-group',
props: ['group'],
props: ['group',
'huntPolicyOptions',
'aliasNumberOptions',
'seatOptions',
],
data () {
return {}
},
@ -65,8 +68,7 @@
QInput,
QIcon,
QSelect,
QChip,
CscNumberChip
QChip
},
computed: {
name() {
@ -86,96 +88,21 @@
},
aliasNumbers() {
let numbers = [];
if(this.group.alias_numbers) {
if(_.isArray(this.group.alias_numbers)) {
this.group.alias_numbers.forEach((number)=>{
numbers.push(number.number_id);
});
}
return numbers;
},
huntPolicyOptions() {
return [
{
label: this.$t('pbxConfig.serialRinging'),
value: 'serial'
},
{
label: this.$t('pbxConfig.parallelRinging'),
value: 'parallel'
},
{
label: this.$t('pbxConfig.randomRinging'),
value: 'random'
},
{
label: this.$t('pbxConfig.circularRinging'),
value: 'circular'
}
];
},
assignedSeats() {
seats() {
let seats = [];
if(this.group.seats) {
if(_.isArray(this.group.seats)) {
this.group.seats.forEach((seat)=>{
seats.push(seat.id);
});
}
return seats;
},
allSeats() {
let seats = [];
if(this.$store.getters['pbxConfig/seats']) {
this.$store.getters['pbxConfig/seats'].forEach((seat)=>{
seats.push({
label: seat.display_name,
sublabel: this.$t('pbxConfig.extension') + ': ' + seat.pbx_extension,
value: seat.id
});
});
}
return seats;
},
allPrimaryNumbers() {
let numbers = [];
if(this.$store.getters['pbxConfig/numbers']) {
this.$store.getters['pbxConfig/numbers'].forEach((number)=>{
if(number.is_primary) {
numbers.push({
label: numberFilter(number),
value: number.id
});
}
});
}
return numbers;
},
allAliasNumbers() {
let numbers = [];
if(this.$store.getters['pbxConfig/numbers']) {
this.$store.getters['pbxConfig/numbers'].forEach((number)=>{
if(!number.is_primary) {
let owner = this.$t('pbxConfig.allocatedByNobody');
if(number.subscriber !== null && number.subscriber.display_name !== null &&
number.subscriber.is_pbx_group) {
owner = this.$t('pbxConfig.allocatedBy', {
type: this.$t('pbxConfig.group'),
name: number.subscriber.display_name
});
} else if (number.subscriber !== null && number.subscriber.display_name !== null) {
owner = this.$t('pbxConfig.allocatedBy', {
type: this.$t('pbxConfig.seat'),
name: number.subscriber.display_name
});
}
numbers.push({
label: numberFilter(number),
sublabel: owner,
value: number.id
});
}
});
}
return numbers;
}
}
}

@ -1,27 +1,96 @@
<template>
<csc-page title="Groups">
<csc-pbx-group v-for="group in groups" :group="group" />
<csc-page id="csc-page-pbx-groups" title="Groups">
<q-card v-if="groupFormEnabled" class="add-form">
<q-card-title>
<q-icon name="add" color="primary" size="22px"/>
<span>Add Group</span>
</q-card-title>
<q-card-main>
<q-field>
<q-input :disabled="addGroupIsRequesting" ref="groupName" v-model="groupForm.name" autofocus
:float-label="$t('pbxConfig.groupName')" clearable />
</q-field>
<q-field>
<q-input :disabled="addGroupIsRequesting" type="number" v-model="groupForm.extension" clearable min="1" max="1000000"
:float-label="$t('pbxConfig.extension')" />
</q-field>
<q-field>
<q-select :disabled="addGroupIsRequesting" v-model="groupForm.huntPolicy" :float-label="$t('pbxConfig.huntPolicy')"
:options="huntPolicyOptions" radio />
</q-field>
<q-field >
<q-input :disabled="addGroupIsRequesting" type="number" v-model="groupForm.huntTimeout" :float-label="$t('pbxConfig.huntTimeout')"
suffix="seconds" min="1" max="3600" clearable />
</q-field>
<q-field>
<q-select :disabled="addGroupIsRequesting" v-model="groupForm.aliasNumbers" :float-label="$t('pbxConfig.aliasNumbers')"
:options="aliasNumberOptions" multiple chips readonly clearable />
</q-field>
<q-field>
<q-select :disabled="addGroupIsRequesting" v-model="groupForm.seats" :float-label="$t('pbxConfig.seats')"
:options="seatOptions" multiple chips readonly clearable />
</q-field>
</q-card-main>
<q-card-separator/>
<q-card-actions align="center">
<q-btn v-if="!addGroupIsRequesting" flat color="secondary" icon="clear" @click="disableGroupForm()">{{ $t('buttons.cancel') }}</q-btn>
<q-btn loader v-model="addGroupIsRequesting" flat color="primary" icon="done" @click="addGroup()">{{ $t('buttons.save') }}</q-btn>
</q-card-actions>
</q-card>
<q-card v-else flat>
<q-card-actions align="center">
<q-btn color="primary" icon="add" flat @click="enableGroupForm">{{ $t('pbxConfig.addGroup') }}</q-btn>
</q-card-actions>
</q-card>
<q-card v-if="listIsRequesting" flat>
<q-card-actions align="center">
<q-spinner-dots color="primary" :size="40"/>
</q-card-actions>
</q-card>
<csc-pbx-group v-for="group in groups" :group="group" :all-seats="seats"
:all-alias-numbers="aliasNumbers" :all-primary-numbers="primaryNumbers"
:alias-number-options="aliasNumberOptions" :seat-options="seatOptions"
:hunt-policy-options="huntPolicyOptions"/>
</csc-page>
</template>
<script>
import { startLoading, stopLoading, showGlobalError, showToast } from '../../../helpers/ui'
import CscPage from '../../CscPage'
import CscNumberChip from '../../card/CscNumberChip'
import CscPbxGroup from './CscPbxGroup'
import { QChip, QCard, QCardSeparator, QCardTitle, QCardMain,
QIcon, QPopover, QList, QItem, QItemMain, QField, QInput } from 'quasar-framework'
import {
QChip,
QCard,
QCardSeparator,
QCardTitle,
QCardMain,
QCardActions,
QIcon,
QPopover,
QList,
QItem,
QItemMain,
QField,
QInput,
QBtn,
QSelect,
QInnerLoading,
QSpinnerGears,
QSpinnerDots
} from 'quasar-framework'
import { mapState } from 'vuex'
import numberFilter from '../../../filters/number'
export default {
components: {
CscPage,
CscNumberChip,
QChip,
QCard,
QCardSeparator,
QCardTitle,
QCardMain,
QCardActions,
QIcon,
QPopover,
QList,
@ -29,17 +98,144 @@
QItemMain,
QField,
QInput,
CscPbxGroup
CscPbxGroup,
QBtn,
QSelect,
QInnerLoading,
QSpinnerGears,
QSpinnerDots
},
mounted() {
this.$store.dispatch('pbxConfig/listGroups');
},
data () {
return {}
return {
groupForm: {
name: '',
extension: '',
huntPolicy: 'serial',
huntTimeout: 10,
aliasNumbers: [],
seats: []
},
groupFormEnabled: false,
}
},
computed: {
groups() {
return this.$store.getters['pbxConfig/groups'];
},
seats() {
return this.$store.getters['pbxConfig/seats'];
},
aliasNumbers() {
return this.$store.getters['pbxConfig/aliasNumbers'];
},
huntPolicyOptions() {
return [
{
label: this.$t('pbxConfig.serialRinging'),
value: 'serial'
},
{
label: this.$t('pbxConfig.parallelRinging'),
value: 'parallel'
},
{
label: this.$t('pbxConfig.randomRinging'),
value: 'random'
},
{
label: this.$t('pbxConfig.circularRinging'),
value: 'circular'
}
];
},
aliasNumberOptions() {
let aliasNumber = [];
this.aliasNumbers.forEach((number)=>{
let owner = this.$t('pbxConfig.allocatedByNobody');
if(number.subscriber !== null && number.subscriber.display_name !== null &&
number.subscriber.is_pbx_group) {
owner = this.$t('pbxConfig.allocatedBy', {
type: this.$t('pbxConfig.group'),
name: number.subscriber.display_name
});
} else if (number.subscriber !== null && number.subscriber.display_name !== null) {
owner = this.$t('pbxConfig.allocatedBy', {
type: this.$t('pbxConfig.seat'),
name: number.subscriber.display_name
});
}
aliasNumber.push({
label: numberFilter(number),
sublabel: owner,
value: number.id
});
});
return aliasNumber;
},
seatOptions() {
let seats = [];
this.seats.forEach((seat)=>{
seats.push({
label: seat.display_name,
sublabel: this.$t('pbxConfig.extension') + ': ' + seat.pbx_extension,
value: seat.id
});
});
return seats;
},
listIsRequesting() {
return this.listState === 'requesting';
},
listState() {
return this.$store.state.pbxConfig.listAllState;
},
listError() {
return this.$store.state.pbxConfig.listAllError;
},
addGroupIsRequesting() {
return this.addGroupState === 'requesting';
},
addGroupState() {
return this.$store.state.pbxConfig.addGroupState;
},
addGroupError() {
return this.$store.state.pbxConfig.addGroupError;
}
},
watch: {
addGroupState(state) {
if(state === 'failed') {
showGlobalError(this.addGroupError);
}
if(state === 'succeeded') {
this.disableGroupForm();
}
}
},
methods: {
resetGroupForm() {
this.groupForm = {
name: '',
extension: '',
huntPolicy: 'serial',
huntTimeout: 10,
aliasNumbers: [],
seats: []
}
},
enableGroupForm() {
this.resetGroupForm();
this.groupFormEnabled = true;
},
disableGroupForm() {
this.resetGroupForm();
this.groupFormEnabled = false;
},
addGroup() {
this.$store.dispatch('pbxConfig/addGroup', this.groupForm);
}
}
}
@ -47,4 +243,7 @@
<style lang="stylus">
@import '../../../../src/themes/app.variables.styl';
.add-form .q-field:last-child {
margin-bottom: 36px;
}
</style>

@ -163,6 +163,7 @@
"randomRinging": "Random Ringing",
"circularRinging": "Circular Ringing",
"allocatedByNobody": "Free",
"allocatedBy": "Allocated by {type} {name}"
"allocatedBy": "Allocated by {type} {name}",
"addGroup": "Add Group"
}
}

@ -1,6 +1,21 @@
import _ from 'lodash';
import { getPbxConfiguration } from '../api/pbx-config'
import { getPbxConfiguration, addGroup } from '../api/pbx-config'
const ListState = {
initiated: 'initiated',
requesting: 'requesting',
succeeded: 'succeeded',
failed: 'failed'
};
const AddGroupState = {
button: 'button',
input: 'input',
requesting: 'requesting',
succeeded: 'succeeded',
failed: 'failed'
};
export default {
namespaced: true,
@ -10,7 +25,12 @@ export default {
groupsOrdered: [],
seats: {},
seatsOrdered: [],
numbers: []
numbers: [],
numbersMap : {},
listAllState: ListState.initiated,
listAllError: null,
addGroupState: AddGroupState.button,
addGroupError: null
},
getters: {
groups(state, getters) {
@ -20,22 +40,46 @@ export default {
return state.seatsOrdered;
},
numbers(state, getters) {
return state.numbers;
return _.get(state, 'numbers', []);
},
primaryNumbers(state, getters) {
let numbers = getters.numbers;
let primaryNumbers = [];
if(_.isArray(numbers)) {
numbers.forEach((number)=>{
if(number.is_primary) {
primaryNumbers.push(number);
}
});
}
return primaryNumbers;
},
aliasNumbers(state, getters) {
let numbers = getters.numbers;
let aliasNumbers = [];
if(_.isArray(numbers) && numbers.length) {
numbers.forEach((number)=>{
if(!number.is_primary) {
aliasNumbers.push(number);
}
});
}
return aliasNumbers;
}
},
mutations: {
show: function(state, options) {
state.groups = options.groups;
listAllRequesting(state) {
state.listAllState = ListState.requesting;
},
listAll(state, all) {
listAllSucceeded(state, all) {
state.listAllState = ListState.succeeded;
state.listAllError = null;
state.pilot = all.pilot;
state.groups = {};
state.groupsOrdered = [];
state.seats = {};
state.seatsOrdered = [];
state.numbersMap = {};
all.groups.forEach((group)=>{
state.groups[group.id] = group;
state.groupsOrdered.push(group);
@ -64,28 +108,51 @@ export default {
} else {
number.subscriber = null;
}
state.numbersMap[number.id] = number;
});
state.numbers = all.numbers;
}
_.reverse(state.groupsOrdered);
_.reverse(state.seatsOrdered);
},
listAllFailed(state, error) {
state.listAllState = ListState.failed;
state.listAllError = error;
},
addGroupRequesting(state){
state.addGroupState = AddGroupState.requesting;
state.addGroupError = null;
},
addGroupSucceeded(state){
state.addGroupState = AddGroupState.succeeded;
state.addGroupError = null;
},
addGroupFailed(state, error) {
state.addGroupState = AddGroupState.failed;
state.addGroupError = error;
}
},
actions: {
listSeats(context, options) {
return new Promise((resolve, reject)=>{
listSeats(context) {
return context.dispatch('listGroups');
},
listGroups(context) {
context.commit('listAllRequesting');
getPbxConfiguration().then((config)=>{
context.commit('listAll', config);
context.commit('listAllSucceeded', config);
}).catch((err)=>{
console.log(err);
});
context.commit('listAllFailed', err.message);
});
},
listGroups(context, options) {
return new Promise((resolve, reject)=>{
getPbxConfiguration().then((config)=>{
context.commit('listAll', config);
addGroup(context, group) {
context.commit('addGroupRequesting');
group.customerId = context.state.pilot.customer_id;
group.domainId = context.state.pilot.domain_id;
addGroup(group).then(()=>{
context.commit('addGroupSucceeded');
context.dispatch('listGroups');
}).catch((err)=>{
console.log(err);
});
context.commit('addGroupFailed', err.message);
});
}
}

@ -36,7 +36,7 @@ describe('PBX Configuration Store', () => {
}
]
};
PbxConfig.mutations.listAll(state, data);
PbxConfig.mutations.listAllSucceeded(state, data);
assert.equal(state.seats[2], data.seats[0]);
assert.equal(state.seats[3], data.seats[1]);
assert.equal(state.groups[4], data.groups[0]);

Loading…
Cancel
Save