修复号码数量列的数据统计方式

This commit is contained in:
root
2026-07-08 01:07:08 +08:00
parent 92b4faf61f
commit 76692e0021
16 changed files with 497 additions and 29 deletions
@@ -27,6 +27,7 @@ CREATE TABLE `fa_split_ticket` (
`number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '号码数量含离线封号(同步)',
`number_offline_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '离线号码数(同步)',
`number_banned_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '封号号码数(同步)',
`active_number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '本地开启号码数(status=normal)',
`online_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '在线人数(同步)',
`sync_status` enum('success','error','pending') NOT NULL DEFAULT 'pending' COMMENT '同步状态',
`sync_time` bigint(16) DEFAULT NULL COMMENT '最近同步时间',
@@ -0,0 +1,21 @@
-- 工单表:本地开启号码数(status=normal),列表直接读字段,避免子查询
SET NAMES utf8mb4;
ALTER TABLE `fa_split_ticket`
ADD COLUMN `active_number_count` int(10) unsigned NOT NULL DEFAULT 0
COMMENT '本地开启号码数(status=normal)' AFTER `number_banned_count`;
-- 历史数据回填
UPDATE `fa_split_ticket` t
SET t.`active_number_count` = (
SELECT COUNT(*)
FROM `fa_split_number` n
WHERE n.`admin_id` = t.`admin_id`
AND n.`split_link_id` = t.`split_link_id`
AND n.`ticket_name` = t.`ticket_name`
AND n.`status` = 'normal'
);
-- 加速按工单统计 status=normal 数量
ALTER TABLE `fa_split_number`
ADD KEY `idx_ticket_active` (`admin_id`, `split_link_id`, `ticket_name`(50), `status`);
@@ -7,6 +7,7 @@ namespace app\admin\controller\split;
use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Number as NumberModel;
use app\common\controller\Backend;
use app\common\service\SplitTicketActiveNumberCountService;
use think\Db;
use think\Exception;
use think\exception\DbException;
@@ -225,6 +226,7 @@ class Number extends Backend
$this->error($e->getMessage());
}
if ($count > 0) {
(new SplitTicketActiveNumberCountService())->refreshForNumberIds(explode(',', (string) $ids));
$this->success();
}
$this->error(__('No rows were updated'));
@@ -331,6 +333,12 @@ class Number extends Backend
$this->error($e->getMessage());
}
(new SplitTicketActiveNumberCountService())->refreshForTicketKeys(
(int) ($baseRow['admin_id'] ?? $adminId),
$splitLinkId,
(string) ($baseRow['ticket_name'] ?? '')
);
$msg = __('Inserted %d number(s)', $inserted);
if ($skipped > 0) {
$msg .= '' . __('Skipped %d duplicate(s)', $skipped);
@@ -378,6 +386,7 @@ class Number extends Backend
}
$result = false;
$before = $row->getData();
Db::startTrans();
try {
if ($this->modelValidate) {
@@ -397,9 +406,50 @@ class Number extends Backend
if ($result === false) {
$this->error(__('No rows were updated'));
}
(new SplitTicketActiveNumberCountService())->refreshAfterNumberEdit($before, $row->getData());
$this->success();
}
/**
* 删除号码后回写工单本地开启号码数
*
* @param string|null $ids
*/
public function del($ids = null)
{
if (false === $this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$ids = $ids ?: $this->request->post('ids');
if (empty($ids)) {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
$pk = $this->model->getPk();
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
$list = $this->model->where($pk, 'in', $ids)->select();
$count = 0;
$countService = new SplitTicketActiveNumberCountService();
Db::startTrans();
try {
foreach ($list as $item) {
$count += $item->delete();
}
Db::commit();
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$countService->refreshFromNumberRows($list);
$this->success();
}
$this->error(__('No rows were deleted'));
}
/**
* 批量更新号码状态与手动管理
*/
@@ -444,6 +494,7 @@ class Number extends Backend
if ($count === false || $count === 0) {
$this->error(__('No rows were updated'));
}
(new SplitTicketActiveNumberCountService())->refreshForNumberIds($ids);
$this->success(__('Batch update success'));
}
@@ -496,6 +547,8 @@ class Number extends Backend
}
$count = 0;
$countService = new SplitTicketActiveNumberCountService();
$keyRows = (clone $query)->field('admin_id,split_link_id,ticket_name')->select();
Db::startTrans();
try {
if ($action === 'delete') {
@@ -523,6 +576,7 @@ class Number extends Backend
$this->error(__('No matching numbers found'));
}
$countService->refreshFromNumberRows($keyRows);
$this->success(sprintf(__('Batch operate success'), $count));
}
@@ -268,6 +268,7 @@ class Ticket extends Backend
$params['inbound_count'],
$params['speed_per_hour'],
$params['number_count'],
$params['active_number_count'],
$params['number_offline_count'],
$params['number_banned_count'],
$params['online_count'],
@@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace app\common\service;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
/**
* 工单本地开启号码数(active_number_count)物化字段维护
*
* 统计口径:fa_split_number 中 admin_id + split_link_id + ticket_name 匹配且 status=normal
*/
class SplitTicketActiveNumberCountService
{
/**
* 按工单关联键统计并回写 active_number_count
*/
public function refreshForTicketKeys(int $adminId, int $linkId, string $ticketName): void
{
$ticketName = trim($ticketName);
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
return;
}
$count = (int) Number::where('admin_id', $adminId)
->where('split_link_id', $linkId)
->where('ticket_name', $ticketName)
->where('status', 'normal')
->count();
Ticket::where('admin_id', $adminId)
->where('split_link_id', $linkId)
->where('ticket_name', $ticketName)
->update([
'active_number_count' => $count,
'updatetime' => time(),
]);
}
/**
* 按工单模型回写
*/
public function refreshForTicket(Ticket $ticket): void
{
$this->refreshForTicketKeys(
(int) $ticket['admin_id'],
(int) $ticket['split_link_id'],
(string) $ticket['ticket_name']
);
}
/**
* 批量号码 ID:去重后刷新涉及工单
*
* @param array<int|string> $ids
*/
public function refreshForNumberIds(array $ids): void
{
$ids = array_values(array_filter(array_map('intval', $ids)));
if ($ids === []) {
return;
}
$rows = Number::where('id', 'in', $ids)
->field('admin_id,split_link_id,ticket_name')
->select();
$this->refreshFromNumberRows($rows);
}
/**
* 编辑单条号码:关联键变更时刷新旧工单与新工单
*
* @param array<string, mixed> $before
* @param array<string, mixed> $after
*/
public function refreshAfterNumberEdit(array $before, array $after): void
{
$keys = [];
$beforeKey = $this->buildTicketKeyFromRow($before);
if ($beforeKey !== null) {
$keys[] = $beforeKey;
}
$afterKey = $this->buildTicketKeyFromRow($after);
if ($afterKey !== null) {
$keys[] = $afterKey;
}
$this->refreshForTicketKeyList($keys);
}
/**
* 批量工单关联键去重后刷新
*
* @param array<int, array{0:int,1:int,2:string}> $keys
*/
public function refreshForTicketKeyList(array $keys): void
{
$seen = [];
foreach ($keys as $key) {
if (!is_array($key) || count($key) < 3) {
continue;
}
$adminId = (int) $key[0];
$linkId = (int) $key[1];
$ticketName = trim((string) $key[2]);
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
continue;
}
$hash = $adminId . ':' . $linkId . ':' . $ticketName;
if (isset($seen[$hash])) {
continue;
}
$seen[$hash] = true;
$this->refreshForTicketKeys($adminId, $linkId, $ticketName);
}
}
/**
* @param iterable<int, array<string, mixed>|Number> $rows
*/
public function refreshFromNumberRows(iterable $rows): void
{
$keys = [];
foreach ($rows as $row) {
$data = $row instanceof Number ? $row->getData() : (array) $row;
$key = $this->buildTicketKeyFromRow($data);
if ($key !== null) {
$keys[] = $key;
}
}
$this->refreshForTicketKeyList($keys);
}
/**
* @param array<string, mixed> $row
* @return array{0:int,1:int,2:string}|null
*/
private function buildTicketKeyFromRow(array $row): ?array
{
$adminId = (int) ($row['admin_id'] ?? 0);
$linkId = (int) ($row['split_link_id'] ?? 0);
$ticketName = trim((string) ($row['ticket_name'] ?? ''));
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
return null;
}
return [$adminId, $linkId, $ticketName];
}
}
@@ -76,6 +76,8 @@ class SplitTicketRuleService
'status' => 'hidden',
'updatetime' => time(),
]);
(new SplitTicketActiveNumberCountService())->refreshForTicket($ticket);
}
/**
@@ -185,6 +187,8 @@ class SplitTicketRuleService
Number::where('id', (int) $number['id'])->update($update);
}
(new SplitTicketActiveNumberCountService())->refreshForTicket($ticket);
}
/**
@@ -27,6 +27,7 @@ CREATE TABLE `fa_split_ticket` (
`number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '号码数量含离线封号(同步)',
`number_offline_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '离线号码数(同步)',
`number_banned_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '封号号码数(同步)',
`active_number_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '本地开启号码数(status=normal)',
`online_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '在线人数(同步)',
`sync_status` enum('success','error','pending') NOT NULL DEFAULT 'pending' COMMENT '同步状态',
`sync_time` bigint(16) DEFAULT NULL COMMENT '最近同步时间',
@@ -0,0 +1,21 @@
-- 工单表:本地开启号码数(status=normal),列表直接读字段,避免子查询
SET NAMES utf8mb4;
ALTER TABLE `fa_split_ticket`
ADD COLUMN `active_number_count` int(10) unsigned NOT NULL DEFAULT 0
COMMENT '本地开启号码数(status=normal)' AFTER `number_banned_count`;
-- 历史数据回填
UPDATE `fa_split_ticket` t
SET t.`active_number_count` = (
SELECT COUNT(*)
FROM `fa_split_number` n
WHERE n.`admin_id` = t.`admin_id`
AND n.`split_link_id` = t.`split_link_id`
AND n.`ticket_name` = t.`ticket_name`
AND n.`status` = 'normal'
);
-- 加速按工单统计 status=normal 数量
ALTER TABLE `fa_split_number`
ADD KEY `idx_ticket_active` (`admin_id`, `split_link_id`, `ticket_name`(50), `status`);
@@ -18,6 +18,7 @@ use think\console\Output;
* 用法:
* php think split:sync-tickets
* php think split:sync-tickets --ticket=12
* php think split:sync-tickets --ticket=12 --mode=auto
*/
class SplitSyncTickets extends Command
{
@@ -25,21 +26,24 @@ class SplitSyncTickets extends Command
{
$this->setName('split:sync-tickets')
->addOption('ticket', 't', Option::VALUE_OPTIONAL, '指定工单 ID(强制同步,忽略周期)', '')
->addOption('mode', 'm', Option::VALUE_OPTIONAL, '同步方式: auto=定时自动, manual=手动触发', 'manual')
->setDescription('同步分流工单云控数据');
}
protected function execute(Input $input, Output $output): void
{
set_time_limit(0);
$syncMode = $this->resolveSyncMode($input);
SplitTicketSyncLogger::log('cli', 'command start', [
'ticketOption' => trim((string) $input->getOption('ticket')),
'syncMode' => $syncMode,
'appDebug' => SplitTicketSyncLogger::isEnabled(),
]);
$service = new SplitTicketSyncService();
$ticketId = trim((string) $input->getOption('ticket'));
if ($ticketId !== '' && ctype_digit($ticketId)) {
$result = $service->syncOne((int) $ticketId, true);
$result = $service->syncOne((int) $ticketId, true, false, $syncMode);
if (!empty($result['skipped'])) {
$output->writeln('<comment>跳过: ' . ($result['message'] ?? '') . '</comment>');
return;
@@ -71,4 +75,14 @@ class SplitSyncTickets extends Command
$output->writeln('<comment>详细调试日志已写入 runtime/log/split_sync.log</comment>');
}
}
/**
* 解析 CLI 同步方式,供 sync_success_mode 落库
*/
private function resolveSyncMode(Input $input): string
{
$mode = strtolower(trim((string) $input->getOption('mode')));
return in_array($mode, ['auto', 'manual'], true) ? $mode : 'manual';
}
}
@@ -7,6 +7,7 @@ namespace app\admin\controller\split;
use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Number as NumberModel;
use app\common\controller\Backend;
use app\common\service\SplitTicketActiveNumberCountService;
use think\Db;
use think\Exception;
use think\exception\DbException;
@@ -225,6 +226,7 @@ class Number extends Backend
$this->error($e->getMessage());
}
if ($count > 0) {
(new SplitTicketActiveNumberCountService())->refreshForNumberIds(explode(',', (string) $ids));
$this->success();
}
$this->error(__('No rows were updated'));
@@ -331,6 +333,12 @@ class Number extends Backend
$this->error($e->getMessage());
}
(new SplitTicketActiveNumberCountService())->refreshForTicketKeys(
(int) ($baseRow['admin_id'] ?? $adminId),
$splitLinkId,
(string) ($baseRow['ticket_name'] ?? '')
);
$msg = __('Inserted %d number(s)', $inserted);
if ($skipped > 0) {
$msg .= '' . __('Skipped %d duplicate(s)', $skipped);
@@ -378,6 +386,7 @@ class Number extends Backend
}
$result = false;
$before = $row->getData();
Db::startTrans();
try {
if ($this->modelValidate) {
@@ -397,9 +406,50 @@ class Number extends Backend
if ($result === false) {
$this->error(__('No rows were updated'));
}
(new SplitTicketActiveNumberCountService())->refreshAfterNumberEdit($before, $row->getData());
$this->success();
}
/**
* 删除号码后回写工单本地开启号码数
*
* @param string|null $ids
*/
public function del($ids = null)
{
if (false === $this->request->isPost()) {
$this->error(__('Invalid parameters'));
}
$ids = $ids ?: $this->request->post('ids');
if (empty($ids)) {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
$pk = $this->model->getPk();
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->model->where($this->dataLimitField, 'in', $adminIds);
}
$list = $this->model->where($pk, 'in', $ids)->select();
$count = 0;
$countService = new SplitTicketActiveNumberCountService();
Db::startTrans();
try {
foreach ($list as $item) {
$count += $item->delete();
}
Db::commit();
} catch (PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($count) {
$countService->refreshFromNumberRows($list);
$this->success();
}
$this->error(__('No rows were deleted'));
}
/**
* 批量更新号码状态与手动管理
*/
@@ -444,6 +494,7 @@ class Number extends Backend
if ($count === false || $count === 0) {
$this->error(__('No rows were updated'));
}
(new SplitTicketActiveNumberCountService())->refreshForNumberIds($ids);
$this->success(__('Batch update success'));
}
@@ -496,6 +547,8 @@ class Number extends Backend
}
$count = 0;
$countService = new SplitTicketActiveNumberCountService();
$keyRows = (clone $query)->field('admin_id,split_link_id,ticket_name')->select();
Db::startTrans();
try {
if ($action === 'delete') {
@@ -523,6 +576,7 @@ class Number extends Backend
$this->error(__('No matching numbers found'));
}
$countService->refreshFromNumberRows($keyRows);
$this->success(sprintf(__('Batch operate success'), $count));
}
@@ -268,6 +268,7 @@ class Ticket extends Backend
$params['inbound_count'],
$params['speed_per_hour'],
$params['number_count'],
$params['active_number_count'],
$params['number_offline_count'],
$params['number_banned_count'],
$params['online_count'],
@@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace app\common\service;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
/**
* 工单本地开启号码数(active_number_count)物化字段维护
*
* 统计口径:fa_split_number admin_id + split_link_id + ticket_name 匹配且 status=normal
*/
class SplitTicketActiveNumberCountService
{
/**
* 按工单关联键统计并回写 active_number_count
*/
public function refreshForTicketKeys(int $adminId, int $linkId, string $ticketName): void
{
$ticketName = trim($ticketName);
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
return;
}
$count = (int) Number::where('admin_id', $adminId)
->where('split_link_id', $linkId)
->where('ticket_name', $ticketName)
->where('status', 'normal')
->count();
Ticket::where('admin_id', $adminId)
->where('split_link_id', $linkId)
->where('ticket_name', $ticketName)
->update([
'active_number_count' => $count,
'updatetime' => time(),
]);
}
/**
* 按工单模型回写
*/
public function refreshForTicket(Ticket $ticket): void
{
$this->refreshForTicketKeys(
(int) $ticket['admin_id'],
(int) $ticket['split_link_id'],
(string) $ticket['ticket_name']
);
}
/**
* 批量号码 ID:去重后刷新涉及工单
*
* @param array<int|string> $ids
*/
public function refreshForNumberIds(array $ids): void
{
$ids = array_values(array_filter(array_map('intval', $ids)));
if ($ids === []) {
return;
}
$rows = Number::where('id', 'in', $ids)
->field('admin_id,split_link_id,ticket_name')
->select();
$this->refreshFromNumberRows($rows);
}
/**
* 编辑单条号码:关联键变更时刷新旧工单与新工单
*
* @param array<string, mixed> $before
* @param array<string, mixed> $after
*/
public function refreshAfterNumberEdit(array $before, array $after): void
{
$keys = [];
$beforeKey = $this->buildTicketKeyFromRow($before);
if ($beforeKey !== null) {
$keys[] = $beforeKey;
}
$afterKey = $this->buildTicketKeyFromRow($after);
if ($afterKey !== null) {
$keys[] = $afterKey;
}
$this->refreshForTicketKeyList($keys);
}
/**
* 批量工单关联键去重后刷新
*
* @param array<int, array{0:int,1:int,2:string}> $keys
*/
public function refreshForTicketKeyList(array $keys): void
{
$seen = [];
foreach ($keys as $key) {
if (!is_array($key) || count($key) < 3) {
continue;
}
$adminId = (int) $key[0];
$linkId = (int) $key[1];
$ticketName = trim((string) $key[2]);
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
continue;
}
$hash = $adminId . ':' . $linkId . ':' . $ticketName;
if (isset($seen[$hash])) {
continue;
}
$seen[$hash] = true;
$this->refreshForTicketKeys($adminId, $linkId, $ticketName);
}
}
/**
* @param iterable<int, array<string, mixed>|Number> $rows
*/
public function refreshFromNumberRows(iterable $rows): void
{
$keys = [];
foreach ($rows as $row) {
$data = $row instanceof Number ? $row->getData() : (array) $row;
$key = $this->buildTicketKeyFromRow($data);
if ($key !== null) {
$keys[] = $key;
}
}
$this->refreshForTicketKeyList($keys);
}
/**
* @param array<string, mixed> $row
* @return array{0:int,1:int,2:string}|null
*/
private function buildTicketKeyFromRow(array $row): ?array
{
$adminId = (int) ($row['admin_id'] ?? 0);
$linkId = (int) ($row['split_link_id'] ?? 0);
$ticketName = trim((string) ($row['ticket_name'] ?? ''));
if ($adminId <= 0 || $linkId <= 0 || $ticketName === '') {
return null;
}
return [$adminId, $linkId, $ticketName];
}
}
@@ -76,6 +76,8 @@ class SplitTicketRuleService
'status' => 'hidden',
'updatetime' => time(),
]);
(new SplitTicketActiveNumberCountService())->refreshForTicket($ticket);
}
/**
@@ -185,6 +187,8 @@ class SplitTicketRuleService
Number::where('id', (int) $number['id'])->update($update);
}
(new SplitTicketActiveNumberCountService())->refreshForTicket($ticket);
}
/**
@@ -378,11 +378,13 @@ class SplitTicketSyncDispatchService
$logDir = $this->resolveLogDir();
$logFile = $logDir . 'cli_' . $ticketId . '_' . date('YmdHis') . '.log';
$syncMode = $this->normalizeSyncModeForCli($source);
$inner = sprintf(
'%s %s split:sync-tickets --ticket=%d >> %s 2>&1',
'%s %s split:sync-tickets --ticket=%d --mode=%s >> %s 2>&1',
escapeshellarg($php),
escapeshellarg($think),
$ticketId,
$syncMode,
escapeshellarg($logFile)
);
@@ -408,11 +410,13 @@ class SplitTicketSyncDispatchService
continue;
}
$logFile = $logDir . 'cli_' . $ticketId . '_' . date('YmdHis') . '.log';
$syncMode = $this->normalizeSyncModeForCli($source);
$parts[] = sprintf(
'%s %s split:sync-tickets --ticket=%d >> %s 2>&1',
'%s %s split:sync-tickets --ticket=%d --mode=%s >> %s 2>&1',
escapeshellarg($php),
escapeshellarg($think),
$ticketId,
$syncMode,
escapeshellarg($logFile)
);
}
@@ -557,4 +561,12 @@ class SplitTicketSyncDispatchService
{
return function_exists('shell_exec') && !in_array('shell_exec', $this->disabledFunctions(), true);
}
/**
* 投递 CLI 时携带 --mode,供 sync_success_mode 区分自动/手动
*/
private function normalizeSyncModeForCli(string $source): string
{
return in_array($source, ['auto', 'manual'], true) ? $source : 'manual';
}
}
@@ -88,10 +88,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
formatter: Controller.api.formatter.speedPerHour
},
{
field: 'number_count',
field: 'active_number_count',
title: __('Number_count'),
operate: false,
formatter: Controller.api.formatter.numberCount
operate: false
},
{
field: 'sync_display_text',
@@ -486,16 +485,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
return num.toFixed(2);
},
numberCount: function (value, row) {
var total = parseInt(value, 10) || 0;
var offline = parseInt(row.number_offline_count, 10) || 0;
var banned = parseInt(row.number_banned_count, 10) || 0;
var tip = __('Number_count_detail').replace('%s', offline).replace('%s', banned);
if (offline > 0 || banned > 0) {
return '<span data-toggle="tooltip" title="' + Fast.api.escape(tip) + '">' + total + '</span>';
}
return String(total);
},
syncDisplay: function (value, row) {
var rowId = parseInt(row.id, 10);
if (Controller.api.syncingTicketIds.indexOf(rowId) !== -1) {
+2 -13
View File
@@ -88,10 +88,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
formatter: Controller.api.formatter.speedPerHour
},
{
field: 'number_count',
field: 'active_number_count',
title: __('Number_count'),
operate: false,
formatter: Controller.api.formatter.numberCount
operate: false
},
{
field: 'sync_display_text',
@@ -486,16 +485,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
return num.toFixed(2);
},
numberCount: function (value, row) {
var total = parseInt(value, 10) || 0;
var offline = parseInt(row.number_offline_count, 10) || 0;
var banned = parseInt(row.number_banned_count, 10) || 0;
var tip = __('Number_count_detail').replace('%s', offline).replace('%s', banned);
if (offline > 0 || banned > 0) {
return '<span data-toggle="tooltip" title="' + Fast.api.escape(tip) + '">' + total + '</span>';
}
return String(total);
},
syncDisplay: function (value, row) {
var rowId = parseInt(row.id, 10);
if (Controller.api.syncingTicketIds.indexOf(rowId) !== -1) {