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 Vue from 'vue';
import { getJsonBody } from './utils'; import { getJsonBody } from './utils';
import { getNumbers } from './user'; import { getNumbers, assignNumber, assignNumbers } from './user';
import { createSubscriber } from './subscriber';
var assumedRows = 1000; var assumedRows = 1000;
@ -12,7 +13,7 @@ export function getAllPbxSubscribers() {
return Vue.http.get('/api/subscribers', { return Vue.http.get('/api/subscribers', {
params: _.assign(params, { params: _.assign(params, {
page: 1, page: 1,
rows: assumedRows, rows: assumedRows
}) })
}); });
}).then((res)=>{ }).then((res)=>{
@ -33,11 +34,11 @@ export function getAllPbxSubscribers() {
let seats = []; let seats = [];
let groups = []; let groups = [];
subscribers.forEach((subscriber)=>{ subscribers.forEach((subscriber)=>{
if(subscriber.is_pbx_group) { if(_.has(subscriber, 'is_pbx_pilot') && subscriber.is_pbx_pilot === true) {
groups.push(subscriber);
} else if(subscriber.is_pbx_pilot) {
pilot = subscriber; 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); 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) { export function disablePrivacy(id) {
return setPrivacy(id, false); 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() { export function getNumbers() {
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
let params = {}; let params = {};

@ -1,5 +1,5 @@
<template> <template>
<q-card class="csc-card-collapsible csc-pbx-group"> <q-card class="csc-pbx-group">
<q-card-title> <q-card-title>
<q-icon name="group" color="primary" size="22px"/> <q-icon name="group" color="primary" size="22px"/>
<span class="csc-pbx-group-title">{{ group.display_name }}</span> <span class="csc-pbx-group-title">{{ group.display_name }}</span>
@ -18,26 +18,25 @@
<q-select v-model="huntPolicy" :options="huntPolicyOptions" readonly radio /> <q-select v-model="huntPolicy" :options="huntPolicyOptions" readonly radio />
</q-field> </q-field>
<q-field :label="$t('pbxConfig.huntTimeout')"> <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>
<q-field :label="$t('pbxConfig.primaryNumber')"> <q-field :label="$t('pbxConfig.primaryNumber')">
<q-input v-model="primaryNumber" readonly disabled /> <q-input v-model="primaryNumber" readonly disabled />
</q-field> </q-field>
<q-field :label="$t('pbxConfig.aliasNumbers')"> <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>
<q-field :label="$t('pbxConfig.seats')"> <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-field>
</q-card-main> </q-card-main>
<q-card-actions> <q-card-actions>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</template> </template>
<script> <script>
import CscNumberChip from '../../card/CscNumberChip' import _ from 'lodash';
import numberFilter from '../../../filters/number' import numberFilter from '../../../filters/number'
import { import {
QCard, QCard,
@ -52,7 +51,11 @@
} from 'quasar-framework' } from 'quasar-framework'
export default { export default {
name: 'csc-pbx-group', name: 'csc-pbx-group',
props: ['group'], props: ['group',
'huntPolicyOptions',
'aliasNumberOptions',
'seatOptions',
],
data () { data () {
return {} return {}
}, },
@ -65,8 +68,7 @@
QInput, QInput,
QIcon, QIcon,
QSelect, QSelect,
QChip, QChip
CscNumberChip
}, },
computed: { computed: {
name() { name() {
@ -86,96 +88,21 @@
}, },
aliasNumbers() { aliasNumbers() {
let numbers = []; let numbers = [];
if(this.group.alias_numbers) { if(_.isArray(this.group.alias_numbers)) {
this.group.alias_numbers.forEach((number)=>{ this.group.alias_numbers.forEach((number)=>{
numbers.push(number.number_id); numbers.push(number.number_id);
}); });
} }
return numbers; return numbers;
}, },
huntPolicyOptions() { seats() {
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() {
let seats = []; let seats = [];
if(this.group.seats) { if(_.isArray(this.group.seats)) {
this.group.seats.forEach((seat)=>{ this.group.seats.forEach((seat)=>{
seats.push(seat.id); seats.push(seat.id);
}); });
} }
return seats; 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> <template>
<csc-page title="Groups"> <csc-page id="csc-page-pbx-groups" title="Groups">
<csc-pbx-group v-for="group in groups" :group="group" /> <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> </csc-page>
</template> </template>
<script> <script>
import { startLoading, stopLoading, showGlobalError, showToast } from '../../../helpers/ui'
import CscPage from '../../CscPage' import CscPage from '../../CscPage'
import CscNumberChip from '../../card/CscNumberChip'
import CscPbxGroup from './CscPbxGroup' import CscPbxGroup from './CscPbxGroup'
import { QChip, QCard, QCardSeparator, QCardTitle, QCardMain, import {
QIcon, QPopover, QList, QItem, QItemMain, QField, QInput } from 'quasar-framework' 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 { mapState } from 'vuex'
import numberFilter from '../../../filters/number'
export default { export default {
components: { components: {
CscPage, CscPage,
CscNumberChip,
QChip, QChip,
QCard, QCard,
QCardSeparator, QCardSeparator,
QCardTitle, QCardTitle,
QCardMain, QCardMain,
QCardActions,
QIcon, QIcon,
QPopover, QPopover,
QList, QList,
@ -29,17 +98,144 @@
QItemMain, QItemMain,
QField, QField,
QInput, QInput,
CscPbxGroup CscPbxGroup,
QBtn,
QSelect,
QInnerLoading,
QSpinnerGears,
QSpinnerDots
}, },
mounted() { mounted() {
this.$store.dispatch('pbxConfig/listGroups'); this.$store.dispatch('pbxConfig/listGroups');
}, },
data () { data () {
return {} return {
groupForm: {
name: '',
extension: '',
huntPolicy: 'serial',
huntTimeout: 10,
aliasNumbers: [],
seats: []
},
groupFormEnabled: false,
}
}, },
computed: { computed: {
groups() { groups() {
return this.$store.getters['pbxConfig/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"> <style lang="stylus">
@import '../../../../src/themes/app.variables.styl'; @import '../../../../src/themes/app.variables.styl';
.add-form .q-field:last-child {
margin-bottom: 36px;
}
</style> </style>

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

@ -1,6 +1,21 @@
import _ from 'lodash'; 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 { export default {
namespaced: true, namespaced: true,
@ -10,7 +25,12 @@ export default {
groupsOrdered: [], groupsOrdered: [],
seats: {}, seats: {},
seatsOrdered: [], seatsOrdered: [],
numbers: [] numbers: [],
numbersMap : {},
listAllState: ListState.initiated,
listAllError: null,
addGroupState: AddGroupState.button,
addGroupError: null
}, },
getters: { getters: {
groups(state, getters) { groups(state, getters) {
@ -20,22 +40,46 @@ export default {
return state.seatsOrdered; return state.seatsOrdered;
}, },
numbers(state, getters) { 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) { 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: { mutations: {
show: function(state, options) { listAllRequesting(state) {
state.groups = options.groups; state.listAllState = ListState.requesting;
}, },
listAll(state, all) { listAllSucceeded(state, all) {
state.listAllState = ListState.succeeded;
state.listAllError = null;
state.pilot = all.pilot; state.pilot = all.pilot;
state.groups = {}; state.groups = {};
state.groupsOrdered = []; state.groupsOrdered = [];
state.seats = {}; state.seats = {};
state.seatsOrdered = []; state.seatsOrdered = [];
state.numbersMap = {};
all.groups.forEach((group)=>{ all.groups.forEach((group)=>{
state.groups[group.id] = group; state.groups[group.id] = group;
state.groupsOrdered.push(group); state.groupsOrdered.push(group);
@ -64,28 +108,51 @@ export default {
} else { } else {
number.subscriber = null; number.subscriber = null;
} }
state.numbersMap[number.id] = number;
}); });
state.numbers = all.numbers; 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: { actions: {
listSeats(context, options) { listSeats(context) {
return new Promise((resolve, reject)=>{ return context.dispatch('listGroups');
},
listGroups(context) {
context.commit('listAllRequesting');
getPbxConfiguration().then((config)=>{ getPbxConfiguration().then((config)=>{
context.commit('listAll', config); context.commit('listAllSucceeded', config);
}).catch((err)=>{ }).catch((err)=>{
console.log(err); context.commit('listAllFailed', err.message);
});
}); });
}, },
listGroups(context, options) { addGroup(context, group) {
return new Promise((resolve, reject)=>{ context.commit('addGroupRequesting');
getPbxConfiguration().then((config)=>{ group.customerId = context.state.pilot.customer_id;
context.commit('listAll', config); group.domainId = context.state.pilot.domain_id;
addGroup(group).then(()=>{
context.commit('addGroupSucceeded');
context.dispatch('listGroups');
}).catch((err)=>{ }).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[2], data.seats[0]);
assert.equal(state.seats[3], data.seats[1]); assert.equal(state.seats[3], data.seats[1]);
assert.equal(state.groups[4], data.groups[0]); assert.equal(state.groups[4], data.groups[0]);

Loading…
Cancel
Save