m0_70156489 1 неделя назад
Родитель
Сommit
c7bf71523d

+ 223 - 16
application/admin/controller/Purchasecontent.php

@@ -3,6 +3,7 @@
 namespace app\admin\controller;
 
 use app\common\controller\Backend;
+use think\Cache;
 use think\Db;
 use think\exception\PDOException;
 use think\exception\ValidateException;
@@ -15,6 +16,15 @@ use Exception;
  */
 class Purchasecontent extends Backend
 {
+    /** 投递接收人可选范围:auth_group 根组 id(含其全部子组) */
+    protected const RECIPIENT_AUTH_GROUP_ROOT_ID = 10;
+
+    /** 表结构已就绪缓存键(避免每次请求重复 SHOW COLUMNS / 探表) */
+    protected const SCHEMA_CACHE_KEY = 'purchase_content_schema_v2';
+
+    /** 可选接收人列表缓存秒数 */
+    protected const RECIPIENT_LIST_CACHE_TTL = 300;
+
     /** @var \app\admin\model\Purchasecontent */
     protected $model = null;
 
@@ -26,7 +36,17 @@ class Purchasecontent extends Backend
     {
         parent::_initialize();
         $this->model = new \app\admin\model\Purchasecontent;
+        if (!Cache::get(self::SCHEMA_CACHE_KEY)) {
+            $this->ensurePurchaseContentSchemaOnce();
+        }
+    }
+
+    /** 首次访问时建表/迁移,完成后写入缓存 */
+    protected function ensurePurchaseContentSchemaOnce(): void
+    {
         $this->ensurePurchaseContentTables();
+        $this->ensurePurchaseContentDatetimeColumns();
+        Cache::set(self::SCHEMA_CACHE_KEY, 1);
     }
 
     protected function ensurePurchaseContentTables(): void
@@ -54,6 +74,68 @@ class Purchasecontent extends Backend
         }
     }
 
+    /** 旧版 int 时间戳列迁移为 datetime */
+    protected function ensurePurchaseContentDatetimeColumns(): void
+    {
+        try {
+            $this->migrateTableDatetimeColumn('purchase_content', 'createtime', '投递时间');
+            $this->migrateTableDatetimeColumn('purchase_content', 'updatetime', '更新时间');
+            $this->migrateTableDatetimeColumn('purchase_content_recipient', 'createtime', '创建时间');
+            $this->migrateTableDatetimeColumn('purchase_content_recipient', 'read_time', '首次阅读时间');
+        } catch (\Throwable $e) {
+        }
+    }
+
+    protected function migrateTableDatetimeColumn(string $table, string $column, string $comment): void
+    {
+        $rows = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
+        if (!is_array($rows) || !isset($rows[0]['Type'])) {
+            return;
+        }
+        $type = strtolower((string)$rows[0]['Type']);
+        if (strpos($type, 'int') === false) {
+            return;
+        }
+        $tmp = $column . '_dt';
+        Db::execute("ALTER TABLE `{$table}` ADD COLUMN `{$tmp}` datetime DEFAULT NULL COMMENT '{$comment}'");
+        Db::execute("UPDATE `{$table}` SET `{$tmp}` = IF(`{$column}` > 0, FROM_UNIXTIME(`{$column}`), NULL)");
+        Db::execute("ALTER TABLE `{$table}` DROP COLUMN `{$column}`");
+        Db::execute("ALTER TABLE `{$table}` CHANGE `{$tmp}` `{$column}` datetime DEFAULT NULL COMMENT '{$comment}'");
+    }
+
+    /**
+     * 投递/阅读时间展示:YYYY-MM-DD HH:mm:ss
+     *
+     * @param mixed $value
+     */
+    protected function formatPurchaseContentTime($value): string
+    {
+        if ($value === null || $value === '') {
+            return '';
+        }
+        if (is_numeric($value)) {
+            $ts = (int)$value;
+            if ($ts > 946684800) {
+                return date('Y-m-d H:i:s', $ts);
+            }
+
+            return '';
+        }
+        $s = trim((string)$value);
+        if ($s === '' || stripos($s, '0000-00-00') === 0) {
+            return '';
+        }
+        if (preg_match('/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/', $s, $m)) {
+            return $m[1];
+        }
+        if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $s, $m)) {
+            return $m[1] . ' 00:00:00';
+        }
+        $ts = strtotime($s);
+
+        return ($ts !== false && $ts > 0) ? date('Y-m-d H:i:s', $ts) : $s;
+    }
+
     /**
      * 选择后台用户(发送人 / 接收人)
      */
@@ -62,7 +144,15 @@ class Purchasecontent extends Backend
         $this->model = model('Admin');
         $this->selectpageFields = 'id,username,nickname';
         $this->searchFields = 'id,username,nickname';
+        $groupIds = $this->loadRecipientAuthGroupIds();
         $this->model->where('status', 'normal');
+        if ($groupIds !== []) {
+            $this->model->where('id', 'in', function ($query) use ($groupIds) {
+                $query->name('auth_group_access')->where('group_id', 'in', $groupIds)->field('uid');
+            });
+        } else {
+            $this->model->where('id', 0);
+        }
 
         return $this->selectpage();
     }
@@ -133,13 +223,13 @@ class Purchasecontent extends Backend
             $this->error('当前登录用户无效');
         }
 
-        $recipientIds = $this->parseIdList($params['recipient_ids'] ?? '');
+        $recipientIds = $this->parseRecipientIdList($params['recipient_ids'] ?? '');
         if ($recipientIds === []) {
             $this->error('请选择投递接收人');
         }
 
         $creatorId = (int)$this->auth->id;
-        $now = time();
+        $now = date('Y-m-d H:i:s');
 
         Db::startTrans();
         try {
@@ -195,7 +285,7 @@ class Purchasecontent extends Backend
             if ($recvRow && empty($recvRow['read_time'])) {
                 Db::table('purchase_content_recipient')
                     ->where('id', (int)$recvRow['id'])
-                    ->update(['read_time' => time()]);
+                    ->update(['read_time' => date('Y-m-d H:i:s')]);
             }
         }
 
@@ -220,7 +310,6 @@ class Purchasecontent extends Backend
         }
 
         $adminId = (int)$this->auth->id;
-        $isSuper = $this->auth->isSuperAdmin();
 
         Db::startTrans();
         try {
@@ -229,8 +318,8 @@ class Purchasecontent extends Backend
                 if (!$row) {
                     continue;
                 }
-                if (!$isSuper && (int)$row['creator_id'] !== $adminId) {
-                    throw new Exception('仅创建人或超级管理员可删除');
+                if ((int)$row['sender_id'] !== $adminId) {
+                    throw new Exception('仅可删除本人投递的公告');
                 }
                 Db::table('purchase_content_recipient')->where('content_id', $cid)->delete();
                 $row->delete();
@@ -353,14 +442,18 @@ class Purchasecontent extends Backend
             $creatorId = (int)($data['creator_id'] ?? 0);
             $senderId = (int)($data['sender_id'] ?? 0);
             $recv = $recvMap[$cid] ?? ['names' => [], 'count' => 0, 'is_receiver' => false, 'read_time' => null];
+            $isSender = ($senderId === $currentAdminId);
             $extra = [
                 'creator_name'    => $nameMap[$creatorId] ?? '',
                 'sender_name'     => $nameMap[$senderId] ?? '',
+                'sender_id'       => $senderId,
                 'recipient_names' => implode('、', $recv['names']),
                 'recipient_count' => $recv['count'],
                 'is_receiver'     => $recv['is_receiver'] ? 1 : 0,
+                'is_sender'       => $isSender ? 1 : 0,
                 'read_time'       => $recv['read_time'],
-                'can_delete'      => ($this->auth->isSuperAdmin() || $creatorId === $currentAdminId) ? 1 : 0,
+                'send_time_text'  => $this->formatPurchaseContentTime($data['createtime'] ?? ''),
+                'can_delete'      => $isSender ? 1 : 0,
             ];
             if (is_object($row)) {
                 foreach ($extra as $k => $v) {
@@ -455,9 +548,9 @@ class Purchasecontent extends Backend
             }
             if ($aid === $currentAdminId) {
                 $out[$cid]['is_receiver'] = true;
-                $rt = (int)($r['read_time'] ?? 0);
-                if ($rt > 0) {
-                    $out[$cid]['read_time'] = $rt;
+                $rt = $r['read_time'] ?? null;
+                if ($rt !== null && $rt !== '' && stripos((string)$rt, '0000-00-00') !== 0) {
+                    $out[$cid]['read_time'] = $this->formatPurchaseContentTime($rt);
                 }
             }
         }
@@ -490,15 +583,110 @@ class Purchasecontent extends Backend
     }
 
     /**
-     * 新增公告:可选接收人列表
+     * 接收人是否属于「供应商证」角色组(id=10)及其子组
+     */
+    protected function recipientAdminExists(int $adminId): bool
+    {
+        if ($adminId <= 0 || !$this->adminExists($adminId)) {
+            return false;
+        }
+        $groupIds = $this->loadRecipientAuthGroupIds();
+        if ($groupIds === []) {
+            return false;
+        }
+
+        return (bool)Db::name('auth_group_access')
+            ->where('uid', $adminId)
+            ->where('group_id', 'in', $groupIds)
+            ->count();
+    }
+
+    /**
+     * auth_group id=10 及其全部子组 id
+     *
+     * @return int[]
+     */
+    protected function loadRecipientAuthGroupIds(): array
+    {
+        static $memo = null;
+        if ($memo !== null) {
+            return $memo;
+        }
+        $cacheKey = 'purchase_content_group_ids_' . self::RECIPIENT_AUTH_GROUP_ROOT_ID;
+        $cached = Cache::get($cacheKey);
+        if (is_array($cached) && $cached !== []) {
+            $memo = $cached;
+
+            return $memo;
+        }
+
+        $rootId = self::RECIPIENT_AUTH_GROUP_ROOT_ID;
+        try {
+            $groups = Db::name('auth_group')->where('status', 'normal')->field('id,pid')->select();
+        } catch (\Throwable $e) {
+            return [$rootId];
+        }
+        if (!is_array($groups) || $groups === []) {
+            return [$rootId];
+        }
+        $groupList = [];
+        foreach ($groups as $g) {
+            if (is_array($g)) {
+                $groupList[] = $g;
+            }
+        }
+        if ($groupList === []) {
+            return [$rootId];
+        }
+        $tree = \fast\Tree::instance();
+        $tree->init($groupList);
+        $ids = $tree->getChildrenIds($rootId, true);
+        if (!is_array($ids) || $ids === []) {
+            $memo = [$rootId];
+
+            return $memo;
+        }
+
+        $memo = array_values(array_unique(array_filter(array_map('intval', $ids))));
+        Cache::set($cacheKey, $memo, self::RECIPIENT_LIST_CACHE_TTL);
+
+        return $memo;
+    }
+
+    /**
+     * 新增公告:可选接收人列表(仅角色组 id=10 及其子组下的用户)
      *
      * @return array<int, array{id:int, username:string, nickname:string, label:string}>
      */
     protected function loadNormalAdminList(): array
     {
+        $cacheKey = 'purchase_content_admin_list_v2_' . self::RECIPIENT_AUTH_GROUP_ROOT_ID;
+        $cached = Cache::get($cacheKey);
+        if (is_array($cached)) {
+            return $cached;
+        }
+
+        $groupIds = $this->loadRecipientAuthGroupIds();
+        if ($groupIds === []) {
+            return [];
+        }
+        try {
+            $adminIds = Db::name('auth_group_access')
+                ->where('group_id', 'in', $groupIds)
+                ->column('uid');
+        } catch (\Throwable $e) {
+            $adminIds = [];
+        }
+        $adminIds = is_array($adminIds)
+            ? array_values(array_unique(array_filter(array_map('intval', $adminIds))))
+            : [];
+        if ($adminIds === []) {
+            return [];
+        }
         try {
             $rows = Db::name('admin')
                 ->where('status', 'normal')
+                ->where('id', 'in', $adminIds)
                 ->field('id,username,nickname')
                 ->order('id', 'asc')
                 ->select();
@@ -522,18 +710,19 @@ class Purchasecontent extends Backend
             if ($nickname === '') {
                 $nickname = $username;
             }
-            $label = $nickname;
-            if ($username !== '' && $username !== $nickname) {
-                $label .= '(' . $username . ')';
+            if ($nickname === '') {
+                $nickname = '#' . $id;
             }
             $out[] = [
                 'id'       => $id,
                 'username' => $username,
                 'nickname' => $nickname,
-                'label'    => $label,
+                'label'    => $nickname,
             ];
         }
 
+        Cache::set($cacheKey, $out, self::RECIPIENT_LIST_CACHE_TTL);
+
         return $out;
     }
 
@@ -554,7 +743,25 @@ class Purchasecontent extends Backend
         $out = [];
         foreach ($parts as $p) {
             $id = (int)$p;
-            if ($id > 0 && $this->adminExists($id)) {
+            if ($id > 0) {
+                $out[$id] = $id;
+            }
+        }
+
+        return array_values($out);
+    }
+
+    /**
+     * 解析并校验接收人 id(须为角色组 id=10 及其子组下的正常用户)
+     *
+     * @param mixed $raw
+     * @return int[]
+     */
+    protected function parseRecipientIdList($raw): array
+    {
+        $out = [];
+        foreach ($this->parseIdList($raw) as $id) {
+            if ($this->recipientAdminExists($id)) {
                 $out[$id] = $id;
             }
         }

+ 1 - 1
application/admin/model/Purchasecontent.php

@@ -8,7 +8,7 @@ class Purchasecontent extends Model
 {
     protected $table = 'purchase_content';
 
-    protected $autoWriteTimestamp = 'int';
+    protected $autoWriteTimestamp = 'datetime';
 
     protected $createTime = 'createtime';
     protected $updateTime = 'updatetime';

+ 3 - 3
application/admin/view/purchasecontent/add.html

@@ -44,7 +44,7 @@
         white-space: nowrap;
     }
     .purchase-recipient-list {
-        max-height: 200px;
+        max-height: 240px;
         overflow-y: auto;
         padding: 6px 10px 8px;
     }
@@ -70,7 +70,7 @@
 </style>
 <form id="add-form" class="form-horizontal purchase-content-add-form" role="form" data-toggle="validator" method="POST" action="">
     <div class="form-group">
-        <label class="control-label col-xs-12 col-sm-2">发送人:</label>
+        <label class="control-label col-xs-12 col-sm-2">投递人:</label>
         <div class="col-xs-12 col-sm-9">
             <p class="form-control-static" style="padding-top:7px;margin:0;">{$admin.nickname|default=''|htmlentities}</p>
         </div>
@@ -81,7 +81,7 @@
             <input type="hidden" name="row[recipient_ids]" id="c-recipient_ids" value="" data-rule="required">
             <div class="purchase-recipient-picker" id="purchase-recipient-picker">
                 <div class="purchase-recipient-toolbar">
-                    <input type="text" class="form-control input-sm purchase-recipient-search" placeholder="搜索昵称或账号" autocomplete="off">
+                    <input type="text" class="form-control input-sm purchase-recipient-search" placeholder="搜索名称" autocomplete="off">
                     <label class="purchase-recipient-check-all">
                         <input type="checkbox" class="purchase-recipient-check-all-input"> 全选
                     </label>

+ 37 - 10
application/admin/view/purchasecontent/detail.html

@@ -1,7 +1,6 @@
 <style>
     body.is-dialog .purchase-content-detail {
         padding: 16px 22px 20px;
-        max-width: 720px;
     }
     .purchase-content-detail .meta-row {
         margin-bottom: 10px;
@@ -9,18 +8,44 @@
         color: #666;
         line-height: 1.7;
     }
+    .purchase-content-detail .meta-row-top {
+        display: flex;
+        align-items: baseline;
+        justify-content: space-between;
+        flex-wrap: wrap;
+        gap: 8px 24px;
+    }
+    .purchase-content-detail .meta-row-top .meta-time {
+        margin-left: auto;
+        text-align: right;
+        white-space: nowrap;
+    }
     .purchase-content-detail .meta-row strong {
         color: #333;
         font-weight: 600;
         margin-right: 4px;
     }
-    .purchase-content-detail .subject-line {
-        font-size: 18px;
-        font-weight: 600;
-        color: #222;
+    .purchase-content-detail .subject-row {
         margin: 12px 0 16px;
         padding-bottom: 10px;
         border-bottom: 1px solid #eee;
+        font-size: 14px;
+        color: #222;
+    }
+    .purchase-content-detail .subject-row strong {
+        color: #333;
+        font-weight: 600;
+        margin-right: 4px;
+    }
+    .purchase-content-detail .content-label {
+        margin-bottom: 8px;
+        font-size: 13px;
+        color: #666;
+    }
+    .purchase-content-detail .content-label strong {
+        color: #333;
+        font-weight: 600;
+        margin-right: 4px;
     }
     .purchase-content-detail .content-body {
         white-space: pre-wrap;
@@ -32,10 +57,12 @@
     }
 </style>
 <div class="purchase-content-detail">
-    <div class="meta-row"><strong>创建人:</strong>{$row.creator_name|default=''|htmlentities}</div>
-    <div class="meta-row"><strong>发送人:</strong>{$row.sender_name|default=''|htmlentities}</div>
-    <div class="meta-row"><strong>创建时间:</strong>{$row.createtime|datetime}</div>
-    <div class="meta-row"><strong>接收人:</strong>{$row.recipient_names|default=''|htmlentities}({$row.recipient_count|default='0'}人)</div>
-    <div class="subject-line">{$row.subject|default=''|htmlentities}</div>
+    <div class="meta-row meta-row-top">
+        <div class="meta-sender"><strong>投递人:</strong>{$row.sender_name|default=''|htmlentities}</div>
+        <div class="meta-time"><strong>投递时间:</strong>{$row.send_time_text|default=''|htmlentities}</div>
+    </div>
+    <div class="meta-row"><strong>投递给:</strong>{$row.recipient_names|default=''|htmlentities}({$row.recipient_count|default='0'}人)</div>
+    <div class="subject-row"><strong>主题:</strong>{$row.subject|default=''|htmlentities}</div>
+    <div class="content-label"><strong>内容:</strong></div>
     <div class="content-body">{$row.content|default=''|htmlentities}</div>
 </div>

+ 5 - 5
application/extra/purchase_content_install.sql

@@ -4,11 +4,11 @@
 CREATE TABLE IF NOT EXISTS `purchase_content` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `creator_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '创建人',
-  `sender_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '发送人',
+  `sender_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '投递人',
   `subject` varchar(255) NOT NULL DEFAULT '' COMMENT '主题',
   `content` mediumtext COMMENT '内容',
-  `createtime` int(10) unsigned DEFAULT NULL,
-  `updatetime` int(10) unsigned DEFAULT NULL,
+  `createtime` datetime DEFAULT NULL COMMENT '投递时间',
+  `updatetime` datetime DEFAULT NULL COMMENT '更新时间',
   PRIMARY KEY (`id`),
   KEY `idx_creator` (`creator_id`),
   KEY `idx_sender` (`sender_id`),
@@ -19,8 +19,8 @@ CREATE TABLE IF NOT EXISTS `purchase_content_recipient` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `content_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '公告ID',
   `admin_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '接收人',
-  `read_time` int(10) unsigned DEFAULT NULL COMMENT '首次阅读时间',
-  `createtime` int(10) unsigned DEFAULT NULL,
+  `read_time` datetime DEFAULT NULL COMMENT '首次阅读时间',
+  `createtime` datetime DEFAULT NULL COMMENT '创建时间',
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_content_admin` (`content_id`,`admin_id`),
   KEY `idx_admin` (`admin_id`)

+ 58 - 18
public/assets/js/backend/purchasecontent.js

@@ -1,7 +1,6 @@
 define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
 
-    var ADD_AREA = ['920px', '720px'];
-    var DETAIL_AREA = ['760px', '560px'];
+    var DIALOG_AREA = ['920px', '800px'];
 
     var Controller = {
         index: function () {
@@ -16,6 +15,24 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
 
             var table = $('#table');
 
+            function syncRecipientDeleteCheckbox() {
+                var data = table.bootstrapTable('getData') || [];
+                table.closest('.bootstrap-table').find('tbody tr').each(function () {
+                    var $tr = $(this);
+                    var idx = $tr.data('index');
+                    if (idx === undefined || idx === null) {
+                        return;
+                    }
+                    var row = data[idx];
+                    if (row && parseInt(row.can_delete, 10) === 1) {
+                        return;
+                    }
+                    $tr.find('input[name="btSelectItem"]').remove();
+                    $tr.find('td.bs-checkbox').empty();
+                    $tr.removeClass('selected');
+                });
+            }
+
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
                 pk: 'id',
@@ -25,43 +42,56 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 columns: [
                     [
                         {checkbox: true},
-                        {field: 'id', title: __('Id'), width: 60, operate: false},
-                        {field: 'subject', title: '主题', operate: 'LIKE', align: 'left'},
-                        {field: 'sender_name', title: '发送人', operate: false, width: 100, align: 'center'},
-                        {field: 'creator_name', title: '创建人', operate: false, width: 100, align: 'center'},
+                        {field: 'id', title: 'ID', width: 60, operate: false},
+                        {field: 'sender_name', title: '投递人', operate: false, width: 100, align: 'center'},
                         {
                             field: 'recipient_names',
-                            title: '接收人',
+                            title: '投递给',
                             operate: false,
                             align: 'left',
+                            class: 'autocontent',
                             formatter: function (value, row) {
                                 var cnt = parseInt(row.recipient_count, 10) || 0;
                                 var text = value != null && value !== '' ? String(value) : '';
-                                if (cnt > 0) {
+                                if (cnt > 0 && text !== '') {
                                     return text + ' <span class="text-muted">(' + cnt + '人)</span>';
                                 }
                                 return text;
                             }
                         },
+                        {field: 'subject', title: '主题', operate: 'LIKE', align: 'left'},
+                        {
+                            field: 'content',
+                            title: '内容',
+                            operate: 'LIKE',
+                            align: 'left',
+                            class: 'autocontent',
+                            formatter: Table.api.formatter.content
+                        },
                         {
                             field: 'createtime',
-                            title: '创建时间',
+                            title: '投递时间',
                             operate: 'RANGE',
                             addclass: 'datetimerange',
-                            width: 158,
+                            width: 165,
                             align: 'center',
-                            formatter: Table.api.formatter.datetime
+                            formatter: function (value, row) {
+                                if (row.send_time_text) {
+                                    return row.send_time_text;
+                                }
+                                return Table.api.formatter.datetime.call(this, value, row);
+                            }
                         },
                         {
-                            field: 'is_receiver',
-                            title: '我的',
+                            field: 'is_sender',
+                            title: '状态',
                             operate: false,
                             width: 72,
                             align: 'center',
                             formatter: function (value) {
                                 return parseInt(value, 10) === 1
-                                    ? '<span class="text-success">收件</span>'
-                                    : '<span class="text-muted">发件</span>';
+                                    ? '<span class="text-primary">发件</span>'
+                                    : '<span class="text-success">收件</span>';
                             }
                         },
                         {
@@ -80,7 +110,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                                     classname: 'btn btn-xs btn-info btn-dialog',
                                     icon: 'fa fa-eye',
                                     url: 'purchasecontent/detail',
-                                    extend: 'data-area=\'["760px","560px"]\''
+                                    extend: 'data-area=\'["920px","800px"]\''
                                 },
                                 {
                                     name: 'del',
@@ -101,10 +131,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
 
             Table.api.bindevent(table);
 
-            $('#toolbar .btn-add').data('area', ADD_AREA);
+            table.on('check-all.bs.table', function () {
+                var data = table.bootstrapTable('getData') || [];
+                $.each(data, function (i, row) {
+                    if (parseInt(row.can_delete, 10) !== 1) {
+                        table.bootstrapTable('uncheck', i);
+                    }
+                });
+            });
+
+            $('#toolbar .btn-add').data('area', DIALOG_AREA);
             table.on('post-body.bs.table', function () {
+                syncRecipientDeleteCheckbox();
                 table.closest('.bootstrap-table').find('.btn-dialog').each(function () {
-                    $(this).data('area', DETAIL_AREA);
+                    $(this).data('area', DIALOG_AREA);
                 });
             });
         },