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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import javax.validation.constraints.Min;
|
import javax.validation.constraints.*;
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
import javax.validation.constraints.Pattern;
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
import com.wacai.tigon.format.annotation.Trim;
|
import com.wacai.tigon.format.annotation.Trim;
|
||||||
import com.pudonghot.yo.cms.form.BaseCreateForm;
|
import com.pudonghot.yo.cms.form.BaseCreateForm;
|
||||||
|
|
||||||
@ -20,10 +17,14 @@ public class CreateFormSequence extends BaseCreateForm {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
@Pattern(regexp = "^\\w+$", message = "序列名必须是字母数字下划线构成")
|
@Pattern(regexp = "^\\w+$", message = "序列名必须是字母数字下划线构成")
|
||||||
private String name;
|
private String name;
|
||||||
|
@Min(2)
|
||||||
|
@Max(16)
|
||||||
|
private int length;
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long initVal;
|
private Long initVal;
|
||||||
@Min(1)
|
@Min(1)
|
||||||
|
@Max(128)
|
||||||
@NotNull
|
@NotNull
|
||||||
private Long step;
|
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;
|
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.pudonghot.yo.model.domain.Sequence;
|
||||||
import com.wacai.tigon.service.BaseCrudByFormService;
|
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>
|
* @author Donghuang <br>
|
||||||
@ -22,4 +22,13 @@ public interface SequenceService
|
|||||||
* @return next seq val
|
* @return next seq val
|
||||||
*/
|
*/
|
||||||
Long nextVal(Integer tenantId, String name);
|
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);
|
super.beforeInsert(model);
|
||||||
if (StringUtils.isBlank(model.getIdentifier())) {
|
if (StringUtils.isBlank(model.getIdentifier())) {
|
||||||
model.setIdentifier(identifierPrefix +
|
model.setIdentifier(identifierPrefix +
|
||||||
StringUtils.leftPad(String.valueOf(
|
seqService.nextValStr(model.getTenantId(), seqName));
|
||||||
seqService.nextVal(model.getTenantId(), seqName)), 6, '0'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ public class AgentServiceImpl
|
|||||||
protected void beforeInsert(final Agent model) {
|
protected void beforeInsert(final Agent model) {
|
||||||
super.beforeInsert(model);
|
super.beforeInsert(model);
|
||||||
if (StringUtils.isBlank(model.getAgent())) {
|
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())) {
|
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);
|
final Tenant tenant = tenantMapper.find(tenantId);
|
||||||
Assert.state(tenant != null,
|
Assert.state(tenant != null,
|
||||||
() -> "No tenant [" + tenantId + "] found");
|
() -> "No tenant [" + tenantId + "] found");
|
||||||
model.setIdentifier(identifierPrefix +
|
model.setIdentifier(identifierPrefix + seqService.nextValStr(tenantId, seqName));
|
||||||
StringUtils.leftPad(String.valueOf(
|
|
||||||
seqService.nextVal(tenantId, seqName)), 6, '0'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package com.pudonghot.yo.cms.service.impl;
|
package com.pudonghot.yo.cms.service.impl;
|
||||||
|
|
||||||
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.wacai.tigon.mybatis.Search;
|
import com.wacai.tigon.mybatis.Search;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import com.pudonghot.yo.mapper.SequenceMapper;
|
import com.pudonghot.yo.mapper.SequenceMapper;
|
||||||
import com.pudonghot.yo.model.domain.Sequence;
|
import com.pudonghot.yo.model.domain.Sequence;
|
||||||
import com.pudonghot.yo.cms.service.SequenceService;
|
import com.pudonghot.yo.cms.service.SequenceService;
|
||||||
|
import com.pudonghot.yo.cms.form.create.CreateFormSequence;
|
||||||
import com.pudonghot.yo.cms.form.update.UpdateFormSequence;
|
import com.pudonghot.yo.cms.form.update.UpdateFormSequence;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.wacai.tigon.service.support.BaseCrudByFormServiceSupport;
|
import com.wacai.tigon.service.support.BaseCrudByFormServiceSupport;
|
||||||
@ -42,17 +43,18 @@ public class SequenceServiceImpl
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional(rollbackFor = RuntimeException.class)
|
||||||
public Long nextVal(final Integer tenantId, final String name) {
|
public Long nextVal(final Integer tenantId, final String name) {
|
||||||
final Sequence seq = mapper.findForUpdate(
|
return nextVal(tenantId, name, false);
|
||||||
new Search(Sequence.NAME, name)
|
}
|
||||||
.eq(Sequence.TENANT_ID, tenantId)
|
|
||||||
.eq(Sequence.ACTIVE, true));
|
/**
|
||||||
Assert.state(seq != null, () -> "No valid sequence [" + name + "] found");
|
* {@inheritDoc}
|
||||||
final Long nextVal = seq.getCurrentVal() + seq.getStep();
|
*/
|
||||||
seq.setCurrentVal(nextVal);
|
@Override
|
||||||
mapper.update(seq);
|
@Transactional(rollbackFor = RuntimeException.class)
|
||||||
return nextVal;
|
public String nextValStr(final Integer tenantId, final String name) {
|
||||||
|
return nextVal(tenantId, name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,8 +63,28 @@ public class SequenceServiceImpl
|
|||||||
@Override
|
@Override
|
||||||
protected void beforeInsert(final Sequence model) {
|
protected void beforeInsert(final Sequence model) {
|
||||||
super.beforeInsert(model);
|
super.beforeInsert(model);
|
||||||
|
Assert.state(String.valueOf(model.getInitVal()).length() <= model.getLength(),
|
||||||
|
"Sequence init value exceeds length");
|
||||||
if (model.getCurrentVal() == null) {
|
if (model.getCurrentVal() == null) {
|
||||||
model.setCurrentVal(model.getInitVal());
|
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;
|
package com.pudonghot.yo.cms.service.impl;
|
||||||
|
|
||||||
import java.util.*;
|
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.mapper.*;
|
||||||
import com.pudonghot.yo.model.domain.*;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import com.wacai.tigon.mybatis.Search;
|
import com.wacai.tigon.mybatis.Search;
|
||||||
|
import com.pudonghot.yo.model.domain.*;
|
||||||
import com.wacai.tigon.model.ViewModel;
|
import com.wacai.tigon.model.ViewModel;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import com.pudonghot.yo.cms.form.SessionForm;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import com.pudonghot.yo.cellphone.CellphoneInfo;
|
import com.pudonghot.yo.cellphone.CellphoneInfo;
|
||||||
import com.pudonghot.yo.common.util.MobileUtils;
|
import com.pudonghot.yo.common.util.MobileUtils;
|
||||||
import com.pudonghot.yo.cellphone.CellphoneService;
|
|
||||||
import com.pudonghot.yo.cms.service.TrunkService;
|
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.AreaCodeService;
|
||||||
import com.pudonghot.yo.cms.service.SequenceService;
|
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 com.pudonghot.yo.cms.form.update.UpdateFormTrunk;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,8 +140,7 @@ public class TrunkServiceImpl
|
|||||||
protected void beforeInsert(final Trunk model) {
|
protected void beforeInsert(final Trunk model) {
|
||||||
super.beforeInsert(model);
|
super.beforeInsert(model);
|
||||||
if (StringUtils.isBlank(model.getPrefix())) {
|
if (StringUtils.isBlank(model.getPrefix())) {
|
||||||
model.setPrefix(String.valueOf(
|
model.setPrefix(seqService.nextValStr(model.getTenantId(), seqName));
|
||||||
seqService.nextVal(model.getTenantId(), seqName)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,8 @@ spring.redis.port=6379
|
|||||||
|
|
||||||
# CAS
|
# CAS
|
||||||
tigon.shiro.cas.server.addr=http://118.24.251.131:8080/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 \
|
tigon.shiro.filter-chain=${site.context-path}/auth/login=anon \
|
||||||
/=anon \
|
/=anon \
|
||||||
/state-machine/**=anon \
|
/state-machine/**=anon \
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.pudonghot.yo.fsagent.controller;
|
package com.pudonghot.yo.fsagent.controller;
|
||||||
|
|
||||||
import com.pudonghot.yo.model.domain.Tenant;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.wacai.tigon.mybatis.Search;
|
import com.wacai.tigon.mybatis.Search;
|
||||||
|
import com.pudonghot.yo.model.domain.Tenant;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -43,4 +43,12 @@ public class BaseDialplanController extends BaseXmlController {
|
|||||||
thisDn + "-" +
|
thisDn + "-" +
|
||||||
otherDn + recordingFileExt;
|
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 javax.sql.DataSource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.wacai.tigon.mybatis.Search;
|
import com.wacai.tigon.mybatis.Search;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import com.pudonghot.yo.model.domain.Gateway;
|
import com.pudonghot.yo.model.domain.Gateway;
|
||||||
import com.alibaba.druid.pool.DruidDataSource;
|
import com.alibaba.druid.pool.DruidDataSource;
|
||||||
import com.pudonghot.yo.fsagent.util.OdbcUtils;
|
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 FreeMarkerView view = view("config/sofia.conf.xml");
|
||||||
final List<Gateway> gateways = gatewayService.list(
|
final List<Gateway> gateways = gatewayService.list(
|
||||||
new Search(Gateway.ACTIVE, true));
|
new Search(Gateway.ACTIVE, true));
|
||||||
gateways.forEach(g -> g.setName(
|
gateways.forEach(g -> g.setName(gatewayService.genGatewayName(g)));
|
||||||
"GW" + StringUtils.leftPad(String.valueOf(g.getId()), 6, '0')));
|
|
||||||
attr(view, "gateways", gateways);
|
attr(view, "gateways", gateways);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
@ -2,14 +2,20 @@ package com.pudonghot.yo.fsagent.controller;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.pudonghot.yo.model.domain.Agent;
|
||||||
import com.wacai.tigon.sequence.IdSequence;
|
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 org.springframework.stereotype.Controller;
|
||||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||||
import com.pudonghot.yo.fsagent.service.LocalApiService;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import static com.pudonghot.yo.fsagent.util.CallStrUtils.DialTarget;
|
||||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
|
import org.springframework.web.servlet.view.freemarker.FreeMarkerView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,9 +28,17 @@ public class DialplanInternalController extends BaseDialplanController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AgentService agentService;
|
private AgentService agentService;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private AgentGroupService agentGroupService;
|
||||||
|
@Autowired
|
||||||
|
private QueueService queueService;
|
||||||
|
@Autowired
|
||||||
private LocalApiService localApiService;
|
private LocalApiService localApiService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private IdSequence idSeq;
|
private IdSequence idSeq;
|
||||||
|
@Autowired
|
||||||
|
private TrunkService trunkService;
|
||||||
|
@Autowired
|
||||||
|
private GatewayService gatewayService;
|
||||||
|
|
||||||
@RequestMapping(params = {
|
@RequestMapping(params = {
|
||||||
"variable_domain_name",
|
"variable_domain_name",
|
||||||
@ -41,31 +55,108 @@ public class DialplanInternalController extends BaseDialplanController {
|
|||||||
@RequestParam
|
@RequestParam
|
||||||
final Map<String, String> params) {
|
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);
|
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 =
|
final Agent calledAgent =
|
||||||
agentService.findOfCalled(domain, calledNumber);
|
agentService.findOfCalled(domain, calledNumber);
|
||||||
|
|
||||||
if (calledAgent != null) {
|
if (calledAgent != null) {
|
||||||
log.info("Local extension [{}] found.", calledAgent);
|
log.info("Local extension [{}] found.", calledAgent);
|
||||||
final FreeMarkerView view = view(domain, "local-extension.xml");
|
return agentCallAgent(domain, callerAgent, calledAgent);
|
||||||
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,
|
|
||||||
callerAgent.getAccount(),
|
|
||||||
calledAgent.getAccount()));
|
|
||||||
attr(view, "postRecUrl", localApiService.getPostRecUrl());
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Dial Queue
|
log.warn("No correct dialplan found for called number [{}].", calledNumber);
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
|
||||||
return view(domain, "dialplan.xml");
|
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());
|
||||||
|
attr(view, "callerAgent", callerAgent);
|
||||||
|
attr(view, "calledAgent", calledAgent);
|
||||||
|
attr(view, "recordingLoc", recordingFile(connId,
|
||||||
|
callerAgent.getAccount(),
|
||||||
|
calledAgent.getAccount()));
|
||||||
|
attr(view, "postRecUrl", localApiService.getPostRecUrl());
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
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.freeswitch.esl.client.transport.event.Event;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@ -39,13 +39,8 @@ public class ChannelAnswer {
|
|||||||
public void onChannelAnswer(final Event event) {
|
public void onChannelAnswer(final Event event) {
|
||||||
log.debug("On channel answer event [{}] [{}].", event, event.getHeaders());
|
log.debug("On channel answer event [{}] [{}].", event, event.getHeaders());
|
||||||
|
|
||||||
// sofia/internal/700001@d1.wacai.info
|
final CallStrUtils.ChannelInfo channelInfo =
|
||||||
// sofia/external/013764268709
|
CallStrUtils.getChannelInfo(event);
|
||||||
final String channelName = event.getHeader("variable_channel_name");
|
|
||||||
log.info("On channel [{}] answer event.", channelName);
|
|
||||||
|
|
||||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
|
||||||
ChannelNameUtils.parse(channelName);
|
|
||||||
|
|
||||||
if (channelInfo.isAgent()) {
|
if (channelInfo.isAgent()) {
|
||||||
final Agent agent = findAgent(channelInfo);
|
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(
|
return agentService.findByDomainAndAgent(
|
||||||
channelInfo.getDomain(), channelInfo.getNumber());
|
channelInfo.getDomain(), channelInfo.getNumber());
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package com.pudonghot.yo.fsagent.listener;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
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.freeswitch.esl.client.transport.event.Event;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@ -29,7 +29,7 @@ public class ChannelCreate {
|
|||||||
public void onChannelCreate(final Event event) {
|
public void onChannelCreate(final Event event) {
|
||||||
log.debug("On channel create event [{}] [{}].", event, event.getHeaders());
|
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()) {
|
if (channelInfo.isAgent()) {
|
||||||
agentStatusService.inACall(channelInfo.getDomain(),
|
agentStatusService.inACall(channelInfo.getDomain(),
|
||||||
|
@ -9,14 +9,14 @@ import org.springframework.stereotype.Component;
|
|||||||
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
import com.pudonghot.yo.fsagent.util.EslEventUtils;
|
||||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
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.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.freeswitch.esl.client.transport.event.Event;
|
import org.freeswitch.esl.client.transport.event.Event;
|
||||||
import com.pudonghot.yo.service.CommonAgentStatusService;
|
import com.pudonghot.yo.service.CommonAgentStatusService;
|
||||||
import com.pudonghot.yo.service.CommonAgentEventQueueService;
|
import com.pudonghot.yo.service.CommonAgentEventQueueService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.AgentOther_PhoneRelease;
|
||||||
import static com.pudonghot.yo.model.agentevent.EventType.AgentEvent_Customer_Release;
|
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 presenceId = event.getHeader("variable_presence_id");
|
||||||
final String callUuid = event.getCallUuid();
|
final String callUuid = event.getCallUuid();
|
||||||
log.info("On channel [{}][{}] destroy event.", presenceId, callUuid);
|
log.info("On channel [{}][{}] destroy event.", presenceId, callUuid);
|
||||||
final ChannelInfo channelInfo = ChannelNameUtils.get(event);
|
final ChannelInfo channelInfo = CallStrUtils.getChannelInfo(event);
|
||||||
|
|
||||||
if (channelInfo.isAgent()) {
|
if (channelInfo.isAgent()) {
|
||||||
agentStatusService.acw(channelInfo.getDomain(),
|
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.util.EslEventUtils;
|
||||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
import com.pudonghot.yo.fsagent.service.AgentService;
|
||||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
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.freeswitch.esl.client.transport.event.Event;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@ -41,8 +41,8 @@ public class ChannelHangupComplete {
|
|||||||
|
|
||||||
if (!event.isHangupNormalClearing()) {
|
if (!event.isHangupNormalClearing()) {
|
||||||
|
|
||||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
final CallStrUtils.ChannelInfo channelInfo =
|
||||||
ChannelNameUtils.get(event);
|
CallStrUtils.getChannelInfo(event);
|
||||||
|
|
||||||
if (channelInfo.isAgent()) {
|
if (channelInfo.isAgent()) {
|
||||||
final Agent agent = agentService.findByDomainAndAgent(
|
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.fsagent.util.EslEventUtils;
|
||||||
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
import com.pudonghot.yo.model.agentevent.AgentEvent;
|
||||||
import com.pudonghot.yo.fsagent.service.AgentService;
|
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.freeswitch.esl.client.transport.event.Event;
|
||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@ -42,8 +42,8 @@ public class ChannelProgress {
|
|||||||
log.info("On channel [{}] answer event.", channelName);
|
log.info("On channel [{}] answer event.", channelName);
|
||||||
|
|
||||||
if (EslEventUtils.isCalled(event)) {
|
if (EslEventUtils.isCalled(event)) {
|
||||||
final ChannelNameUtils.ChannelInfo channelInfo =
|
final CallStrUtils.ChannelInfo channelInfo =
|
||||||
ChannelNameUtils.get(event);
|
CallStrUtils.getChannelInfo(event);
|
||||||
if (channelInfo.isAgent()) {
|
if (channelInfo.isAgent()) {
|
||||||
final Agent agent = agentService.findByDomainAndAgent(
|
final Agent agent = agentService.findByDomainAndAgent(
|
||||||
channelInfo.getDomain(),
|
channelInfo.getDomain(),
|
||||||
|
@ -8,4 +8,20 @@ import com.wacai.tigon.service.BaseQueryService;
|
|||||||
* Dec 04, 2019 19:04:12
|
* Dec 04, 2019 19:04:12
|
||||||
*/
|
*/
|
||||||
public interface GatewayService extends BaseQueryService<Integer, Gateway> {
|
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
|
* @return called number
|
||||||
*/
|
*/
|
||||||
String calledNumber(Trunk trunk, String 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;
|
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.mapper.GatewayMapper;
|
||||||
import com.pudonghot.yo.model.domain.Gateway;
|
import com.pudonghot.yo.model.domain.Gateway;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.pudonghot.yo.fsagent.service.GatewayService;
|
||||||
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
|
import com.wacai.tigon.service.support.BaseQueryServiceSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,4 +19,20 @@ public class GatewayerviceImpl
|
|||||||
Gateway,
|
Gateway,
|
||||||
GatewayMapper>
|
GatewayMapper>
|
||||||
implements GatewayService {
|
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 java.util.List;
|
||||||
|
|
||||||
|
import com.pudonghot.yo.fsagent.service.GatewayService;
|
||||||
import com.pudonghot.yo.fsagent.service.TrunkService;
|
import com.pudonghot.yo.fsagent.service.TrunkService;
|
||||||
import com.pudonghot.yo.mapper.GatewayMapper;
|
import com.pudonghot.yo.mapper.GatewayMapper;
|
||||||
|
import com.pudonghot.yo.mapper.SequenceMapper;
|
||||||
import com.pudonghot.yo.mapper.TrunkMapper;
|
import com.pudonghot.yo.mapper.TrunkMapper;
|
||||||
import com.pudonghot.yo.mapper.TrunkStrategyMapper;
|
import com.pudonghot.yo.mapper.TrunkStrategyMapper;
|
||||||
import com.pudonghot.yo.model.domain.Gateway;
|
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.Trunk;
|
||||||
import com.pudonghot.yo.model.domain.TrunkStrategy;
|
import com.pudonghot.yo.model.domain.TrunkStrategy;
|
||||||
|
import com.wacai.tigon.mybatis.Search;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -32,12 +37,18 @@ public class TrunkServiceImpl
|
|||||||
TrunkMapper>
|
TrunkMapper>
|
||||||
implements TrunkService {
|
implements TrunkService {
|
||||||
|
|
||||||
|
@Value("${yo.fsagent.trunk-prefix.seq-name:TRUNK}")
|
||||||
|
private String seqName;
|
||||||
|
@Autowired
|
||||||
|
private SequenceMapper sequenceMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private TrunkStrategyMapper trunkStrategyMapper;
|
private TrunkStrategyMapper trunkStrategyMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private GatewayMapper gatewayMapper;
|
private GatewayMapper gatewayMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private CellphoneService cellphoneService;
|
private CellphoneService cellphoneService;
|
||||||
|
@Autowired
|
||||||
|
private GatewayService gatewayService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -70,8 +81,8 @@ public class TrunkServiceImpl
|
|||||||
Assert.state(gateway.getActive(),
|
Assert.state(gateway.getActive(),
|
||||||
() -> "Trunk [" + trunk.getPrefix() + "] gateway is not active");
|
() -> "Trunk [" + trunk.getPrefix() + "] gateway is not active");
|
||||||
|
|
||||||
return Pair.of(trunk, "sofia/gateway/GW" +
|
return Pair.of(trunk, "sofia/gateway/" +
|
||||||
StringUtils.leftPad(String.valueOf(gateway.getId()), 6, '0') +
|
gatewayService.genGatewayName(gateway) +
|
||||||
"/" + calledNumber(trunk, number));
|
"/" + calledNumber(trunk, number));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +111,30 @@ public class TrunkServiceImpl
|
|||||||
return addPrefix(trunk, calledNumber);
|
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) {
|
private String addPrefix(final Trunk trunk, String number) {
|
||||||
final String gatewayPrefix = trunk.getGatewayPrefix();
|
final String gatewayPrefix = trunk.getGatewayPrefix();
|
||||||
return StringUtils.isNotBlank(gatewayPrefix) ? gatewayPrefix + number : number;
|
return StringUtils.isNotBlank(gatewayPrefix) ? gatewayPrefix + number : number;
|
||||||
|
@ -12,20 +12,23 @@ import org.freeswitch.esl.client.transport.event.Event;
|
|||||||
* @author Donghuang
|
* @author Donghuang
|
||||||
* @date Jul 08, 2020 11:42:21
|
* @date Jul 08, 2020 11:42:21
|
||||||
*/
|
*/
|
||||||
public class ChannelNameUtils {
|
public class CallStrUtils {
|
||||||
public static final Pattern PATTERN_USER_AND_DOMAIN =
|
public static final Pattern PATTERN_USER_AND_DOMAIN =
|
||||||
Pattern.compile("^sofia/internal/([^@]+)@(.+)$");
|
Pattern.compile("^sofia/internal/([^@]+)@(.+)$");
|
||||||
public static final Pattern PATTERN_EXTERNAL_NUMBER =
|
public static final Pattern PATTERN_EXTERNAL_NUMBER =
|
||||||
Pattern.compile("^sofia/external/(\\w+)$");
|
Pattern.compile("^sofia/external/(\\w+)$");
|
||||||
|
|
||||||
|
public static final Pattern PATTERN_DIAL_TARGET =
|
||||||
|
Pattern.compile("^(\\d+)\\.(A|Q|(?:AG))$");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get channel info
|
* get channel info
|
||||||
*
|
*
|
||||||
* @param event event
|
* @param event event
|
||||||
* @return channel info
|
* @return channel info
|
||||||
*/
|
*/
|
||||||
public static ChannelInfo get(final Event event) {
|
public static ChannelInfo getChannelInfo(final Event event) {
|
||||||
return parse(event.getChannelName());
|
return parseChannelName(event.getChannelName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,7 +37,7 @@ public class ChannelNameUtils {
|
|||||||
* @param channelName channel name
|
* @param channelName channel name
|
||||||
* @return channel info
|
* @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);
|
Matcher m = PATTERN_USER_AND_DOMAIN.matcher(channelName);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
return new ChannelInfo(m.group(1), m.group(2), true);
|
return new ChannelInfo(m.group(1), m.group(2), true);
|
||||||
@ -49,6 +52,18 @@ public class ChannelNameUtils {
|
|||||||
"Invalid channel name: " + channelName);
|
"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
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
@ -58,4 +73,19 @@ public class ChannelNameUtils {
|
|||||||
private final String domain;
|
private final String domain;
|
||||||
private final boolean agent;
|
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"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<document type="freeswitch/xml">
|
<document type="freeswitch/xml">
|
||||||
<section name="result">
|
<section name="${section}" description="No valid content found">
|
||||||
<result status="not found" />
|
<result status="not found" />
|
||||||
</section>
|
</section>
|
||||||
</document>
|
</document>
|
||||||
|
@ -2,7 +2,7 @@ package com.pudonghot.yo.fsagent;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.pudonghot.yo.fsagent.util.ChannelNameUtils;
|
import com.pudonghot.yo.fsagent.util.CallStrUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Donghuang
|
* @author Donghuang
|
||||||
@ -13,7 +13,7 @@ public class ChannelNameUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParse() {
|
public void testParse() {
|
||||||
log.info("User and domain [{}]", ChannelNameUtils.parse("sofia/internal/700001@d1.wacai.info"));
|
log.info("User and domain [{}]", CallStrUtils.parseChannelName("sofia/internal/700001@d1.wacai.info"));
|
||||||
log.info("External number [{}]", ChannelNameUtils.parse("sofia/external/013764268709"));
|
log.info("External number [{}]", CallStrUtils.parseChannelName("sofia/external/013764268709"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ public class Sequence extends BaseDomain {
|
|||||||
@NotUpdate
|
@NotUpdate
|
||||||
private String name;
|
private String name;
|
||||||
@NotUpdate
|
@NotUpdate
|
||||||
|
private int length;
|
||||||
|
@NotUpdate
|
||||||
private Long initVal;
|
private Long initVal;
|
||||||
private Long step;
|
private Long step;
|
||||||
private Long currentVal;
|
private Long currentVal;
|
||||||
|
@ -28,8 +28,7 @@ public class AgentStatusController implements SessionAbility {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AgentEventService agentEventService;
|
private AgentEventService agentEventService;
|
||||||
|
|
||||||
@RequestMapping({"/", "/forcelogin"
|
@RequestMapping({"/", "/forcelogin"})
|
||||||
})
|
|
||||||
public RespAgentReady signIn(
|
public RespAgentReady signIn(
|
||||||
@PathVariable("account")
|
@PathVariable("account")
|
||||||
final String account) {
|
final String account) {
|
||||||
|
@ -60,6 +60,12 @@ export default (function() {
|
|||||||
route: 'agent.list',
|
route: 'agent.list',
|
||||||
icon: 'fa-user',
|
icon: 'fa-user',
|
||||||
text: '坐席管理'
|
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',
|
route: 'campaign.list',
|
||||||
icon: 'fa-fax',
|
icon: 'fa-fax',
|
||||||
text: '外呼活动管理'
|
text: '外呼活动管理'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
perm: 'PERM_VIEW_CAMPAIGN_LIST',
|
|
||||||
route: 'calling-list.list',
|
|
||||||
icon: 'fa fa-list-alt',
|
|
||||||
text: '拨打列表'
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
text: '公共',
|
text: '公共',
|
||||||
|
@ -97,25 +97,19 @@ Router.map(function() {
|
|||||||
this.route('edit', {path: '/:id/edit'});
|
this.route('edit', {path: '/:id/edit'});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('campaign-stat', function() {
|
|
||||||
this.route('list');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.route('trunk-attr', function() {
|
this.route('trunk-attr', function() {
|
||||||
this.route('list');
|
this.route('list');
|
||||||
this.route('create');
|
this.route('create');
|
||||||
this.route('edit', {path: '/:id/edit'});
|
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('call-detail-record', function() {
|
||||||
this.route('list');
|
this.route('list');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route('agent-status', function() {
|
||||||
|
this.route('list');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Router;
|
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 {
|
return {
|
||||||
active: true,
|
active: true,
|
||||||
initVal: 1,
|
initVal: 1,
|
||||||
|
length: 4,
|
||||||
step: 1
|
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
|
maximum: 36
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
length: {
|
||||||
|
presence: true,
|
||||||
|
numericality: {
|
||||||
|
onlyInteger: true,
|
||||||
|
greaterThan: 1,
|
||||||
|
lessThanOrEqualTo: 16
|
||||||
|
}
|
||||||
|
},
|
||||||
initVal: {
|
initVal: {
|
||||||
presence: true,
|
presence: true,
|
||||||
numericality: {
|
numericality: {
|
||||||
@ -26,14 +34,7 @@ export default Service.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createConstraints: {
|
updateConstraints: {
|
||||||
name: {
|
|
||||||
presence: true,
|
|
||||||
length: {
|
|
||||||
minimum: 1,
|
|
||||||
maximum: 36
|
|
||||||
}
|
|
||||||
},
|
|
||||||
step: {
|
step: {
|
||||||
presence: true,
|
presence: true,
|
||||||
numericality: {
|
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}}
|
{{delete-button model=it}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/has-perm}}
|
{{/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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{{#form-content}}
|
{{#form-content}}
|
||||||
<hr />
|
<hr />
|
||||||
{{form-input name='name' label='名称'}}
|
{{form-input name='name' label='名称'}}
|
||||||
|
{{form-input name='length' label='长度'}}
|
||||||
{{form-input name='initVal' label='初始值'}}
|
{{form-input name='initVal' label='初始值'}}
|
||||||
{{form-input name='step' label='递增值'}}
|
{{form-input name='step' label='递增值'}}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{{#form-content}}
|
{{#form-content}}
|
||||||
{{form-input type='hidden' name='id'}}
|
{{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='initVal' label='初始值' readonly=true}}
|
||||||
{{form-input name='step' label='递增值'}}
|
{{form-input name='step' label='递增值'}}
|
||||||
|
|
||||||
|
@ -19,6 +19,9 @@
|
|||||||
<th>
|
<th>
|
||||||
名称
|
名称
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
长度
|
||||||
|
</th>
|
||||||
<th>
|
<th>
|
||||||
当前值
|
当前值
|
||||||
</th>
|
</th>
|
||||||
@ -53,6 +56,9 @@
|
|||||||
<td>
|
<td>
|
||||||
{{it.name}}
|
{{it.name}}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{{it.length}}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{it.currentVal}}
|
{{it.currentVal}}
|
||||||
</td>
|
</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