更新
This commit is contained in:
@@ -52,6 +52,19 @@ class WecomPromotionController extends BaseAdminController
|
||||
)));
|
||||
}
|
||||
|
||||
public function batchSetOperators()
|
||||
{
|
||||
if (!$this->hasPagePermission()) {
|
||||
return $this->fail('权限不足');
|
||||
}
|
||||
|
||||
return $this->run(fn () => $this->success('方案操作人已批量更新', WecomPromotionLogic::batchSetOperators(
|
||||
$this->request->post(),
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
)));
|
||||
}
|
||||
|
||||
public function deletePool()
|
||||
{
|
||||
if (!$this->hasPagePermission()) {
|
||||
|
||||
@@ -20,7 +20,12 @@ class WecomAcquisitionCustomerLogic
|
||||
->whereNull('l.delete_time')
|
||||
->where('l.remote_link_id', '<>', '')
|
||||
->where('l.remote_status', 1);
|
||||
self::applyScope($query, 'l', DataScopeService::getVisibleAdminIds($adminId, $adminInfo));
|
||||
self::applyScope(
|
||||
$query,
|
||||
'l',
|
||||
DataScopeService::getVisibleAdminIds($adminId, $adminInfo),
|
||||
self::operatorPoolIds($adminId)
|
||||
);
|
||||
if ($localLinkId > 0) {
|
||||
$query->where('l.id', $localLinkId);
|
||||
}
|
||||
@@ -56,7 +61,8 @@ class WecomAcquisitionCustomerLogic
|
||||
$page = max(1, (int) ($params['page_no'] ?? $params['page'] ?? 1));
|
||||
$pageSize = min(100, max(1, (int) ($params['page_size'] ?? 20)));
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$base = self::customerQuery($params, $visibleIds);
|
||||
$operatorPoolIds = self::operatorPoolIds($adminId);
|
||||
$base = self::customerQuery($params, $visibleIds, $operatorPoolIds);
|
||||
$total = (int) (clone $base)->count();
|
||||
$rows = $base
|
||||
->field('c.id,c.promotion_link_id,c.link_id,c.external_userid,c.userid,c.owner_admin_id,c.dept_id,c.state,c.chat_status,c.recv_msg_cnt,c.message_count_known,c.first_acquired_time,c.last_chat_time,c.last_sync_time,c.create_time,c.update_time,a.name as owner_name,d.name as dept_name,l.name as link_name,p.name as pool_name')
|
||||
@@ -71,7 +77,7 @@ class WecomAcquisitionCustomerLogic
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$summaryQuery = self::customerQuery($params, $visibleIds);
|
||||
$summaryQuery = self::customerQuery($params, $visibleIds, $operatorPoolIds);
|
||||
$summaryRow = $summaryQuery->fieldRaw(
|
||||
'COUNT(*) AS customer_count, '
|
||||
. 'COALESCE(SUM(CASE WHEN c.message_count_known = 1 THEN c.recv_msg_cnt ELSE 0 END),0) AS recv_msg_cnt, '
|
||||
@@ -98,14 +104,14 @@ class WecomAcquisitionCustomerLogic
|
||||
];
|
||||
}
|
||||
|
||||
private static function customerQuery(array $params, ?array $visibleIds)
|
||||
private static function customerQuery(array $params, ?array $visibleIds, array $operatorPoolIds)
|
||||
{
|
||||
$query = Db::name('qywx_customer_acquisition_customer')->alias('c')
|
||||
->leftJoin('admin a', 'a.id = c.owner_admin_id AND a.delete_time IS NULL')
|
||||
->leftJoin('dept d', 'd.id = c.dept_id')
|
||||
->leftJoin('qywx_promotion_link l', 'l.id = c.promotion_link_id')
|
||||
->leftJoin('qywx_promotion_pool p', 'p.id = l.pool_id');
|
||||
self::applyCustomerScope($query, $visibleIds);
|
||||
self::applyCustomerScope($query, $visibleIds, $operatorPoolIds);
|
||||
$localLinkId = max(0, (int) ($params['promotion_link_id'] ?? 0));
|
||||
if ($localLinkId > 0) {
|
||||
$query->where('c.promotion_link_id', $localLinkId);
|
||||
@@ -131,35 +137,69 @@ class WecomAcquisitionCustomerLogic
|
||||
* 企微 customer_list 的 state/customer_channel 允许为空,不能因此丢掉已返回的客户;
|
||||
* 同时也不能直接放开全部 owner_admin_id=0 的记录,否则会跨部门泄露未映射客户。
|
||||
*/
|
||||
private static function applyCustomerScope($query, ?array $visibleIds): void
|
||||
private static function applyCustomerScope($query, ?array $visibleIds, array $operatorPoolIds): void
|
||||
{
|
||||
if ($visibleIds === null) {
|
||||
return;
|
||||
}
|
||||
if ($visibleIds === []) {
|
||||
if ($visibleIds === [] && $operatorPoolIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
return;
|
||||
}
|
||||
$ids = array_values(array_unique(array_map('intval', $visibleIds)));
|
||||
$query->where(function ($scope) use ($ids): void {
|
||||
$scope->whereIn('c.owner_admin_id', $ids)
|
||||
->whereOr(function ($unmapped) use ($ids): void {
|
||||
$unmapped->where('c.owner_admin_id', 0)
|
||||
->whereIn('l.owner_admin_id', $ids);
|
||||
});
|
||||
$query->where(function ($scope) use ($ids, $operatorPoolIds): void {
|
||||
if ($ids !== []) {
|
||||
$scope->whereIn('c.owner_admin_id', $ids)
|
||||
->whereOr(function ($unmapped) use ($ids): void {
|
||||
$unmapped->where('c.owner_admin_id', 0)
|
||||
->whereIn('l.owner_admin_id', $ids);
|
||||
});
|
||||
if ($operatorPoolIds !== []) {
|
||||
$scope->whereOr('p.id', 'in', $operatorPoolIds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$scope->whereIn('p.id', $operatorPoolIds);
|
||||
});
|
||||
}
|
||||
|
||||
private static function applyScope($query, string $alias, ?array $visibleIds): void
|
||||
private static function applyScope($query, string $alias, ?array $visibleIds, array $operatorPoolIds): void
|
||||
{
|
||||
if ($visibleIds === null) {
|
||||
return;
|
||||
}
|
||||
if ($visibleIds === []) {
|
||||
if ($visibleIds === [] && $operatorPoolIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
return;
|
||||
}
|
||||
$query->whereIn($alias . '.owner_admin_id', array_values(array_unique(array_map('intval', $visibleIds))));
|
||||
$ids = array_values(array_unique(array_map('intval', $visibleIds)));
|
||||
$query->where(function ($scope) use ($alias, $ids, $operatorPoolIds): void {
|
||||
if ($ids !== []) {
|
||||
$scope->whereIn($alias . '.owner_admin_id', $ids);
|
||||
if ($operatorPoolIds !== []) {
|
||||
$scope->whereOr($alias . '.pool_id', 'in', $operatorPoolIds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$scope->whereIn($alias . '.pool_id', $operatorPoolIds);
|
||||
});
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private static function operatorPoolIds(int $adminId): array
|
||||
{
|
||||
if ($adminId <= 0) {
|
||||
return [];
|
||||
}
|
||||
$ids = Db::name('qywx_promotion_pool_operator')
|
||||
->where('admin_id', $adminId)
|
||||
->whereNull('delete_time')
|
||||
->column('pool_id');
|
||||
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn ($value): int => (int) $value,
|
||||
$ids
|
||||
), static fn (int $value): bool => $value > 0)));
|
||||
}
|
||||
|
||||
private static function maskIdentifier(string $value): string
|
||||
|
||||
@@ -21,12 +21,14 @@ class WecomPromotionLogic
|
||||
public static function overview(int $adminId, array $adminInfo, string $domain): array
|
||||
{
|
||||
self::assertMemberDispatchSchema();
|
||||
self::assertPoolOperatorSchema();
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$operatorPoolIds = self::operatorPoolIds($adminId);
|
||||
$poolsQuery = Db::name('qywx_promotion_pool')->alias('p')
|
||||
->leftJoin('admin u', 'u.id = p.owner_admin_id')
|
||||
->leftJoin('dept d', 'd.id = p.dept_id')
|
||||
->whereNull('p.delete_time');
|
||||
self::applyOwnerScope($poolsQuery, 'p', $visibleIds);
|
||||
self::applyPoolAccessScope($poolsQuery, 'p', $visibleIds, $operatorPoolIds);
|
||||
$pools = $poolsQuery
|
||||
->field('p.id,p.name,p.public_key,p.status,p.fallback_url,p.widget_config_json,p.click_count,p.owner_admin_id,p.dept_id,p.create_time,p.update_time,u.name as owner_name,d.name as dept_name')
|
||||
->order('p.id', 'desc')
|
||||
@@ -75,7 +77,10 @@ class WecomPromotionLogic
|
||||
}
|
||||
unset($link);
|
||||
|
||||
$memberOptions = self::memberOptions($adminId, $adminInfo);
|
||||
$poolMemberAdminIds = self::poolMemberAdminIds($poolIds);
|
||||
$memberOptions = self::memberOptions($adminId, $adminInfo, $poolMemberAdminIds);
|
||||
$operatorOptions = self::operatorOptions($adminId, $adminInfo);
|
||||
$operatorsByPool = self::poolOperators($poolIds);
|
||||
$adminIdByUserId = [];
|
||||
$memberOptionByAdminId = [];
|
||||
foreach ($memberOptions as $member) {
|
||||
@@ -153,6 +158,15 @@ class WecomPromotionLogic
|
||||
$pool['legacy_link_count'] = $legacyCount;
|
||||
$pool['member_admin_ids'] = array_values(array_unique($memberAdminIds));
|
||||
$pool['member_rules'] = $memberRules;
|
||||
$pool['operators'] = $operatorsByPool[(int) $pool['id']] ?? [];
|
||||
$pool['operator_admin_ids'] = array_values(array_map(
|
||||
static fn (array $operator): int => (int) $operator['id'],
|
||||
$pool['operators']
|
||||
));
|
||||
$pool['is_shared_with_me'] = in_array($adminId, $pool['operator_admin_ids'], true);
|
||||
$pool['can_operate'] = true;
|
||||
$pool['can_manage_access'] = self::ownerInScope((int) ($pool['owner_admin_id'] ?? 0), $visibleIds);
|
||||
$pool['can_delete'] = $pool['can_manage_access'];
|
||||
$pool['dispatch_sync'] = $sync;
|
||||
$pool['skip_verify'] = (int) ($officialLink['skip_verify'] ?? 0);
|
||||
$pool['migration_state'] = count($officialLinks) > 1
|
||||
@@ -171,6 +185,7 @@ class WecomPromotionLogic
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
'admin_id' => $adminId,
|
||||
'scope_label' => DataScopeService::scopeLabel(DataScopeService::getEffectiveScope($adminInfo)),
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
],
|
||||
@@ -184,6 +199,7 @@ class WecomPromotionLogic
|
||||
'pools' => $pools,
|
||||
'links' => $links,
|
||||
'member_options' => $memberOptions,
|
||||
'operator_options' => $operatorOptions,
|
||||
'department_options' => DeptLogic::getAllDataScoped($adminId, $adminInfo),
|
||||
'customer_acquisition_link_example' => QywxCustomerAcquisitionLinkService::example(),
|
||||
];
|
||||
@@ -204,7 +220,7 @@ class WecomPromotionLogic
|
||||
if (!QywxCustomerAcquisitionLinkService::isAllowed($fallback, true)) {
|
||||
throw new RuntimeException('兜底链接必须是企业微信获客助手生成的 HTTPS 链接');
|
||||
}
|
||||
$members = self::resolveMembers((array) ($params['member_admin_ids'] ?? []), $adminId, $adminInfo);
|
||||
$members = self::resolveMembers((array) ($params['member_admin_ids'] ?? []), $adminId, $adminInfo, $id);
|
||||
$userIds = array_values(array_column($members, 'userid'));
|
||||
$eligibleUserIds = self::eligibleSelectedUserIds($id, $members);
|
||||
$skipVerify = (int) ($params['skip_verify'] ?? 0) === 1 ? 1 : 0;
|
||||
@@ -388,7 +404,7 @@ class WecomPromotionLogic
|
||||
?QywxCustomerAcquisitionApiService $api = null
|
||||
): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo);
|
||||
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo, false);
|
||||
$links = Db::name('qywx_promotion_link')
|
||||
->where('pool_id', $id)
|
||||
->order('id', 'asc')
|
||||
@@ -479,6 +495,10 @@ class WecomPromotionLogic
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::name('qywx_promotion_pool_operator')->where('pool_id', $id)->whereNull('delete_time')->update([
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->update([
|
||||
'status' => 4,
|
||||
'next_retry' => 0,
|
||||
@@ -490,6 +510,139 @@ class WecomPromotionLogic
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加或移除分流方案共享操作人。
|
||||
* 共享操作人可访问并编辑方案;删除方案和继续授权仍受 owner 数据范围控制。
|
||||
*
|
||||
* @return array{action:string,pool_ids:list<int>,operator_admin_ids:list<int>,affected:int}
|
||||
*/
|
||||
public static function batchSetOperators(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
self::assertPoolOperatorSchema();
|
||||
$poolIds = self::normalizePositiveIds((array) ($params['pool_ids'] ?? []));
|
||||
$operatorAdminIds = self::normalizePositiveIds((array) (
|
||||
$params['operator_admin_ids'] ?? $params['admin_ids'] ?? []
|
||||
));
|
||||
$action = strtolower(trim((string) ($params['action'] ?? 'grant')));
|
||||
if (!in_array($action, ['grant', 'revoke'], true)) {
|
||||
throw new RuntimeException('批量设置方式仅支持添加或移除操作人');
|
||||
}
|
||||
if ($poolIds === []) {
|
||||
throw new RuntimeException('请至少选择一个分流方案');
|
||||
}
|
||||
if (count($poolIds) > 100) {
|
||||
throw new RuntimeException('单次最多设置 100 个分流方案');
|
||||
}
|
||||
if ($operatorAdminIds === []) {
|
||||
throw new RuntimeException('请至少选择一名操作人');
|
||||
}
|
||||
if (count($operatorAdminIds) > 100) {
|
||||
throw new RuntimeException('单次最多设置 100 名操作人');
|
||||
}
|
||||
if (in_array($adminId, $operatorAdminIds, true)) {
|
||||
throw new RuntimeException('不能将当前账号设置为自己的共享操作人');
|
||||
}
|
||||
|
||||
$pools = [];
|
||||
foreach ($poolIds as $poolId) {
|
||||
$pools[$poolId] = self::assertScopedRow(
|
||||
'qywx_promotion_pool',
|
||||
$poolId,
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds !== null) {
|
||||
foreach ($operatorAdminIds as $operatorAdminId) {
|
||||
if (!in_array($operatorAdminId, $visibleIds, true)) {
|
||||
throw new RuntimeException('选择的操作人超出当前角色或部门的数据范围');
|
||||
}
|
||||
}
|
||||
}
|
||||
$adminQuery = Db::name('admin')->whereIn('id', $operatorAdminIds)->whereNull('delete_time');
|
||||
if ($action === 'grant') {
|
||||
$adminQuery->where('disable', 0);
|
||||
}
|
||||
$operatorAdmins = $adminQuery->field('id,root')->select()->toArray();
|
||||
$existingAdminIds = self::normalizePositiveIds(array_column($operatorAdmins, 'id'));
|
||||
if (count($existingAdminIds) !== count($operatorAdminIds)) {
|
||||
throw new RuntimeException($action === 'grant'
|
||||
? '选择的操作人不存在或账号已被禁用'
|
||||
: '选择的操作人不存在');
|
||||
}
|
||||
if ($action === 'grant') {
|
||||
$pagePermissionAdminIds = self::promotionPagePermissionAdminIdSet($operatorAdminIds);
|
||||
foreach ($operatorAdmins as $operatorAdmin) {
|
||||
if ((int) ($operatorAdmin['root'] ?? 0) !== 1
|
||||
&& !isset($pagePermissionAdminIds[(int) ($operatorAdmin['id'] ?? 0)])) {
|
||||
throw new RuntimeException('选择的操作人尚未获得企业微信获客助手页面权限');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$affected = Db::transaction(function () use (
|
||||
$action,
|
||||
$poolIds,
|
||||
$pools,
|
||||
$operatorAdminIds,
|
||||
$adminId,
|
||||
$now
|
||||
): int {
|
||||
$changed = 0;
|
||||
foreach ($poolIds as $poolId) {
|
||||
$ownerAdminId = (int) ($pools[$poolId]['owner_admin_id'] ?? 0);
|
||||
foreach ($operatorAdminIds as $operatorAdminId) {
|
||||
// 方案归属人天然拥有权限,无需写入共享关系。
|
||||
if ($operatorAdminId === $ownerAdminId) {
|
||||
continue;
|
||||
}
|
||||
$query = Db::name('qywx_promotion_pool_operator')
|
||||
->where('pool_id', $poolId)
|
||||
->where('admin_id', $operatorAdminId);
|
||||
$existing = (clone $query)->lock(true)->find();
|
||||
if ($action === 'grant') {
|
||||
$data = [
|
||||
'granted_by_admin_id' => $adminId,
|
||||
'delete_time' => null,
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($existing) {
|
||||
if ($existing['delete_time'] !== null) {
|
||||
$changed++;
|
||||
}
|
||||
$query->update($data);
|
||||
} else {
|
||||
Db::name('qywx_promotion_pool_operator')->insert($data + [
|
||||
'pool_id' => $poolId,
|
||||
'admin_id' => $operatorAdminId,
|
||||
'create_time' => $now,
|
||||
]);
|
||||
$changed++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($existing && $existing['delete_time'] === null) {
|
||||
$query->update(['delete_time' => $now, 'update_time' => $now]);
|
||||
$changed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $changed;
|
||||
});
|
||||
|
||||
return [
|
||||
'action' => $action,
|
||||
'pool_ids' => $poolIds,
|
||||
'operator_admin_ids' => $operatorAdminIds,
|
||||
'affected' => $affected,
|
||||
];
|
||||
}
|
||||
|
||||
private static function isRemoteLinkAlreadyMissing(\Throwable $error): bool
|
||||
{
|
||||
$message = strtolower($error->getMessage());
|
||||
@@ -637,7 +790,11 @@ class WecomPromotionLogic
|
||||
$visibleAdminIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$visibleUserIds = null;
|
||||
if ($visibleAdminIds !== null) {
|
||||
$visibleUserIds = array_fill_keys(array_column(self::memberOptions($adminId, $adminInfo), 'userid'), true);
|
||||
$visibleUserIds = array_fill_keys(array_column(self::memberOptions(
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
self::poolMemberAdminIds([$poolId])
|
||||
), 'userid'), true);
|
||||
}
|
||||
|
||||
$api = new QywxCustomerAcquisitionApiService();
|
||||
@@ -704,7 +861,11 @@ class WecomPromotionLogic
|
||||
$remote = self::normaliseRemoteLink($api->getLink($remoteLinkId), $remoteLinkId);
|
||||
$visibleUserIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo) === null
|
||||
? null
|
||||
: array_fill_keys(array_column(self::memberOptions($adminId, $adminInfo), 'userid'), true);
|
||||
: array_fill_keys(array_column(self::memberOptions(
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
self::poolMemberAdminIds([(int) ($row['pool_id'] ?? 0)])
|
||||
), 'userid'), true);
|
||||
if (!self::canSeeRemoteLink($remote, $visibleUserIds)) {
|
||||
throw new RuntimeException('该获客链接已不在当前角色或部门的数据范围内');
|
||||
}
|
||||
@@ -749,8 +910,8 @@ class WecomPromotionLogic
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return list<array{id:int,name:string,userid:string,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
|
||||
private static function memberOptions(int $adminId, array $adminInfo): array
|
||||
/** @return list<array{id:int,name:string,disable:int,can_grant:bool,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
|
||||
private static function operatorOptions(int $adminId, array $adminInfo): array
|
||||
{
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds === []) {
|
||||
@@ -758,29 +919,89 @@ class WecomPromotionLogic
|
||||
}
|
||||
$query = Db::name('admin')->alias('a')
|
||||
->whereNull('a.delete_time')
|
||||
->where('a.disable', 0)
|
||||
->where('a.work_wechat_userid', '<>', '');
|
||||
->where('a.id', '<>', $adminId);
|
||||
if ($visibleIds !== null) {
|
||||
$query->whereIn('a.id', $visibleIds);
|
||||
}
|
||||
$admins = $query->field('a.id,a.name,a.root,a.disable')->order('a.disable', 'asc')->order('a.name', 'asc')->order('a.id', 'asc')->select()->toArray();
|
||||
if ($admins === []) {
|
||||
return [];
|
||||
}
|
||||
$adminIds = self::normalizePositiveIds(array_column($admins, 'id'));
|
||||
$departments = self::adminDepartmentMaps($adminIds);
|
||||
$pagePermissionAdminIds = self::promotionPagePermissionAdminIdSet($adminIds);
|
||||
$result = [];
|
||||
foreach ($admins as $admin) {
|
||||
$aid = (int) $admin['id'];
|
||||
$deptIds = array_values(array_unique(array_filter($departments[$aid]['ids'] ?? [])));
|
||||
$hasPagePermission = (int) ($admin['root'] ?? 0) === 1
|
||||
|| isset($pagePermissionAdminIds[$aid]);
|
||||
$result[] = [
|
||||
'id' => $aid,
|
||||
'name' => (string) ($admin['name'] ?? ('账号 ' . $aid)),
|
||||
'disable' => (int) ($admin['disable'] ?? 0),
|
||||
'can_grant' => (int) ($admin['disable'] ?? 0) === 0 && $hasPagePermission,
|
||||
'display_dept_id' => (int) ($deptIds[0] ?? 0),
|
||||
'dept_ids' => $deptIds,
|
||||
'dept_names' => array_values(array_unique($departments[$aid]['names'] ?? [])),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @param list<int> $poolIds @return array<int,list<array{id:int,name:string,disable:int,dept_names:list<string>}>> */
|
||||
private static function poolOperators(array $poolIds): array
|
||||
{
|
||||
if ($poolIds === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('qywx_promotion_pool_operator')->alias('po')
|
||||
->join('admin a', 'a.id = po.admin_id')
|
||||
->whereIn('po.pool_id', $poolIds)
|
||||
->whereNull('po.delete_time')
|
||||
->whereNull('a.delete_time')
|
||||
->field('po.pool_id,a.id,a.name,a.disable')
|
||||
->order('a.name', 'asc')
|
||||
->order('a.id', 'asc')
|
||||
->select()->toArray();
|
||||
$departments = self::adminDepartmentMaps(self::normalizePositiveIds(array_column($rows, 'id')));
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$aid = (int) ($row['id'] ?? 0);
|
||||
$result[(int) $row['pool_id']][] = [
|
||||
'id' => $aid,
|
||||
'name' => (string) ($row['name'] ?? ('账号 ' . $aid)),
|
||||
'disable' => (int) ($row['disable'] ?? 0),
|
||||
'dept_names' => array_values(array_unique($departments[$aid]['names'] ?? [])),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @return list<array{id:int,name:string,userid:string,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
|
||||
private static function memberOptions(int $adminId, array $adminInfo, array $extraAdminIds = []): array
|
||||
{
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$allowedIds = $visibleIds === null
|
||||
? null
|
||||
: self::normalizePositiveIds(array_merge($visibleIds, $extraAdminIds));
|
||||
if ($allowedIds === []) {
|
||||
return [];
|
||||
}
|
||||
$query = Db::name('admin')->alias('a')
|
||||
->whereNull('a.delete_time')
|
||||
->where('a.disable', 0)
|
||||
->where('a.work_wechat_userid', '<>', '');
|
||||
if ($allowedIds !== null) {
|
||||
$query->whereIn('a.id', $allowedIds);
|
||||
}
|
||||
$admins = $query->field('a.id,a.name,a.work_wechat_userid')->order('a.id', 'asc')->select()->toArray();
|
||||
if ($admins === []) {
|
||||
return [];
|
||||
}
|
||||
$adminIds = array_map('intval', array_column($admins, 'id'));
|
||||
$deptRows = Db::name('admin_dept')->alias('ad')
|
||||
->leftJoin('dept d', 'd.id = ad.dept_id')
|
||||
->whereIn('ad.admin_id', $adminIds)
|
||||
->field('ad.admin_id,ad.dept_id,d.name as dept_name')
|
||||
->order('ad.dept_id', 'asc')->select()->toArray();
|
||||
$departments = [];
|
||||
foreach ($deptRows as $row) {
|
||||
$aid = (int) ($row['admin_id'] ?? 0);
|
||||
$departments[$aid]['ids'][] = (int) ($row['dept_id'] ?? 0);
|
||||
if (trim((string) ($row['dept_name'] ?? '')) !== '') {
|
||||
$departments[$aid]['names'][] = (string) $row['dept_name'];
|
||||
}
|
||||
}
|
||||
$departments = self::adminDepartmentMaps(self::normalizePositiveIds(array_column($admins, 'id')));
|
||||
|
||||
$result = [];
|
||||
$seenUserIds = [];
|
||||
@@ -808,14 +1029,15 @@ class WecomPromotionLogic
|
||||
}
|
||||
|
||||
/** @return list<array{id:int,name:string,userid:string,display_dept_id:int,dept_ids:list<int>,dept_names:list<string>}> */
|
||||
private static function resolveMembers(array $adminIds, int $adminId, array $adminInfo): array
|
||||
private static function resolveMembers(array $adminIds, int $adminId, array $adminInfo, int $poolId = 0): array
|
||||
{
|
||||
$requested = array_values(array_unique(array_filter(array_map('intval', $adminIds))));
|
||||
if ($requested === []) {
|
||||
throw new RuntimeException('请至少选择一名当前角色或部门范围内的获客成员');
|
||||
}
|
||||
$available = [];
|
||||
foreach (self::memberOptions($adminId, $adminInfo) as $member) {
|
||||
$existingMemberAdminIds = $poolId > 0 ? self::poolMemberAdminIds([$poolId]) : [];
|
||||
foreach (self::memberOptions($adminId, $adminInfo, $existingMemberAdminIds) as $member) {
|
||||
$available[(int) $member['id']] = $member;
|
||||
}
|
||||
$members = [];
|
||||
@@ -1033,9 +1255,8 @@ class WecomPromotionLogic
|
||||
$existing = Db::name('qywx_promotion_link')->where('remote_link_id', $remoteLinkId)->find();
|
||||
$remoteData = self::remoteColumns($remote, $now);
|
||||
if ($existing) {
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds !== null && !in_array((int) ($existing['owner_admin_id'] ?? 0), $visibleIds, true)) {
|
||||
throw new RuntimeException('该链接已归属其他数据范围');
|
||||
if ((int) ($existing['pool_id'] ?? 0) !== (int) ($pool['id'] ?? 0)) {
|
||||
throw new RuntimeException('该企业微信链接已归属其他分流方案');
|
||||
}
|
||||
$remoteData['delete_time'] = null;
|
||||
Db::name('qywx_promotion_link')->where('id', (int) $existing['id'])->update($remoteData);
|
||||
@@ -1146,37 +1367,183 @@ class WecomPromotionLogic
|
||||
return $encoded === false ? '[]' : $encoded;
|
||||
}
|
||||
|
||||
private static function assertScopedRow(string $table, int $id, int $adminId, array $adminInfo): array
|
||||
private static function assertScopedRow(
|
||||
string $table,
|
||||
int $id,
|
||||
int $adminId,
|
||||
array $adminInfo,
|
||||
bool $allowOperator = true
|
||||
): array
|
||||
{
|
||||
if ($id <= 0) {
|
||||
throw new RuntimeException('数据不存在');
|
||||
}
|
||||
$query = Db::name($table)->where('id', $id)->whereNull('delete_time');
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds !== null) {
|
||||
if ($visibleIds === []) {
|
||||
throw new RuntimeException('无权访问该数据');
|
||||
}
|
||||
$query->whereIn('owner_admin_id', $visibleIds);
|
||||
}
|
||||
$row = $query->find();
|
||||
$row = Db::name($table)->where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$row) {
|
||||
throw new RuntimeException('数据不存在或已删除');
|
||||
}
|
||||
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
$pool = $table === 'qywx_promotion_pool'
|
||||
? $row
|
||||
: ($table === 'qywx_promotion_link'
|
||||
? Db::name('qywx_promotion_pool')
|
||||
->where('id', (int) ($row['pool_id'] ?? 0))
|
||||
->whereNull('delete_time')
|
||||
->find()
|
||||
: null);
|
||||
if ($pool !== null) {
|
||||
if (self::ownerInScope((int) ($pool['owner_admin_id'] ?? 0), $visibleIds)) {
|
||||
return $row;
|
||||
}
|
||||
if ($allowOperator && self::isPoolOperator((int) ($pool['id'] ?? 0), $adminId)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
throw new RuntimeException($allowOperator
|
||||
? '无权访问或操作该分流方案'
|
||||
: '只有方案原有管理范围内的账号可以执行此操作');
|
||||
}
|
||||
if (!self::ownerInScope((int) ($row['owner_admin_id'] ?? 0), $visibleIds)) {
|
||||
throw new RuntimeException('数据不存在或超出当前权限范围');
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
private static function applyOwnerScope($query, string $alias, ?array $visibleIds): void
|
||||
/** @param list<int>|null $visibleIds */
|
||||
private static function ownerInScope(int $ownerAdminId, ?array $visibleIds): bool
|
||||
{
|
||||
if ($visibleIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($ownerAdminId, $visibleIds, true);
|
||||
}
|
||||
|
||||
/** @param list<int>|null $visibleIds @param list<int> $operatorPoolIds */
|
||||
private static function applyPoolAccessScope(
|
||||
$query,
|
||||
string $alias,
|
||||
?array $visibleIds,
|
||||
array $operatorPoolIds
|
||||
): void {
|
||||
if ($visibleIds === null) {
|
||||
return;
|
||||
}
|
||||
if ($visibleIds === []) {
|
||||
if ($visibleIds === [] && $operatorPoolIds === []) {
|
||||
$query->whereRaw('1 = 0');
|
||||
return;
|
||||
}
|
||||
$query->whereIn($alias . '.owner_admin_id', $visibleIds);
|
||||
$query->where(function ($scope) use ($alias, $visibleIds, $operatorPoolIds): void {
|
||||
if ($visibleIds !== []) {
|
||||
$scope->whereIn($alias . '.owner_admin_id', $visibleIds);
|
||||
if ($operatorPoolIds !== []) {
|
||||
$scope->whereOr($alias . '.id', 'in', $operatorPoolIds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$scope->whereIn($alias . '.id', $operatorPoolIds);
|
||||
});
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private static function operatorPoolIds(int $adminId): array
|
||||
{
|
||||
if ($adminId <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return self::normalizePositiveIds(Db::name('qywx_promotion_pool_operator')
|
||||
->where('admin_id', $adminId)
|
||||
->whereNull('delete_time')
|
||||
->column('pool_id'));
|
||||
}
|
||||
|
||||
private static function isPoolOperator(int $poolId, int $adminId): bool
|
||||
{
|
||||
if ($poolId <= 0 || $adminId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Db::name('qywx_promotion_pool_operator')
|
||||
->where('pool_id', $poolId)
|
||||
->where('admin_id', $adminId)
|
||||
->whereNull('delete_time')
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
/** @param list<int> $poolIds @return list<int> */
|
||||
private static function poolMemberAdminIds(array $poolIds): array
|
||||
{
|
||||
$poolIds = self::normalizePositiveIds($poolIds);
|
||||
if ($poolIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return self::normalizePositiveIds(Db::name('qywx_promotion_pool_member')
|
||||
->whereIn('pool_id', $poolIds)
|
||||
->whereNull('delete_time')
|
||||
->column('admin_id'));
|
||||
}
|
||||
|
||||
/** @param list<int> $adminIds @return array<int,array{ids:list<int>,names:list<string>}> */
|
||||
private static function adminDepartmentMaps(array $adminIds): array
|
||||
{
|
||||
if ($adminIds === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('admin_dept')->alias('ad')
|
||||
->leftJoin('dept d', 'd.id = ad.dept_id')
|
||||
->whereIn('ad.admin_id', $adminIds)
|
||||
->field('ad.admin_id,ad.dept_id,d.name as dept_name')
|
||||
->order('ad.dept_id', 'asc')->select()->toArray();
|
||||
$departments = [];
|
||||
foreach ($rows as $row) {
|
||||
$aid = (int) ($row['admin_id'] ?? 0);
|
||||
$departments[$aid]['ids'][] = (int) ($row['dept_id'] ?? 0);
|
||||
if (trim((string) ($row['dept_name'] ?? '')) !== '') {
|
||||
$departments[$aid]['names'][] = (string) $row['dept_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $departments;
|
||||
}
|
||||
|
||||
/** @param list<int> $adminIds @return array<int,true> */
|
||||
private static function promotionPagePermissionAdminIdSet(array $adminIds): array
|
||||
{
|
||||
if ($adminIds === []) {
|
||||
return [];
|
||||
}
|
||||
$menuIds = self::normalizePositiveIds(Db::name('system_menu')
|
||||
->where('perms', 'firstvisit.wecomPromotion/overview')
|
||||
->where('is_disable', 0)
|
||||
->column('id'));
|
||||
if ($menuIds === []) {
|
||||
return [];
|
||||
}
|
||||
$roleIds = self::normalizePositiveIds(Db::name('system_role_menu')
|
||||
->whereIn('menu_id', $menuIds)
|
||||
->column('role_id'));
|
||||
if ($roleIds === []) {
|
||||
return [];
|
||||
}
|
||||
$permittedAdminIds = self::normalizePositiveIds(Db::name('admin_role')
|
||||
->whereIn('role_id', $roleIds)
|
||||
->whereIn('admin_id', $adminIds)
|
||||
->column('admin_id'));
|
||||
|
||||
return array_fill_keys($permittedAdminIds, true);
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private static function normalizePositiveIds(array $ids): array
|
||||
{
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn ($value): int => (int) $value,
|
||||
$ids
|
||||
), static fn (int $value): bool => $value > 0)));
|
||||
}
|
||||
|
||||
private static function primaryDeptId(int $adminId): int
|
||||
@@ -1210,6 +1577,17 @@ class WecomPromotionLogic
|
||||
}
|
||||
}
|
||||
|
||||
private static function assertPoolOperatorSchema(): void
|
||||
{
|
||||
try {
|
||||
Db::name('qywx_promotion_pool_operator')->limit(1)->find();
|
||||
} catch (\Throwable $e) {
|
||||
throw new RuntimeException(
|
||||
'分流方案共享操作人数据表尚未安装,请先执行 server/sql/1.9.20260828/add_wecom_promotion_pool_operators.sql'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function mask(string $value): string
|
||||
{
|
||||
$length = strlen($value);
|
||||
|
||||
Reference in New Issue
Block a user