add dial group/queue
This commit is contained in:
parent
2e4bd458f2
commit
24d1881fa0
Binary file not shown.
@ -0,0 +1,35 @@
|
||||
package com.pudonghot.yo.cms.controller;
|
||||
|
||||
import com.wacai.tigon.form.FormList;
|
||||
import com.pudonghot.yo.model.domain.Agent;
|
||||
import com.wacai.tigon.web.annotation.ListApi;
|
||||
import com.wacai.tigon.web.annotation.FilterCol;
|
||||
import com.pudonghot.yo.model.domain.AgentStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import com.pudonghot.yo.cms.annotation.TenantResource;
|
||||
import com.wacai.tigon.web.controller.BaseQueryController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* @author Donghuang
|
||||
* @date Jul 09, 2020 16:44:23
|
||||
*/
|
||||
@Controller
|
||||
@ListApi(searchCols = {
|
||||
Agent.NAME,
|
||||
Agent.ACCOUNT,
|
||||
Agent.AGENT,
|
||||
Agent.NOTE
|
||||
},
|
||||
filterCols = {
|
||||
@FilterCol(param = AgentStatus.STATUS, type = AgentStatus.Status.class),
|
||||
@FilterCol(param = AgentStatus.STATE, type = AgentStatus.State.class),
|
||||
@FilterCol(param = AgentStatus.REGISTERED, type = boolean.class),
|
||||
})
|
||||
@TenantResource
|
||||
@RequestMapping("/agent-status")
|
||||
public class AgentStatusController
|
||||
extends BaseQueryController<Integer,
|
||||
AgentStatus,
|
||||
FormList> {
|
||||
}
|
@ -2,10 +2,7 @@ package com.pudonghot.yo.cms.form.create;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.*;
|
||||
import com.wacai.tigon.format.annotation.Trim;
|
||||
import com.pudonghot.yo.cms.form.BaseCreateForm;
|
||||
|
||||
@ -20,10 +17,14 @@ public class CreateFormSequence extends BaseCreateForm {
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^\\w+$", message = "序列名必须是字母数字下划线构成")
|
||||
private String name;
|
||||
@Min(2)
|
||||
@Max(16)
|
||||
private int length;
|
||||
@Min(1)
|
||||
@NotNull
|
||||
private Long initVal;
|
||||
@Min(1)
|
||||
@Max(128)
|
||||
@NotNull
|
||||
private Long step;
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.pudonghot.yo.cms.service;
|
||||
|
||||
import com.pudonghot.yo.model.domain.AgentStatus;
|
||||
import com.wacai.tigon.service.BaseQueryService;
|
||||
|
||||
/**
|
||||
* @author Donghuang
|
||||
* @date Jul 09, 2020 16:53:30
|
||||
*/
|
||||
public interface AgentStatusService
|
||||
extends BaseQueryService<Integer, AgentStatus> {
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package com.pudonghot.yo.cms.service;
|
||||
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
||||
import com.pudonghot.yo.cms.form.update.UpdateFormSequence;
|
||||
import com.pudonghot.yo.model.domain.Sequence;
|
||||
import com.wacai.tigon.service.BaseCrudByFormService;
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
||||
import com.pudonghot.yo.cms.form.update.UpdateFormSequence;
|
||||
|
||||
/**
|
||||
* @author Donghuang <br>
|
||||
@ -22,4 +22,13 @@ public interface SequenceService
|
||||
* @return next seq val
|
||||
*/
|
||||
Long nextVal(Integer tenantId, String name);
|
||||
|
||||
/**
|
||||
* next seq val string, padding 0 to length
|
||||
*
|
||||
* @param tenantId tenant id
|
||||
* @param name seq name
|
||||
* @return next val
|
||||
*/
|
||||
String nextValStr(Integer tenantId, String name);
|
||||
}
|
||||
|
@ -99,8 +99,7 @@ public class AgentGroupServiceImpl
|
||||
super.beforeInsert(model);
|
||||
if (StringUtils.isBlank(model.getIdentifier())) {
|
||||
model.setIdentifier(identifierPrefix +
|
||||
StringUtils.leftPad(String.valueOf(
|
||||
seqService.nextVal(model.getTenantId(), seqName)), 6, '0'));
|
||||
seqService.nextValStr(model.getTenantId(), seqName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ public class AgentServiceImpl
|
||||
protected void beforeInsert(final Agent model) {
|
||||
super.beforeInsert(model);
|
||||
if (StringUtils.isBlank(model.getAgent())) {
|
||||
model.setAgent(String.valueOf(seqService.nextVal(model.getTenantId(), seqName)));
|
||||
model.setAgent(seqService.nextValStr(model.getTenantId(), seqName));
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(model.getPassword())) {
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.pudonghot.yo.cms.service.impl;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.pudonghot.yo.mapper.AgentStatusMapper;
|
||||
import com.pudonghot.yo.model.domain.AgentStatus;
|
||||
import com.pudonghot.yo.cms.service.AgentStatusService;
|
||||
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
|
||||
|
||||
/**
|
||||
* @author Donghuang <br>
|
||||
* Dec 24, 2019 16:49:51
|
||||
*/
|
||||
@Service
|
||||
public class AgentStatusServiceImpl
|
||||
extends BaseQueryServiceSupport<Integer,
|
||||
AgentStatus,
|
||||
AgentStatusMapper>
|
||||
implements AgentStatusService {
|
||||
}
|
@ -50,9 +50,7 @@ public class QueueServiceImpl
|
||||
final Tenant tenant = tenantMapper.find(tenantId);
|
||||
Assert.state(tenant != null,
|
||||
() -> "No tenant [" + tenantId + "] found");
|
||||
model.setIdentifier(identifierPrefix +
|
||||
StringUtils.leftPad(String.valueOf(
|
||||
seqService.nextVal(tenantId, seqName)), 6, '0'));
|
||||
model.setIdentifier(identifierPrefix + seqService.nextValStr(tenantId, seqName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package com.pudonghot.yo.cms.service.impl;
|
||||
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.wacai.tigon.mybatis.Search;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.pudonghot.yo.mapper.SequenceMapper;
|
||||
import com.pudonghot.yo.model.domain.Sequence;
|
||||
import com.pudonghot.yo.cms.service.SequenceService;
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
||||
import com.pudonghot.yo.cms.form.update.UpdateFormSequence;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.wacai.tigon.service.support.BaseCrudByFormServiceSupport;
|
||||
@ -42,17 +43,18 @@ public class SequenceServiceImpl
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@Transactional(rollbackFor = RuntimeException.class)
|
||||
public Long nextVal(final Integer tenantId, final String name) {
|
||||
final Sequence seq = mapper.findForUpdate(
|
||||
new Search(Sequence.NAME, name)
|
||||
.eq(Sequence.TENANT_ID, tenantId)
|
||||
.eq(Sequence.ACTIVE, true));
|
||||
Assert.state(seq != null, () -> "No valid sequence [" + name + "] found");
|
||||
final Long nextVal = seq.getCurrentVal() + seq.getStep();
|
||||
seq.setCurrentVal(nextVal);
|
||||
mapper.update(seq);
|
||||
return nextVal;
|
||||
return nextVal(tenantId, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = RuntimeException.class)
|
||||
public String nextValStr(final Integer tenantId, final String name) {
|
||||
return nextVal(tenantId, name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,8 +63,28 @@ public class SequenceServiceImpl
|
||||
@Override
|
||||
protected void beforeInsert(final Sequence model) {
|
||||
super.beforeInsert(model);
|
||||
Assert.state(String.valueOf(model.getInitVal()).length() <= model.getLength(),
|
||||
"Sequence init value exceeds length");
|
||||
if (model.getCurrentVal() == null) {
|
||||
model.setCurrentVal(model.getInitVal());
|
||||
}
|
||||
}
|
||||
|
||||
<T> T nextVal(final Integer tenantId, final String name, final boolean str) {
|
||||
final Sequence seq = mapper.findForUpdate(
|
||||
new Search(Sequence.TENANT_ID, tenantId)
|
||||
.eq(Sequence.NAME, name)
|
||||
.eq(Sequence.ACTIVE, true));
|
||||
Assert.state(seq != null, () ->
|
||||
"No valid sequence [" + name + "] found");
|
||||
final Long nextVal = seq.getCurrentVal() + seq.getStep();
|
||||
final String strVal = String.valueOf(nextVal);
|
||||
Assert.state(strVal.length() <= seq.getLength(),
|
||||
"Sequence next value exceeds length");
|
||||
seq.setCurrentVal(nextVal);
|
||||
mapper.update(seq);
|
||||
return str ? (T) StringUtils.leftPad(strVal, seq.getLength(), '0')
|
||||
: (T) nextVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,29 +1,27 @@
|
||||
package com.pudonghot.yo.cms.service.impl;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.pudonghot.yo.cms.form.SessionForm;
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormTrunk;
|
||||
import com.pudonghot.yo.mapper.*;
|
||||
import com.pudonghot.yo.model.domain.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import com.wacai.tigon.mybatis.Search;
|
||||
import com.pudonghot.yo.model.domain.*;
|
||||
import com.wacai.tigon.model.ViewModel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.pudonghot.yo.cms.form.SessionForm;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.pudonghot.yo.cellphone.CellphoneInfo;
|
||||
import com.pudonghot.yo.common.util.MobileUtils;
|
||||
import com.pudonghot.yo.cellphone.CellphoneService;
|
||||
import com.pudonghot.yo.cms.service.TrunkService;
|
||||
import com.pudonghot.yo.cellphone.CellphoneService;
|
||||
import com.pudonghot.yo.cms.service.AreaCodeService;
|
||||
import com.pudonghot.yo.cms.service.SequenceService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import com.pudonghot.yo.cms.form.create.CreateFormTrunk;
|
||||
import com.pudonghot.yo.cms.form.update.UpdateFormTrunk;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
@ -142,8 +140,7 @@ public class TrunkServiceImpl
|
||||
protected void beforeInsert(final Trunk model) {
|
||||
super.beforeInsert(model);
|
||||
if (StringUtils.isBlank(model.getPrefix())) {
|
||||
model.setPrefix(String.valueOf(
|
||||
seqService.nextVal(model.getTenantId(), seqName)));
|
||||
model.setPrefix(seqService.nextValStr(model.getTenantId(), seqName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,8 @@ spring.redis.port=6379
|
||||
|
||||
# CAS
|
||||
tigon.shiro.cas.server.addr=http://118.24.251.131:8080/cas
|
||||
tigon.shiro.cas.client.addr=http://118.24.251.131:8080/cms/
|
||||
tigon.shiro.cas.client.addr=http://118.24.251.131:8080/cms
|
||||
tigon.shiro.cas.login-success-url=/cms/
|
||||
tigon.shiro.filter-chain=${site.context-path}/auth/login=anon \
|
||||
/=anon \
|
||||
/state-machine/**=anon \
|
||||
|
@ -1,8 +1,8 @@
|
||||
package com.pudonghot.yo.fsagent.controller;
|
||||
|
||||
import com.pudonghot.yo.model.domain.Tenant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.wacai.tigon.mybatis.Search;
|
||||
import com.pudonghot.yo.model.domain.Tenant;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -43,4 +43,12 @@ public class BaseDialplanController extends BaseXmlController {
|
||||
thisDn + "-" +
|
||||
otherDn + recordingFileExt;
|
||||
}
|
||||
|
||||
/**
|
||||
* return empty dial plan
|
||||
* @return empty dial plan
|
||||
*/
|
||||
protected FreeMarkerView empty() {
|
||||
return empty(SECTION_DIALPLAN);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import java.util.List;
|
||||
import javax.sql.DataSource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.wacai.tigon.mybatis.Search;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.pudonghot.yo.model.domain.Gateway;
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.pudonghot.yo.fsagent.util.OdbcUtils;
|
||||
@ -55,8 +54,7 @@ public class ConfigController extends BaseXmlController {
|
||||
final FreeMarkerView view = view("config/sofia.conf.xml");
|
||||
final List<Gateway> gateways = gatewayService.list(
|
||||
new Search(Gateway.ACTIVE, true));
|
||||
gateways.forEach(g -> g.setName(
|
||||
"GW" + StringUtils.leftPad(String.valueOf(g.getId()), 6, '0')));
|
||||
gateways.forEach(g -> g.setName(gatewayService.genGatewayName(g)));
|
||||
attr(view, "gateways", gateways);
|
||||
|
||||
return view;
|
||||
|
@ -2,14 +2,20 @@ package com.pudonghot.yo.fsagent.controller;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.pudonghot.yo.fsagent.service.*;
|
||||
import com.pudonghot.yo.model.domain.Queue;
|
||||
import com.pudonghot.yo.model.domain.Trunk;
|
||||
import com.pudonghot.yo.model.domain.Agent;
|
||||
import com.wacai.tigon.sequence.IdSequence;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import com.pudonghot.yo.model.domain.AgentGroup;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||
import com.pudonghot.yo.fsagent.service.LocalApiService;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import static com.pudonghot.yo.fsagent.util.CallStrUtils.DialTarget;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
|
||||
|
||||
/**
|
||||
@ -22,9 +28,17 @@ public class DialplanInternalController extends BaseDialplanController {
|
||||
@Autowired
|
||||
private AgentService agentService;
|
||||
@Autowired
|
||||
private AgentGroupService agentGroupService;
|
||||
@Autowired
|
||||
private QueueService queueService;
|
||||
@Autowired
|
||||
private LocalApiService localApiService;
|
||||
@Autowired
|
||||
private IdSequence idSeq;
|
||||
@Autowired
|
||||
private TrunkService trunkService;
|
||||
@Autowired
|
||||
private GatewayService gatewayService;
|
||||
|
||||
@RequestMapping(params = {
|
||||
"variable_domain_name",
|
||||
@ -41,20 +55,102 @@ public class DialplanInternalController extends BaseDialplanController {
|
||||
@RequestParam
|
||||
final Map<String, String> params) {
|
||||
|
||||
log.info("XML dialplan of domain [{}].", domain);
|
||||
log.info("XML dialplan of domain [{}] [{}] -> [{}].", domain, user, calledNumber);
|
||||
log.debug("XML dialplan params [{}].", params);
|
||||
|
||||
final Agent callerAgent =
|
||||
agentService.findByDomainAndAgent(domain, user);
|
||||
|
||||
final DialTarget dialTarget =
|
||||
CallStrUtils.parseDialTarget(calledNumber);
|
||||
|
||||
if (dialTarget != null) {
|
||||
final Integer dialTargetId = dialTarget.getId();
|
||||
final DialTarget.Type dialTargetType = dialTarget.getType();
|
||||
|
||||
if (dialTargetType == DialTarget.Type.AGENT) {
|
||||
final Agent calledAgent = agentService.find(dialTargetId);
|
||||
if (calledAgent != null) {
|
||||
return agentCallAgent(domain, callerAgent, calledAgent);
|
||||
}
|
||||
|
||||
log.warn("No agent found of dial target [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
else if (dialTargetType == DialTarget.Type.AGENT_GROUP) {
|
||||
final AgentGroup agentGroup = agentGroupService.find(dialTargetId);
|
||||
if (agentGroup != null) {
|
||||
final Agent calledAgent =
|
||||
agentService.lockIdleOfGroup(dialTargetId);
|
||||
if (calledAgent != null) {
|
||||
return agentCallAgent(domain, callerAgent, calledAgent);
|
||||
}
|
||||
|
||||
log.warn("No idle agent found of group [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
|
||||
log.warn("No agent group found of dial target [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
else if (dialTargetType == DialTarget.Type.QUEUE) {
|
||||
final Queue queue = queueService.find(dialTargetId);
|
||||
if (queue != null) {
|
||||
final Agent calledAgent =
|
||||
agentService.lockIdleOfQueue(dialTargetId);
|
||||
if (calledAgent != null) {
|
||||
return agentCallAgent(domain, callerAgent, calledAgent);
|
||||
}
|
||||
|
||||
log.warn("No idle agent found of queue [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
|
||||
log.warn("No queue found of dial target [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
|
||||
log.error("Never here!!!");
|
||||
}
|
||||
|
||||
final Pair<Trunk, String> trunkInfo = trunkService.parseDialTarget(
|
||||
callerAgent.getTenantId(), calledNumber);
|
||||
if (trunkInfo != null) {
|
||||
log.info("Trunk outbound call found.");
|
||||
final Trunk trunk = trunkInfo.getLeft();
|
||||
final String targetNumber = trunkInfo.getRight();
|
||||
|
||||
final FreeMarkerView view = view(domain, "trunk-outbound.xml");
|
||||
final String connId = idSeq.get();
|
||||
attr(view, "connId", connId);
|
||||
attr(view, "callerAgent", callerAgent);
|
||||
attr(view, "gatewayName",
|
||||
gatewayService.genGatewayName(trunk.getGatewayId()));
|
||||
attr(view, "targetNumber", targetNumber);
|
||||
attr(view, "recordingLoc", recordingFile(connId,
|
||||
callerAgent.getAccount(),
|
||||
DigestUtils.md5DigestAsHex(targetNumber.getBytes())));
|
||||
attr(view, "postRecUrl", localApiService.getPostRecUrl());
|
||||
return view;
|
||||
}
|
||||
|
||||
final Agent calledAgent =
|
||||
agentService.findOfCalled(domain, calledNumber);
|
||||
|
||||
if (calledAgent != null) {
|
||||
log.info("Local extension [{}] found.", calledAgent);
|
||||
return agentCallAgent(domain, callerAgent, calledAgent);
|
||||
}
|
||||
|
||||
log.warn("No correct dialplan found for called number [{}].", calledNumber);
|
||||
return empty();
|
||||
}
|
||||
|
||||
private FreeMarkerView agentCallAgent(final String domain, Agent callerAgent, Agent calledAgent) {
|
||||
final FreeMarkerView view = view(domain, "local-extension.xml");
|
||||
final String connId = idSeq.get();
|
||||
attr(view, "connId", connId);
|
||||
attr(view, "destinationUser", calledAgent.getAgent());
|
||||
final Agent callerAgent =
|
||||
agentService.findByDomainAndAgent(domain, user);
|
||||
attr(view, "callerAgent", callerAgent);
|
||||
attr(view, "calledAgent", calledAgent);
|
||||
attr(view, "recordingLoc", recordingFile(connId,
|
||||
@ -63,9 +159,4 @@ public class DialplanInternalController extends BaseDialplanController {
|
||||
attr(view, "postRecUrl", localApiService.getPostRecUrl());
|
||||
return view;
|
||||
}
|
||||
|
||||
// TODO Dial Queue
|
||||
|
||||
return view(domain, "dialplan.xml");
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.freeswitch.esl.client.transport.event.Event;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@ -39,13 +39,8 @@ public class ChannelAnswer {
|
||||
public void onChannelAnswer(final Event event) {
|
||||
log.debug("On channel answer event [{}] [{}].", event, event.getHeaders());
|
||||
|
||||
// sofia/internal/700001@d1.wacai.info
|
||||
// sofia/external/013764268709
|
||||
final String channelName = event.getHeader("variable_channel_name");
|
||||
log.info("On channel [{}] answer event.", channelName);
|
||||
|
||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
||||
ChannelNameUtils.parse(channelName);
|
||||
final CallStrUtils.ChannelInfo channelInfo =
|
||||
CallStrUtils.getChannelInfo(event);
|
||||
|
||||
if (channelInfo.isAgent()) {
|
||||
final Agent agent = findAgent(channelInfo);
|
||||
@ -81,7 +76,7 @@ public class ChannelAnswer {
|
||||
}
|
||||
}
|
||||
|
||||
private Agent findAgent(final ChannelNameUtils.ChannelInfo channelInfo) {
|
||||
private Agent findAgent(final CallStrUtils.ChannelInfo channelInfo) {
|
||||
return agentService.findByDomainAndAgent(
|
||||
channelInfo.getDomain(), channelInfo.getNumber());
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.pudonghot.yo.fsagent.listener;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.freeswitch.esl.client.transport.event.Event;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@ -29,7 +29,7 @@ public class ChannelCreate {
|
||||
public void onChannelCreate(final Event event) {
|
||||
log.debug("On channel create event [{}] [{}].", event, event.getHeaders());
|
||||
|
||||
final ChannelNameUtils.ChannelInfo channelInfo = ChannelNameUtils.get(event);
|
||||
final CallStrUtils.ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event);
|
||||
|
||||
if (channelInfo.isAgent()) {
|
||||
agentStatusService.inACall(channelInfo.getDomain(),
|
||||
|
@ -9,14 +9,14 @@ import org.springframework.stereotype.Component;
|
||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.freeswitch.esl.client.transport.event.Event;
|
||||
import com.pudonghot.yo.service.CommonAgentStatusService;
|
||||
import com.pudonghot.yo.service.CommonAgentEventQueueService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils.ChannelInfo;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils.ChannelInfo;
|
||||
import static com.pudonghot.yo.model.agentevent.EventType.AgentOther_PhoneRelease;
|
||||
import static com.pudonghot.yo.model.agentevent.EventType.AgentEvent_Customer_Release;
|
||||
|
||||
@ -47,7 +47,7 @@ public class ChannelDestroy {
|
||||
final String presenceId = event.getHeader("variable_presence_id");
|
||||
final String callUuid = event.getCallUuid();
|
||||
log.info("On channel [{}][{}] destroy event.", presenceId, callUuid);
|
||||
final ChannelInfo channelInfo = ChannelNameUtils.get(event);
|
||||
final ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event);
|
||||
|
||||
if (channelInfo.isAgent()) {
|
||||
agentStatusService.acw(channelInfo.getDomain(),
|
||||
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.freeswitch.esl.client.transport.event.Event;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@ -41,8 +41,8 @@ public class ChannelHangupComplete {
|
||||
|
||||
if (!event.isHangupNormalClearing()) {
|
||||
|
||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
||||
ChannelNameUtils.get(event);
|
||||
final CallStrUtils.ChannelInfo channelInfo =
|
||||
CallStrUtils.getChannelInfo(event);
|
||||
|
||||
if (channelInfo.isAgent()) {
|
||||
final Agent agent = agentService.findByDomainAndAgent(
|
||||
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
import org.freeswitch.esl.client.transport.event.Event;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@ -42,8 +42,8 @@ public class ChannelProgress {
|
||||
log.info("On channel [{}] answer event.", channelName);
|
||||
|
||||
if (EslEventUtils.isCalled(event)) {
|
||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
||||
ChannelNameUtils.get(event);
|
||||
final CallStrUtils.ChannelInfo channelInfo =
|
||||
CallStrUtils.getChannelInfo(event);
|
||||
if (channelInfo.isAgent()) {
|
||||
final Agent agent = agentService.findByDomainAndAgent(
|
||||
channelInfo.getDomain(),
|
||||
|
@ -8,4 +8,20 @@ import com.wacai.tigon.service.BaseQueryService;
|
||||
* Dec 04, 2019 19:04:12
|
||||
*/
|
||||
public interface GatewayService extends BaseQueryService<Integer, Gateway> {
|
||||
|
||||
/**
|
||||
* generate gateway name
|
||||
*
|
||||
* @param gateway gateway
|
||||
* @return gateway name
|
||||
*/
|
||||
String genGatewayName(Gateway gateway);
|
||||
|
||||
/**
|
||||
* generate gateway name
|
||||
*
|
||||
* @param gatewayId gatewayId
|
||||
* @return gateway name
|
||||
*/
|
||||
String genGatewayName(Integer gatewayId);
|
||||
}
|
||||
|
@ -27,4 +27,13 @@ public interface TrunkService extends BaseQueryService<Integer, Trunk> {
|
||||
* @return called number
|
||||
*/
|
||||
String calledNumber(Trunk trunk, String number);
|
||||
|
||||
/**
|
||||
* parse dial target
|
||||
*
|
||||
* @param tenantId tenant id
|
||||
* @param calledNumber called number
|
||||
* @return trunk and called number
|
||||
*/
|
||||
Pair<Trunk, String> parseDialTarget(Integer tenantId, String calledNumber);
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package com.pudonghot.yo.fsagent.service.impl;
|
||||
|
||||
import com.pudonghot.yo.fsagent.service.GatewayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.pudonghot.yo.mapper.GatewayMapper;
|
||||
import com.pudonghot.yo.model.domain.Gateway;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.pudonghot.yo.fsagent.service.GatewayService;
|
||||
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
|
||||
|
||||
/**
|
||||
@ -18,4 +19,20 @@ public class GatewayerviceImpl
|
||||
Gateway,
|
||||
GatewayMapper>
|
||||
implements GatewayService {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String genGatewayName(final Gateway gateway) {
|
||||
return genGatewayName(gateway.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String genGatewayName(Integer gatewayId) {
|
||||
return "GW" + StringUtils.leftPad(String.valueOf(gatewayId), 6, '0');
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,19 @@ package com.pudonghot.yo.fsagent.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.pudonghot.yo.fsagent.service.GatewayService;
|
||||
import com.pudonghot.yo.fsagent.service.TrunkService;
|
||||
import com.pudonghot.yo.mapper.GatewayMapper;
|
||||
import com.pudonghot.yo.mapper.SequenceMapper;
|
||||
import com.pudonghot.yo.mapper.TrunkMapper;
|
||||
import com.pudonghot.yo.mapper.TrunkStrategyMapper;
|
||||
import com.pudonghot.yo.model.domain.Gateway;
|
||||
import com.pudonghot.yo.model.domain.Sequence;
|
||||
import com.pudonghot.yo.model.domain.Trunk;
|
||||
import com.pudonghot.yo.model.domain.TrunkStrategy;
|
||||
import com.wacai.tigon.mybatis.Search;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.util.Assert;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -32,12 +37,18 @@ public class TrunkServiceImpl
|
||||
TrunkMapper>
|
||||
implements TrunkService {
|
||||
|
||||
@Value("${yo.fsagent.trunk-prefix.seq-name:TRUNK}")
|
||||
private String seqName;
|
||||
@Autowired
|
||||
private SequenceMapper sequenceMapper;
|
||||
@Autowired
|
||||
private TrunkStrategyMapper trunkStrategyMapper;
|
||||
@Autowired
|
||||
private GatewayMapper gatewayMapper;
|
||||
@Autowired
|
||||
private CellphoneService cellphoneService;
|
||||
@Autowired
|
||||
private GatewayService gatewayService;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
@ -70,8 +81,8 @@ public class TrunkServiceImpl
|
||||
Assert.state(gateway.getActive(),
|
||||
() -> "Trunk [" + trunk.getPrefix() + "] gateway is not active");
|
||||
|
||||
return Pair.of(trunk, "sofia/gateway/GW" +
|
||||
StringUtils.leftPad(String.valueOf(gateway.getId()), 6, '0') +
|
||||
return Pair.of(trunk, "sofia/gateway/" +
|
||||
gatewayService.genGatewayName(gateway) +
|
||||
"/" + calledNumber(trunk, number));
|
||||
}
|
||||
|
||||
@ -100,6 +111,30 @@ public class TrunkServiceImpl
|
||||
return addPrefix(trunk, calledNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Pair<Trunk, String> parseDialTarget(final Integer tenantId, final String calledNumber) {
|
||||
final Sequence trunkSeq = sequenceMapper.find(
|
||||
new Search(Sequence.TENANT_ID, tenantId)
|
||||
.eq(Sequence.NAME, seqName)
|
||||
.eq(Sequence.ACTIVE, true));
|
||||
final int prefixLength = trunkSeq.getLength();
|
||||
if (calledNumber.length() > prefixLength) {
|
||||
final String prefix = calledNumber.substring(0, prefixLength);
|
||||
final Trunk trunk = mapper.find(
|
||||
new Search(Trunk.TENANT_ID, tenantId)
|
||||
.eq(Trunk.PREFIX, prefix)
|
||||
.eq(Trunk.ACTIVE, true));
|
||||
if (trunk != null) {
|
||||
return Pair.of(trunk, calledNumber.substring(prefixLength));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String addPrefix(final Trunk trunk, String number) {
|
||||
final String gatewayPrefix = trunk.getGatewayPrefix();
|
||||
return StringUtils.isNotBlank(gatewayPrefix) ? gatewayPrefix + number : number;
|
||||
|
@ -12,20 +12,23 @@ import org.freeswitch.esl.client.transport.event.Event;
|
||||
* @author Donghuang
|
||||
* @date Jul 08, 2020 11:42:21
|
||||
*/
|
||||
public class ChannelNameUtils {
|
||||
public class CallStrUtils {
|
||||
public static final Pattern PATTERN_USER_AND_DOMAIN =
|
||||
Pattern.compile("^sofia/internal/([^@]+)@(.+)$");
|
||||
public static final Pattern PATTERN_EXTERNAL_NUMBER =
|
||||
Pattern.compile("^sofia/external/(\\w+)$");
|
||||
|
||||
public static final Pattern PATTERN_DIAL_TARGET =
|
||||
Pattern.compile("^(\\d+)\\.(A|Q|(?:AG))$");
|
||||
|
||||
/**
|
||||
* get channel info
|
||||
*
|
||||
* @param event event
|
||||
* @return channel info
|
||||
*/
|
||||
public static ChannelInfo get(final Event event) {
|
||||
return parse(event.getChannelName());
|
||||
public static ChannelInfo getChannelInfo(final Event event) {
|
||||
return parseChannelName(event.getChannelName());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,7 +37,7 @@ public class ChannelNameUtils {
|
||||
* @param channelName channel name
|
||||
* @return channel info
|
||||
*/
|
||||
public static ChannelInfo parse(final String channelName) {
|
||||
public static ChannelInfo parseChannelName(final String channelName) {
|
||||
Matcher m = PATTERN_USER_AND_DOMAIN.matcher(channelName);
|
||||
if (m.find()) {
|
||||
return new ChannelInfo(m.group(1), m.group(2), true);
|
||||
@ -49,6 +52,18 @@ public class ChannelNameUtils {
|
||||
"Invalid channel name: " + channelName);
|
||||
}
|
||||
|
||||
public static DialTarget parseDialTarget(final String target) {
|
||||
Matcher m = PATTERN_DIAL_TARGET.matcher(target);
|
||||
if (m.find()) {
|
||||
final String type = m.group(2);
|
||||
return new DialTarget(Integer.parseInt(m.group(1)),
|
||||
"A".equals(type) ? DialTarget.Type.AGENT :
|
||||
"AG".equals(type) ? DialTarget.Type.AGENT_GROUP :
|
||||
DialTarget.Type.QUEUE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ -58,4 +73,19 @@ public class ChannelNameUtils {
|
||||
private final String domain;
|
||||
private final boolean agent;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public static class DialTarget {
|
||||
private final Integer id;
|
||||
private final Type type;
|
||||
|
||||
public enum Type {
|
||||
AGENT,
|
||||
AGENT_GROUP,
|
||||
QUEUE
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="freeswitch/xml">
|
||||
<section name="dialplan" description="Dialplan Trunk Outbound">
|
||||
<context name="${tenant.realm}">
|
||||
<extension name="TrunkOutbound">
|
||||
<condition>
|
||||
|
||||
<!-- <#noparse> -->
|
||||
<action application="set" data="odbc-cdr-ignore-leg=true" />
|
||||
<action application="set" data="call_timeout=30" />
|
||||
<action application="set" data="hangup_after_bridge=true" />
|
||||
<action application="set" data="continue_on_fail=true" />
|
||||
|
||||
<action application="set" data="media_bug_answer_req=true" />
|
||||
<action application="set" data="RECORD_STEREO=true" />
|
||||
<!-- </#noparse> -->
|
||||
|
||||
<action application="set" data="x_role=CALLER" />
|
||||
<action application="export" data="x_conn_id=${connId}" />
|
||||
<action application="export" data="x_tenant_id=${tenant.id}" />
|
||||
<action application="export" data="x_tenant_code=${tenant.code}" />
|
||||
<action application="export" data="x_realm=${tenant.realm}" />
|
||||
|
||||
<action application="export" data="x_dial_type=MANUAL" />
|
||||
<action application="export" data="x_call_type=OUTBOUND" />
|
||||
<action application="set" data="x_agent_type=${callerAgent.type}" />
|
||||
|
||||
<!-- Recording -->
|
||||
<action application="set" data="record_post_process_exec_api=curl:${postRecUrl} post tenantId=${tenant.id}&loc=${recordingLoc}" />
|
||||
<action application="record_session" data="${recordingLoc}" />
|
||||
<!-- /Recording -->
|
||||
|
||||
<action application="bridge" data="[origination_uuid=${connId},sip_invite_call_id=${connId},odbc-cdr-ignore-leg=false,sip_contact_user=${r'${caller_id_number}'},x_role=CALLED,x_agent_type=${calledAgent.type},effective_caller_id_number=${trunk.cpn}]sofia/gateway/${gatewayName}/${targetNumber}" />
|
||||
</condition>
|
||||
</extension>
|
||||
</context>
|
||||
</section>
|
||||
</document>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="freeswitch/xml">
|
||||
<section name="result">
|
||||
<section name="${section}" description="No valid content found">
|
||||
<result status="not found" />
|
||||
</section>
|
||||
</document>
|
||||
|
@ -2,7 +2,7 @@ package com.pudonghot.yo.fsagent;
|
||||
|
||||
import org.junit.Test;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
||||
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||
|
||||
/**
|
||||
* @author Donghuang
|
||||
@ -13,7 +13,7 @@ public class ChannelNameUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testParse() {
|
||||
log.info("User and domain [{}]", ChannelNameUtils.parse("sofia/internal/700001@d1.wacai.info"));
|
||||
log.info("External number [{}]", ChannelNameUtils.parse("sofia/external/013764268709"));
|
||||
log.info("User and domain [{}]", CallStrUtils.parseChannelName("sofia/internal/700001@d1.wacai.info"));
|
||||
log.info("External number [{}]", CallStrUtils.parseChannelName("sofia/external/013764268709"));
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ public class Sequence extends BaseDomain {
|
||||
@NotUpdate
|
||||
private String name;
|
||||
@NotUpdate
|
||||
private int length;
|
||||
@NotUpdate
|
||||
private Long initVal;
|
||||
private Long step;
|
||||
private Long currentVal;
|
||||
|
@ -28,8 +28,7 @@ public class AgentStatusController implements SessionAbility {
|
||||
@Autowired
|
||||
private AgentEventService agentEventService;
|
||||
|
||||
@RequestMapping({"/", "/forcelogin"
|
||||
})
|
||||
@RequestMapping({"/", "/forcelogin"})
|
||||
public RespAgentReady signIn(
|
||||
@PathVariable("account")
|
||||
final String account) {
|
||||
|
@ -60,6 +60,12 @@ export default (function() {
|
||||
route: 'agent.list',
|
||||
icon: 'fa-user',
|
||||
text: '坐席管理'
|
||||
},
|
||||
{
|
||||
perm: 'PERM_VIEW_AGENT_STATUS_LIST',
|
||||
route: 'agent-status.list',
|
||||
icon: 'fa-user',
|
||||
text: '坐席状态管理'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
@ -70,13 +76,7 @@ export default (function() {
|
||||
route: 'campaign.list',
|
||||
icon: 'fa-fax',
|
||||
text: '外呼活动管理'
|
||||
},
|
||||
{
|
||||
perm: 'PERM_VIEW_CAMPAIGN_LIST',
|
||||
route: 'calling-list.list',
|
||||
icon: 'fa fa-list-alt',
|
||||
text: '拨打列表'
|
||||
},
|
||||
}
|
||||
]
|
||||
}, {
|
||||
text: '公共',
|
||||
|
@ -97,25 +97,19 @@ Router.map(function() {
|
||||
this.route('edit', {path: '/:id/edit'});
|
||||
});
|
||||
|
||||
this.route('campaign-stat', function() {
|
||||
this.route('list');
|
||||
});
|
||||
|
||||
this.route('trunk-attr', function() {
|
||||
this.route('list');
|
||||
this.route('create');
|
||||
this.route('edit', {path: '/:id/edit'});
|
||||
});
|
||||
|
||||
this.route('calling-list', function() {
|
||||
this.route('list');
|
||||
this.route('create');
|
||||
this.route('edit', {path: '/:id/edit'});
|
||||
});
|
||||
|
||||
this.route('call-detail-record', function() {
|
||||
this.route('list');
|
||||
});
|
||||
|
||||
this.route('agent-status', function() {
|
||||
this.route('list');
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
6
web/cms/app/routes/agent-status/list.js
Normal file
6
web/cms/app/routes/agent-status/list.js
Normal file
@ -0,0 +1,6 @@
|
||||
import BaseListRoute from './../base-list';
|
||||
|
||||
export default BaseListRoute.extend({
|
||||
perm: 'PERM_VIEW_AGENT_STATUS_LIST',
|
||||
breadcrumbs: [{text: '坐席状态列表'}],
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
import BaseRoute from '../base';
|
||||
import RSVP from 'rsvp';
|
||||
|
||||
export default BaseRoute.extend({
|
||||
perm: 'PERM_VIEW_CAMPAIGN_CREATE',
|
||||
breadcrumbs: [{route: 'calling-list.list', text: '拨打列表'}, {text: '创建拨打名单'}],
|
||||
model() {
|
||||
const store = this.get('store');
|
||||
return RSVP.hash({
|
||||
active: true,
|
||||
privacy: true,
|
||||
campaignList: store.ajaxGet('campaign/list'),
|
||||
callingListStatus: store.ajaxGet('calling-list/status'),
|
||||
timeList:[{value:'25200', text:'7:00'},
|
||||
{value:'28800', text:'8:00'},
|
||||
{value:'32400', text:'9:00'},
|
||||
{value:'36000', text:'10:00'},
|
||||
{value:'39600', text:'11:00'},
|
||||
{value:'43200', text:'12:00'},
|
||||
{value:'46800', text:'13:00'},
|
||||
{value:'50400', text:'14:00'},
|
||||
{value:'54000', text:'15:00'},
|
||||
{value:'57600', text:'16:00'},
|
||||
{value:'61200', text:'17:00'},
|
||||
{value:'64800', text:'18:00'},
|
||||
{value:'68400', text:'19:00'},
|
||||
{value:'72000', text:'20:00'},
|
||||
{value:'75600', text:'21:00'},
|
||||
{value:'79200', text:'22:00'},
|
||||
{value:'82800', text:'23:00'}
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
import BaseEditRoute from '../base-edit';
|
||||
|
||||
export default BaseEditRoute.extend({
|
||||
perm: 'PERM_VIEW_CAMPAIGN_EDIT',
|
||||
afterModel(model) {
|
||||
this.set('breadcrumbs',
|
||||
[{route: 'calling-list.list', text: '拨打列表'},
|
||||
{text: '编辑拨打名单'}]);
|
||||
model.timeList=[{value:'25200', text:'7:00'},
|
||||
{value:'28800', text:'8:00'},
|
||||
{value:'32400', text:'9:00'},
|
||||
{value:'36000', text:'10:00'},
|
||||
{value:'39600', text:'11:00'},
|
||||
{value:'43200', text:'12:00'},
|
||||
{value:'46800', text:'13:00'},
|
||||
{value:'50400', text:'14:00'},
|
||||
{value:'54000', text:'15:00'},
|
||||
{value:'57600', text:'16:00'},
|
||||
{value:'61200', text:'17:00'},
|
||||
{value:'64800', text:'18:00'},
|
||||
{value:'68400', text:'19:00'},
|
||||
{value:'72000', text:'20:00'},
|
||||
{value:'75600', text:'21:00'},
|
||||
{value:'79200', text:'22:00'},
|
||||
{value:'82800', text:'23:00'}
|
||||
]
|
||||
}
|
||||
});
|
@ -1,6 +0,0 @@
|
||||
import BaseListRoute from './../base-list';
|
||||
|
||||
export default BaseListRoute.extend({
|
||||
perm: 'PERM_VIEW_CAMPAIGN_LIST',
|
||||
breadcrumbs: [{text: '拨打列表'}]
|
||||
});
|
@ -7,6 +7,7 @@ export default BaseRoute.extend({
|
||||
return {
|
||||
active: true,
|
||||
initVal: 1,
|
||||
length: 4,
|
||||
step: 1
|
||||
};
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpComponentGroup',
|
||||
constraints: {
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpComponent',
|
||||
createConstraints: {
|
||||
groupId: {
|
||||
presence: true
|
||||
},
|
||||
type: {
|
||||
presence: true
|
||||
},
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
},
|
||||
constraints: {
|
||||
groupId: {
|
||||
presence: true
|
||||
},
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpNodeSample',
|
||||
constraints: {
|
||||
sampleText: {
|
||||
presence: true,
|
||||
}
|
||||
},
|
||||
batchCreateConstraints: {
|
||||
sampleTexts: {
|
||||
presence: true
|
||||
}
|
||||
},
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpNode',
|
||||
constraints: {
|
||||
componentId: {
|
||||
presence: true
|
||||
},
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
},
|
||||
nodeIndex: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
onlyInteger: true,
|
||||
greaterThanOrEqualTo: 0,
|
||||
lessThanOrEqualTo: 128
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpTermGroup',
|
||||
constraints: {
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'NlpTerm',
|
||||
constraints: {
|
||||
groupId: {
|
||||
presence: true
|
||||
},
|
||||
term: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
},
|
||||
pinyin: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 128
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -10,6 +10,14 @@ export default Service.extend({
|
||||
maximum: 36
|
||||
}
|
||||
},
|
||||
length: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
onlyInteger: true,
|
||||
greaterThan: 1,
|
||||
lessThanOrEqualTo: 16
|
||||
}
|
||||
},
|
||||
initVal: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
@ -26,14 +34,7 @@ export default Service.extend({
|
||||
}
|
||||
}
|
||||
},
|
||||
createConstraints: {
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
},
|
||||
updateConstraints: {
|
||||
step: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
|
@ -1,53 +0,0 @@
|
||||
import BaseService from '../service';
|
||||
|
||||
export default BaseService.extend({
|
||||
modelName: 'TalkComponent',
|
||||
constraints: {
|
||||
note: {
|
||||
presence: true
|
||||
},
|
||||
content: {
|
||||
presence: true
|
||||
},
|
||||
},
|
||||
voiceConstraints: {
|
||||
note: {
|
||||
presence: true
|
||||
},
|
||||
voice: {
|
||||
presence: true
|
||||
}
|
||||
},
|
||||
ttsConstraints: {
|
||||
text: {
|
||||
presence: true
|
||||
},
|
||||
ttsVoiceActor: {
|
||||
presence: true
|
||||
},
|
||||
note: {
|
||||
presence: true
|
||||
},
|
||||
volume: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
},
|
||||
speechRate: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
},
|
||||
pitchRate: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'TalkGroup',
|
||||
constraints: {
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
import BaseService from '../service';
|
||||
|
||||
export default BaseService.extend({
|
||||
modelName: 'Talk',
|
||||
normalConstraints: {
|
||||
talk: {
|
||||
presence: true
|
||||
}
|
||||
},
|
||||
audioConstraints: {
|
||||
overwriteStrategy: {
|
||||
presence: true
|
||||
},
|
||||
files: {
|
||||
presence: true
|
||||
}
|
||||
},
|
||||
ttsConstraints: {
|
||||
talk: {
|
||||
presence: true
|
||||
},
|
||||
ttsVoiceActor: {
|
||||
presence: true
|
||||
},
|
||||
volume: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
},
|
||||
speechRate: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
},
|
||||
pitchRate: {
|
||||
presence: true,
|
||||
numericality: {
|
||||
notLessThan: -500,
|
||||
notGreaterThan: 500
|
||||
}
|
||||
}
|
||||
},
|
||||
updateConstraints: {
|
||||
talk: {
|
||||
presence: true
|
||||
}
|
||||
},
|
||||
importConstraints: {
|
||||
archive: {
|
||||
presence: true
|
||||
}
|
||||
}
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
import Service from '../service';
|
||||
|
||||
export default Service.extend({
|
||||
modelName: 'VoiceActor',
|
||||
constraints: {
|
||||
name: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
},
|
||||
code: {
|
||||
presence: true,
|
||||
length: {
|
||||
minimum: 1,
|
||||
maximum: 36
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
88
web/cms/app/templates/agent-status/list.hbs
Normal file
88
web/cms/app/templates/agent-status/list.hbs
Normal file
@ -0,0 +1,88 @@
|
||||
<div class="widget-box transparent" style="padding-top: 2px; border: 1px solid #ddd;">
|
||||
{{grid-header search-box=false}}
|
||||
|
||||
<div class="widget-body">
|
||||
<!-- #section:custom/scrollbar -->
|
||||
<div class="widget-main no-padding">
|
||||
<table class="table table-striped table-bordered table-hover dataTable">
|
||||
<thead class="thin-border-bottom">
|
||||
<tr>
|
||||
<th>
|
||||
账户
|
||||
</th>
|
||||
<th>
|
||||
分机
|
||||
</th>
|
||||
<th>
|
||||
{{th-filter name='registerd'
|
||||
text='注册状态'
|
||||
options=(array
|
||||
(hash value=true text='在线')
|
||||
(hash value=false text='离线')
|
||||
)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
{{th-filter name='status'
|
||||
text='坐席状态'
|
||||
options=(array
|
||||
(hash value='READY' text='就绪')
|
||||
(hash value='NOT_READY' text='未就绪')
|
||||
(hash value='OFFLINE' text='离线')
|
||||
)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
{{th-filter name='state'
|
||||
text='话机状态'
|
||||
options=(array
|
||||
(hash value='IDLE' text='空闲')
|
||||
(hash value='IN_A_CALL' text='通话中')
|
||||
(hash value='ACW' text='话后事务')
|
||||
)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
通话ID
|
||||
</th>
|
||||
<th>
|
||||
事件Key
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each model.data as |it|}}
|
||||
<tr>
|
||||
<td>
|
||||
{{it.account}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.agent}}
|
||||
</td>
|
||||
<td>
|
||||
{{status-cell model=it
|
||||
field='registered'
|
||||
enabled-text='在线'
|
||||
disabled-text='离线'}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.status}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.state}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.callUuid}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.eventKey}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{pagination-bar}}
|
||||
</div>
|
||||
</div>
|
||||
{{outlet}}
|
@ -1,41 +0,0 @@
|
||||
{{#form-content}}
|
||||
<hr />
|
||||
{{form-input-select
|
||||
name='campaignId'
|
||||
label='外呼活动'
|
||||
options=model.campaignList.data
|
||||
value-field='id'
|
||||
text-field='name'
|
||||
enabled-field='active'
|
||||
}}
|
||||
{{form-input name='phone' label='号码'}}
|
||||
{{form-input-select
|
||||
name='status'
|
||||
label='状态'
|
||||
options=model.callingListStatus
|
||||
value-field='value'
|
||||
text-field='label'
|
||||
}}
|
||||
{{form-input-select
|
||||
name='dailyFrom'
|
||||
label='开始时间'
|
||||
options=model.timeList
|
||||
value-field='value'
|
||||
text-field='text'
|
||||
}}
|
||||
{{form-input-select
|
||||
name='dailyTo'
|
||||
label='结束时间'
|
||||
options=model.timeList
|
||||
value-field='value'
|
||||
text-field='text'
|
||||
}}
|
||||
{{form-input name='taskKey' label='任务KEY'}}
|
||||
{{form-input name='recordKey' label='记录KEY'}}
|
||||
{{form-input type='textarea' name='recordData' label='记录数据' placeholder='请在此输入业务数据,例如:{"username": "东皇", "gender": "MALE"}'}}
|
||||
{{form-input name='attachedData' label='随路数据'}}
|
||||
{{form-input name='note' label='备注'}}
|
||||
<hr />
|
||||
{{form-footer-buttons type='create'}}
|
||||
{{/form-content}}
|
||||
{{outlet}}
|
@ -1,60 +0,0 @@
|
||||
{{#form-content}}
|
||||
<hr />
|
||||
{{form-input type='hidden' name='id'}}
|
||||
{{form-input-select
|
||||
name='campaignId'
|
||||
label='外呼活动'
|
||||
options=model.campaignList
|
||||
value-field='id'
|
||||
text-field='name'
|
||||
enabled-field='active'
|
||||
}}
|
||||
{{form-input name='phone' label='号码'}}
|
||||
{{form-input-select
|
||||
name='status'
|
||||
label='状态'
|
||||
options=model.callingListStatus
|
||||
value-field='value'
|
||||
text-field='label'
|
||||
}}
|
||||
{{form-input-select
|
||||
name='dailyFrom'
|
||||
label='开始时间'
|
||||
options=model.timeList
|
||||
value-field='value'
|
||||
text-field='text'
|
||||
}}
|
||||
{{form-input-select
|
||||
name='dailyTo'
|
||||
label='结束时间'
|
||||
options=model.timeList
|
||||
value-field='value'
|
||||
text-field='text'
|
||||
}}
|
||||
{{form-input name='callUuid' label='通话ID'}}
|
||||
{{form-input-datetimepicker
|
||||
labelClass='col-sm-3 col-md-3'
|
||||
inputClass='col-sm-5 col-md-5'
|
||||
name='callStartTime'
|
||||
label='通话开始时间'}}
|
||||
{{form-input-datetimepicker
|
||||
labelClass='col-sm-3 col-md-3'
|
||||
inputClass='col-sm-5 col-md-5'
|
||||
name='callEstablishedTime'
|
||||
label='通话接通时间'}}
|
||||
{{form-input-datetimepicker
|
||||
labelClass='col-sm-3 col-md-3'
|
||||
inputClass='col-sm-5 col-md-5'
|
||||
name='callEndTime'
|
||||
label='通话结束时间'}}
|
||||
{{form-input name='callResult' label='通话结果'}}
|
||||
{{form-input name='taskKey' label='任务KEY'}}
|
||||
{{form-input name='recordKey' label='记录KEY'}}
|
||||
{{form-input type='textarea' name='recordData' label='记录数据' placeholder='请在此输入业务数据,例如:{"username": "东皇", "gender": "MALE"}'}}
|
||||
{{form-input name='attachedData' label='随路数据'}}
|
||||
{{form-input-enabled}}
|
||||
{{form-input name='note' label='备注'}}
|
||||
<hr />
|
||||
{{form-footer-buttons type='update'}}
|
||||
{{/form-content}}
|
||||
{{outlet}}
|
@ -1,137 +0,0 @@
|
||||
<div class="widget-box transparent" style="padding-top: 2px; border: 1px solid #ddd;">
|
||||
{{#grid-header}}
|
||||
{{#has-perm 'PERM_VIEW_CAMPAIGN_CREATE'}}
|
||||
<li>
|
||||
{{#link-to 'calling-list.create'}}
|
||||
<i class="ace-icon fa fa-plus-circle bigger-110 green"></i>
|
||||
新建名单
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/has-perm}}
|
||||
{{/grid-header}}
|
||||
|
||||
<div class="widget-body">
|
||||
<!-- #section:custom/scrollbar -->
|
||||
<div class="widget-main no-padding">
|
||||
<table class="table table-striped table-bordered table-hover dataTable">
|
||||
<thead class="thin-border-bottom">
|
||||
<tr>
|
||||
<th>
|
||||
{{th-filter name='campaignId'
|
||||
text='外呼活动'
|
||||
options=model.campaignList
|
||||
value-field='id'
|
||||
text-field='name'
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
号码
|
||||
</th>
|
||||
<th>
|
||||
{{th-filter name='status'
|
||||
text='状态'
|
||||
options=model.callingListStatus
|
||||
value-field='value'
|
||||
text-field='label'
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
开始时间
|
||||
</th>
|
||||
<th>
|
||||
结束时间
|
||||
</th>
|
||||
<th>
|
||||
通话ID
|
||||
</th>
|
||||
<th>
|
||||
拨打开始时间
|
||||
</th>
|
||||
<th>
|
||||
拨打接通时间
|
||||
</th>
|
||||
<th>
|
||||
拨打结束时间
|
||||
</th>
|
||||
<th>
|
||||
拨打结果
|
||||
</th>
|
||||
<th>
|
||||
任务KEY
|
||||
</th>
|
||||
<th>
|
||||
记录KEY
|
||||
</th>
|
||||
<th>
|
||||
创建时间
|
||||
</th>
|
||||
<th>
|
||||
<i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i>
|
||||
管理
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each model.data as |it|}}
|
||||
<tr>
|
||||
<td>
|
||||
{{it.campaign.name}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.phone}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.status}}
|
||||
</td>
|
||||
<td>
|
||||
{{second-time timeInSecond=it.dailyFrom}}
|
||||
</td>
|
||||
<td>
|
||||
{{second-time timeInSecond=it.dailyTo}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.callUuid}}
|
||||
</td>
|
||||
<td>
|
||||
{{date-cell value=it.callStartTime format='YYYY-MM-DD H:mm:ss'}}
|
||||
</td>
|
||||
<td>
|
||||
{{date-cell value=it.callEstablishedTime format='YYYY-MM-DD H:mm:ss'}}
|
||||
</td>
|
||||
<td>
|
||||
{{date-cell value=it.callEndTime format='YYYY-MM-DD H:mm:ss'}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.callResult}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.taskKey}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.recordKey}}
|
||||
</td>
|
||||
<td>
|
||||
{{date-cell value=it.createdTime format='YYYY-MM-DD H:mm:ss'}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{#has-perm 'PERM_VIEW_CAMPAIGN_EDIT'}}
|
||||
{{status-toggle-button model=it}}
|
||||
{{edit-btn route-name='calling-list.edit' model-id=it.id perm='PERM_VIEW_CAMPAIGN_EDIT'}}
|
||||
{{/has-perm}}
|
||||
{{#has-perm 'PERM_VIEW_CAMPAIGN_DELETE'}}
|
||||
{{#unless it.active}}
|
||||
{{delete-button model=it}}
|
||||
{{/unless}}
|
||||
{{/has-perm}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{pagination-bar}}
|
||||
</div>
|
||||
</div>
|
||||
{{outlet}}
|
@ -140,13 +140,6 @@
|
||||
{{delete-button model=it}}
|
||||
{{/unless}}
|
||||
{{/has-perm}}
|
||||
{{#link-to 'calling-list.list'
|
||||
(query-params filters=(concat '{"campaignId":[' it.id ']}'))
|
||||
class='btn btn-xs btn-info'
|
||||
data-rel='tooltip'
|
||||
title='拨打列表'}}
|
||||
<i class="ace-icon fa fa-sliders bigger-120"></i>
|
||||
{{/link-to}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -1,6 +1,7 @@
|
||||
{{#form-content}}
|
||||
<hr />
|
||||
{{form-input name='name' label='名称'}}
|
||||
{{form-input name='length' label='长度'}}
|
||||
{{form-input name='initVal' label='初始值'}}
|
||||
{{form-input name='step' label='递增值'}}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
{{#form-content}}
|
||||
{{form-input type='hidden' name='id'}}
|
||||
{{form-input name='name' label='名称'}}
|
||||
{{form-input name='name' label='名称' readonly=true}}
|
||||
{{form-input name='length' label='长度' readonly=true}}
|
||||
{{form-input name='initVal' label='初始值' readonly=true}}
|
||||
{{form-input name='step' label='递增值'}}
|
||||
|
||||
|
@ -19,6 +19,9 @@
|
||||
<th>
|
||||
名称
|
||||
</th>
|
||||
<th>
|
||||
长度
|
||||
</th>
|
||||
<th>
|
||||
当前值
|
||||
</th>
|
||||
@ -53,6 +56,9 @@
|
||||
<td>
|
||||
{{it.name}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.length}}
|
||||
</td>
|
||||
<td>
|
||||
{{it.currentVal}}
|
||||
</td>
|
||||
|
11
web/cms/tests/unit/routes/agent-status/list-test.js
Normal file
11
web/cms/tests/unit/routes/agent-status/list-test.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | agent-status/list', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:agent-status/list');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user