Compare commits

...

5 Commits

Author SHA1 Message Date
Donghuang
ae4c595551 CallSatisfaction query bug fixes 2021-12-21 19:12:06 +08:00
Shaun Chyxion
4c3009126f catch fetch calling list error 2021-11-18 23:54:31 +08:00
Shaun Chyxion
3e1f16dea5 add batch create agent 2021-11-17 01:01:43 +08:00
Shaun Chyxion
8533faf904 add batch op 2021-11-16 01:17:02 +08:00
Donghuang
8a270e4a9d trunk dialout add blacklist validate 2021-11-07 20:47:06 +08:00
16 changed files with 363 additions and 35 deletions

View File

@ -1,12 +1,15 @@
package com.pudonghot.yo.cms.controller;
import lombok.val;
import javax.validation.Valid;
import com.wacai.tigon.form.FormList;
import me.chyxion.tigon.mybatis.Search;
import com.pudonghot.yo.model.domain.Queue;
import com.pudonghot.yo.model.domain.Agent;
import com.wacai.tigon.web.controller.ArgQuery;
import com.pudonghot.yo.cms.form.BatchForm;
import com.wacai.tigon.web.annotation.ListApi;
import com.wacai.tigon.web.controller.ArgQuery;
import com.pudonghot.yo.cms.service.AgentService;
import org.springframework.stereotype.Controller;
import com.pudonghot.yo.model.domain.AgentGroup;
import com.wacai.tigon.web.annotation.FilterCol;
@ -18,6 +21,8 @@ import com.pudonghot.yo.cms.annotation.TenantResource;
import com.pudonghot.yo.cms.form.create.CreateFormAgent;
import com.pudonghot.yo.cms.form.update.UpdateFormAgent;
import com.wacai.tigon.web.controller.BaseCrudController;
import org.springframework.web.bind.annotation.PostMapping;
import com.pudonghot.yo.cms.form.create.CreateBatchFormAgent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@ -72,10 +77,30 @@ public class AgentController
protected void after(final ArgQuery<?> arg) {
super.after(arg);
val model = arg.getResult();
final Integer tenantId = getTenantId();
val tenantId = getTenantId();
model.setAttr("groupsList", agentGroupService.list(
new Search(AgentGroup.TENANT_ID, tenantId)));
model.setAttr("queuesList", queueService.list(
new Search(Queue.TENANT_ID, tenantId)));
}
@PostMapping("/create-batch")
public void createBatch(@Valid CreateBatchFormAgent form) {
((AgentService) queryService).createBatch(form);
}
@PostMapping("/enable-batch")
public void enableBatch(@Valid BatchForm form) {
((AgentService) queryService).enableBatch(form);
}
@PostMapping("/disable-batch")
public void disableBatch(@Valid BatchForm form) {
((AgentService) queryService).disableBatch(form);
}
@PostMapping("/remove-batch")
public void removeBatch(@Valid BatchForm form) {
((AgentService) queryService).removeBatch(form);
}
}

View File

@ -11,7 +11,7 @@ import javax.validation.constraints.NotEmpty;
*/
@Setter
@Getter
public class BatchForm<T> extends BasicForm {
public class BatchForm extends BasicForm {
@NotEmpty
private List<T> ids;
private Integer[] ids;
}

View File

@ -0,0 +1,42 @@
package com.pudonghot.yo.cms.form.create;
import lombok.Getter;
import lombok.Setter;
import com.pudonghot.yo.model.domain.Agent;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import com.pudonghot.yo.cms.form.TaggableForm;
import com.wacai.tigon.format.annotation.Trim;
import com.pudonghot.yo.cms.form.BaseCreateForm;
import org.hibernate.validator.constraints.Range;
import com.wacai.tigon.format.annotation.EmptyToNull;
/**
* @author Donghuang <br>
* Nov 02, 2019 12:51:22
*/
@Getter
@Setter
public class CreateBatchFormAgent extends BaseCreateForm implements TaggableForm {
@NotNull
private Integer groupId;
@NotNull
private Agent.Type type;
private Boolean webrtc;
@Trim
@NotBlank
private String agents;
@Trim
@EmptyToNull
private String password;
@Range(min = 0, max = 240000)
private Integer wrapUpTime;
@NotNull
private Boolean updateExisted;
@NotNull
private Boolean trunkDialAllowed;
@NotNull
private Boolean internalDialAllowed;
private Integer[] queues;
private Integer[] tags;
}

View File

@ -2,12 +2,12 @@ package com.pudonghot.yo.cms.form.create;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.*;
import com.pudonghot.yo.model.domain.Agent;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotBlank;
import com.wacai.tigon.format.annotation.Trim;
import com.pudonghot.yo.cms.form.TaggableForm;
import com.pudonghot.yo.cms.form.BaseCreateForm;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import com.wacai.tigon.format.annotation.EmptyToNull;
@ -28,9 +28,15 @@ public class CreateFormAgent extends BaseCreateForm implements TaggableForm {
private String name;
@Trim
@NotBlank
@Length(min = 4, max = 32)
@Pattern(regexp = "^\\w+$")
private String account;
@Trim
@EmptyToNull
@Pattern(regexp = "^[1-9]+\\d*$")
private String agent;
@Trim
@EmptyToNull
private String password;
@Range(min = 0, max = 240000)
private Integer wrapUpTime;

View File

@ -1,5 +1,7 @@
package com.pudonghot.yo.cms.service;
import com.pudonghot.yo.cms.form.BatchForm;
import com.pudonghot.yo.cms.form.create.CreateBatchFormAgent;
import com.pudonghot.yo.cms.form.create.CreateFormAgent;
import com.pudonghot.yo.cms.form.update.UpdateFormAgent;
import com.pudonghot.yo.model.domain.Agent;
@ -13,6 +15,14 @@ public interface AgentService
CreateFormAgent,
UpdateFormAgent> {
void createBatch(CreateBatchFormAgent form);
void enableBatch(BatchForm form);
void disableBatch(BatchForm form);
void removeBatch(BatchForm form);
/**
* find agent of domain
* @param domain domain

View File

@ -1,21 +1,22 @@
package com.pudonghot.yo.cms.service.impl;
import lombok.val;
import java.util.Set;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import java.util.stream.Collectors;
import me.chyxion.tigon.mybatis.Search;
import com.wacai.tigon.model.ViewModel;
import org.springframework.util.Assert;
import com.wacai.tigon.json.JsonService;
import org.apache.commons.lang3.tuple.Pair;
import com.pudonghot.yo.model.domain.Agent;
import com.pudonghot.yo.mapper.AgentMapper;
import com.pudonghot.yo.cms.form.BatchForm;
import org.apache.commons.lang3.StringUtils;
import com.pudonghot.yo.cms.form.SessionForm;
import org.springframework.stereotype.Service;
import com.pudonghot.yo.mapper.AgentGroupMapper;
import com.pudonghot.yo.mapper.QueueAgentMapper;
import com.pudonghot.yo.model.domain.AgentGroup;
import com.pudonghot.yo.model.domain.QueueAgent;
import com.pudonghot.yo.cms.service.AgentService;
import org.apache.commons.lang3.RandomStringUtils;
@ -23,6 +24,7 @@ import com.pudonghot.yo.cms.service.SequenceService;
import com.pudonghot.yo.cms.form.create.CreateFormAgent;
import com.pudonghot.yo.cms.form.update.UpdateFormAgent;
import org.springframework.beans.factory.annotation.Value;
import com.pudonghot.yo.cms.form.create.CreateBatchFormAgent;
import org.springframework.beans.factory.annotation.Autowired;
/**
@ -48,17 +50,136 @@ public class AgentServiceImpl
private AgentGroupMapper agentGroupMapper;
@Autowired
private QueueAgentMapper queueAgentMapper;
@Autowired
private JsonService jsonService;
/**
* {@inheritDoc}
*/
@Override
public void createBatch(final CreateBatchFormAgent form) {
log.info("Batch create agents [{}].", form);
val groupId = form.getGroupId();
val agentGroup = agentGroupMapper.find(groupId);
Assert.state(agentGroup != null,
() -> "No agent group [" + groupId + "] found");
Assert.state(agentGroup != null,
() -> "Agent group [" + agentGroup.getName() + "] is not active");
val validateResult = batchCreateValidate(form);
// create agents
for (val createAgent : validateResult.getLeft()) {
create(createAgent);
}
// update agents
for (val updateAgent : validateResult.getRight()) {
update(updateAgent);
}
}
/**
* {@inheritDoc}
*/
@Override
public void enableBatch(final BatchForm form) {
log.info("Batch enable agents [{}].", form);
for (val id : form.getIds()) {
val agent = find(id);
Assert.state(agent != null, "No agent [" + id + "] found");
agent.setActive(true);
update(agent);
}
}
/**
* {@inheritDoc}
*/
@Override
public void disableBatch(final BatchForm form) {
log.info("Batch disable agents [{}].", form);
for (val id : form.getIds()) {
val agent = find(id);
Assert.state(agent != null, "No agent [" + id + "] found");
agent.setActive(false);
update(agent);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeBatch(final BatchForm form) {
log.info("Batch remove agents [{}].", form);
delete(new Search(form.getIds()));
}
private Pair<List<CreateFormAgent>, List<UpdateFormAgent>> batchCreateValidate(
final CreateBatchFormAgent form) {
val agents = form.getAgents().split("\\s*\n\\s*");
val agentsCreate = new ArrayList<CreateFormAgent>(agents.length);
val agentsUpdate = new ArrayList<UpdateFormAgent>(agents.length);
for (val line : agents) {
if (StringUtils.isNotBlank(line)) {
log.info("Create agent [{}].", line);
val agentData = line.split("[\\s,;]+");
Assert.state(agentData.length < 4,
() -> "Agent info [" + agentData + "] is not valid");
val name = agentData[0];
val account = agentData.length > 1 ?
agentData[1] : agentData[0];
Assert.state(account.matches("^\\w+$"),
() -> "Agent data [" + line + "] account [" + account + "] is not valid");
val agent = agentData.length == 3 ? agentData[2] : null;
if (agent != null) {
Assert.state(agent.matches("^[1-9]+\\d*$"),
() -> "Agent data [" + line + "] number [" + agent + "] is not valid");
}
val agentExisted = mapper.find(
new Search(Agent.ACCOUNT, account));
if (agentExisted != null) {
Assert.state(form.getUpdateExisted(),
() -> "Agent [" + account + "] existed");
val formUpdate = jsonService.convert(
form, UpdateFormAgent.class);
formUpdate.setId(agentExisted.getId());
formUpdate.setName(name);
agentsUpdate.add(formUpdate);
continue;
}
val formCreate = jsonService.convert(
form, CreateFormAgent.class);
formCreate.setName(name);
formCreate.setAccount(account);
formCreate.setAgent(account);
agentsCreate.add(formCreate);
}
}
return Pair.of(agentsCreate, agentsUpdate);
}
/**
* {@inheritDoc}
*/
@Override
public ViewModel<Agent> create(final CreateFormAgent form) {
final ViewModel<Agent> vm = super.create(form);
final Agent agent = vm.getData();
final Integer[] queues = form.getQueues();
val vm = super.create(form);
val agent = vm.getData();
val queues = form.getQueues();
if (queues != null) {
for (final Integer queue : queues) {
for (val queue : queues) {
insertQueueAgent(agent, queue, form);
}
}
@ -70,20 +191,19 @@ public class AgentServiceImpl
*/
@Override
public ViewModel<Agent> update(final UpdateFormAgent form) {
final ViewModel<Agent> vm = super.update(form);
final Agent agent = vm.getData();
val vm = super.update(form);
val agent = vm.getData();
final Integer agentId = agent.getId();
val agentId = agent.getId();
final Set<Integer> queuesExisted = queueAgentMapper.list(
val queuesExisted = queueAgentMapper.list(
new Search(QueueAgent.AGENT_ID, agentId)).stream()
.map(QueueAgent::getQueueId)
.collect(Collectors.toSet());
final Pair<Set<Integer>, Set<Integer>> queuesDiff =
diff(queuesExisted, form.getQueues());
val queuesDiff = diff(queuesExisted, form.getQueues());
for (final Integer queueId : queuesDiff.getLeft()) {
for (val queueId : queuesDiff.getLeft()) {
insertQueueAgent(agent, queueId, form);
}
@ -102,8 +222,8 @@ public class AgentServiceImpl
@Override
protected void validate(final CreateFormAgent form) {
super.validate(form);
final Integer groupId = form.getGroupId();
final AgentGroup agentGroup = agentGroupMapper.find(groupId);
val groupId = form.getGroupId();
val agentGroup = agentGroupMapper.find(groupId);
Assert.state(agentGroup != null, () -> "No agent group [" + groupId + "] found");
Assert.state(agentGroup.getActive(), () -> "Agent group [" + groupId + "] is inactive");
Assert.state(!mapper.exists(
@ -132,6 +252,7 @@ public class AgentServiceImpl
@Override
protected void beforeInsert(final Agent model) {
super.beforeInsert(model);
if (StringUtils.isBlank(model.getAgent())) {
model.setAgent(seqService.nextValStr(model.getTenantId(), seqName));
}

View File

@ -61,9 +61,14 @@ public class CallingListPreparedServiceImpl
log.info("Operator [{}] fetch campaign [{}] remote calling list, task key [{}].",
operator, campaign, taskKey);
while ((fetchedSize = doFetchRemote(operator, taskKey, campaign)) > 0) {
total += fetchedSize;
log.info("Task [{}] calling list [{}] fetched.", taskKey, fetchedSize);
try {
while ((fetchedSize = doFetchRemote(operator, taskKey, campaign)) > 0) {
total += fetchedSize;
log.info("Task [{}] calling list [{}] fetched.", taskKey, fetchedSize);
}
}
catch (final Exception e) {
log.error("Fetch campaign calling list error caused.", e);
}
log.info("Task [{}] calling list total [{}] fetched.", total);

View File

@ -13,7 +13,7 @@
<select id="listWithCdr" resultType="com.pudonghot.yo.mapper.response.CallSatisfactionMapperResp">
select t.digits, cdr.* from
<include refid="table" /> t
join br_call_detail_record cdr
join br_call_detail_record_all cdr
on t.conn_id = cdr.conn_id
<include refid="Tigon.search" />
</select>
@ -21,7 +21,7 @@
<select id="countWithCdr" resultType="int">
select count(t.id) from
<include refid="table" /> t
join br_call_detail_record cdr
join br_call_detail_record_all cdr
on t.conn_id = cdr.conn_id
<include refid="Tigon.searchForCount" />
</select>

View File

@ -21,7 +21,7 @@
<jackson.version>2.12.0</jackson.version>
<spring.version>5.2.12.RELEASE</spring.version>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<tigon-mybatis.version>0.0.7</tigon-mybatis.version>
<tigon-mybatis.version>0.0.8</tigon-mybatis.version>
</properties>
<modules>

View File

@ -8,6 +8,8 @@ import org.springframework.stereotype.Service;
import com.pudonghot.yo.model.domain.PhoneAlias;
import com.pudonghot.yo.mapper.PhoneAliasMapper;
import org.springframework.core.annotation.Order;
import com.pudonghot.yo.model.domain.PhoneBlacklist;
import com.pudonghot.yo.mapper.PhoneBlacklistMapper;
import com.pudonghot.yo.fsagent.service.dialplan.Call;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.yo.fsagent.service.dialplan.DialplanConfig;
@ -23,6 +25,8 @@ public class DialplanService28AgentToTrunkPrefix extends BaseDialplanService {
@Autowired
private PhoneAliasMapper phoneAliasMapper;
@Autowired
private PhoneBlacklistMapper phoneBlacklistMapper;
/**
* {@inheritDoc}
@ -52,13 +56,22 @@ public class DialplanService28AgentToTrunkPrefix extends BaseDialplanService {
val calledNumberObj = getCalledNumber(tenantId, trunkInfo.getRight());
if (!calledNumberObj.isAlias()) {
if (!callerAgent.getTrunkDialAllowed()) {
log.debug("Caller [{}] is not allowed to dial trunk.", callerAgent);
log.info("Caller [{}] is not allowed to dial trunk.", callerAgent);
return null;
}
}
// number without trunk prefix
val calledNumber = calledNumberObj.getNumber();
if (phoneBlacklistMapper.exists(
new Search(PhoneBlacklist.TENANT_ID, tenantId)
.eq(PhoneBlacklist.PHONE, calledNumber)
.eq(PhoneBlacklist.ACTIVE, true))) {
log.info("Called number [{}] is in blacklist", calledNumber);
return null;
}
model.put("calledNumber", calledNumber);
model.put("targetNumber", trunkService.calledNumber(trunk, calledNumber));
recFile(model, connId, callerAgent.getAccount(), md5(calledNumber));

View File

@ -8,6 +8,7 @@ export default BaseRoute.extend({
const store = this.get('store');
const queryParams = this.get('activeListQueryParams');
return RSVP.hash({
batch: false,
wrapUpTime: 24000,
active: true,
trunkDialAllowed: false,

View File

@ -1,6 +1,8 @@
import BaseListRoute from '../base-list';
import batchActions from '../batch-actions';
export default BaseListRoute.extend({
perm: 'PERM_VIEW_AGENT_LIST',
breadcrumbs: [{text: '坐席列表'}],
actions: batchActions
});

View File

@ -0,0 +1,38 @@
export default {
enableBatch() {
const me = this;
const ids = me.get('controller.model.data').filter(i => i.checked).map(i => i.id);
ids.length && me.get('dialog').confirm('确认启用选中项吗?', () => {
me.get('service').ajaxPost('enable-batch', {
ids: ids.join(',')
}).then(() => {
me.refresh();
me.get('message').alert('启用成功');
});
});
},
disableBatch() {
const me = this;
const ids = me.get('controller.model.data').filter(i => i.checked).map(i => i.id);
ids.length && me.get('dialog').confirm('确认禁用选中项吗?', () => {
me.get('service').ajaxPost('disable-batch', {
ids: ids.join(',')
}).then(() => {
me.refresh();
me.get('message').alert('禁用成功');
});
});
},
removeBatch() {
const me = this;
const ids = me.get('controller.model.data').filter(i => i.checked).map(i => i.id);
ids.length && me.get('dialog').confirm('确认删除选中项吗?', () => {
me.get('service').ajaxPost('remove-batch', {
ids: ids.join(',')
}).then(() => {
me.refresh();
me.get('message').alert('删除成功');
});
});
}
}

View File

@ -1,4 +1,5 @@
import Service from '../service';
import { isBlank } from '@ember/utils';
export default Service.extend({
modelName: 'Agent',
@ -16,8 +17,32 @@ export default Service.extend({
account: {
presence: true,
length: {
minimum: 1,
minimum: 4,
maximum: 36
},
format: {
pattern: /^\w+$/,
message: '仅允许字母数字'
}
},
agent: {
length: function(value) {
if (isBlank(value)) {
return false;
}
return {
minimum: 4,
maximum: 36
};
},
format: function(value) {
if (isBlank(value)) {
return false;
}
return {
pattern: /^[1-9]+\d*$/,
message: '仅允许数字'
};
}
},
wrapUpTime: {
@ -46,5 +71,10 @@ export default Service.extend({
greaterThan: -1
}
}
}
},
createBatchConstraints: {
agents: {
presence: true
}
},
});

View File

@ -1,5 +1,6 @@
{{#form-content}}
<hr />
{{form-input-checkbox name='batch' label='单条/批量'}}
{{form-input-select
name='groupId'
label='坐席组'
@ -8,8 +9,16 @@
text-field='name'
enabled-field='active'
}}
{{form-input name='name' label='名称'}}
{{form-input name='account' label='账户'}}
{{#if model.batch}}
{{form-input type='textarea' name='agents' label='坐席' placeholder='坐席数据[姓名 账户 分机号],支持多行,其中账户必选,姓名分机号可选,如:东皇 donghuang 700002'}}
{{form-input-checkbox name='updateExisted' label='更新已存在'}}
{{else}}
{{form-input name='name' label='名称'}}
{{form-input name='account' label='账户'}}
{{form-input name='agent' label='分机号' placeholder='数字分机号,空则自动生成'}}
{{/if}}
{{form-input-select
readonly=true
name='type'
@ -25,7 +34,7 @@
{{form-input type='number' min=0 max=24000 step=1 name='wrapUpTime' label='Wrap up time'}}
{{form-input-checkbox name='trunkDialAllowed' label='允许手拨外呼'}}
{{form-input-checkbox name='internalDialAllowed' label='允许拨打分机'}}
{{form-input-select
{{!form-input-select
multiple=true
name='queues'
label='队列'
@ -46,6 +55,7 @@
{{form-input name='note' label='备注'}}
<hr />
{{form-footer-buttons type='create'}}
{{form-footer-buttons type=(if model.batch 'createBatch' 'create')
post-url=(if model.batch 'agent/create-batch') success-message='创建成功'}}
{{/form-content}}
{{outlet}}

View File

@ -1,5 +1,18 @@
<div class="widget-box transparent" style="padding-top: 2px; border: 1px solid #ddd;">
{{#grid-header}}
{{#grid-header button=true as |sec|}}
{{#if (and sec.button model.checked)}}
{{#a-btn click=(route-action 'enableBatch') title='启用'}}
<i class="ace-icon fa fa-check bigger-120 green"></i>
{{/a-btn}}
{{#a-btn click=(route-action 'disableBatch') title='禁用'}}
<i class="ace-icon fa fa-ban bigger-120 orange2"></i>
{{/a-btn}}
{{#a-btn click=(route-action 'removeBatch') title='删除'}}
<i class="ace-icon fa fa-trash-o bigger-120 red"></i>
{{/a-btn}}
{{/if}}
{{#if sec.dropdown}}
{{#has-perm 'PERM_VIEW_AGENT_CREATE'}}
<li>
{{#link-to 'agent.create'}}
@ -8,6 +21,7 @@
{{/link-to}}
</li>
{{/has-perm}}
{{/if}}
{{/grid-header}}
<div class="widget-body">
@ -16,6 +30,12 @@
<table class="table table-striped table-bordered table-hover dataTable">
<thead class="thin-border-bottom">
<tr>
{{!#if agentGroupFiltered}}
<th style="width: 32px;">
{{grid-all-checkbox list=model.data
any-checked=(mut model.checked)}}
</th>
{{!/if}}
<th>
名称
</th>
@ -111,6 +131,11 @@
<tbody>
{{#each model.data as |it|}}
<tr>
{{!#if agentGroupFiltered}}
<td>
{{grid-row-checkbox checked=(mut it.checked)}}
</td>
{{!/if}}
<td>
{{it.name}}
</td>