| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- <?php
- namespace plugin\saiadmin\app\logic\agent;
- use plugin\saiadmin\app\cache\UserInfoCache;
- use plugin\saiadmin\app\cache\UserAuthCache;
- use plugin\saiadmin\app\cache\UserMenuCache;
- use plugin\saiadmin\app\model\system\SystemDept;
- use plugin\saiadmin\app\model\system\SystemUser;
- use plugin\saiadmin\basic\think\BaseLogic;
- use plugin\saiadmin\exception\ApiException;
- use plugin\saiadmin\service\OpenSpoutWriter;
- use support\Response;
- use support\think\Db;
- /**
- * 代理列表与设备管理权分配。
- *
- * 旧系统 DeviceManagerRecord 的 last_aid/now_aid/flow_type 映射到
- * sa_device_manager_record,并以 sa_device.manage_agent_id 作为当前管理关系。
- */
- class AgentLogic extends BaseLogic
- {
- private const PRODUCT_CODES = [
- 'vehicle',
- 'ktv',
- 'bluetooth-cable',
- 'power-bank',
- 'password-cable',
- 'password-bluetooth-cable',
- ];
- private const PERMISSION_CODES = [
- 'self_operated',
- 'view_order',
- 'self_merchant',
- 'finish_order',
- 'refund_order',
- 'dd',
- ];
- private const TYPE_NAMES = [
- 1 => '充电底座',
- 100 => 'K霸宝',
- 101 => '充电宝',
- 102 => 'K霸宝 Blue',
- 103 => '密码线充',
- 104 => '蓝牙线充',
- 105 => '密码蓝牙线充',
- 107 => '车载充电器',
- ];
- public function __construct()
- {
- $this->model = new SystemUser();
- }
- public function indexList(array $where): array
- {
- $rows = $this->agentRows($where);
- $page = max(1, (int) request()->input('page', 1));
- $limit = min(100, max(1, (int) request()->input('limit', 10)));
- return [
- 'total' => count($rows),
- 'per_page' => $limit,
- 'current_page' => $page,
- 'last_page' => max(1, (int) ceil(count($rows) / $limit)),
- 'data' => array_slice($rows, ($page - 1) * $limit, $limit),
- ];
- }
- public function export(array $where): Response
- {
- $data = [];
- foreach ($this->agentRows($where) as $row) {
- $data[] = [
- 'id' => $row['id'],
- 'name' => $this->excelSafe($row['name']),
- 'phone' => $this->excelSafe($row['phone']),
- 'agent_type' => $row['agent_type'],
- 'region' => $this->excelSafe(implode(' ', array_filter([$row['province'], $row['city'], $row['district']]))),
- 'device_count' => $row['device_count'],
- 'subordinate_count' => $row['subordinate_count'],
- 'income' => (float) $row['income'],
- 'withdrawable' => (float) $row['withdrawable'],
- 'profit_rate' => $row['profit_rate'],
- 'status' => $row['status_name'],
- 'created_at' => $row['created_at'],
- ];
- }
- $fileName = '代理数据_' . date('Ymd_His') . '.xlsx';
- $writer = new OpenSpoutWriter($fileName);
- $writer->setWidth([10, 18, 18, 16, 32, 14, 14, 14, 14, 14, 12, 22]);
- $writer->setHeader([
- '代理ID', '代理姓名', '手机号码', '代理类型', '运营区域', '设备数',
- '下级总数', '总收益', '可提现金额', '分润比例', '状态', '创建时间',
- ]);
- $writer->setData($data);
- return response()->download($writer->returnFile(), urlencode($fileName));
- }
- public function readAgent(int $id): array
- {
- $agent = $this->findAccessibleAgent($id, false);
- $deptRows = SystemDept::whereNull('delete_time')->field('id,name,level')->select()->toArray();
- $deptMap = [];
- foreach ($deptRows as $dept) {
- $deptMap[(int) $dept['id']] = $dept;
- }
- $dept = $deptMap[(int) $agent['dept_id']] ?? [];
- $region = $this->regionNames($dept, $deptMap);
- $currentDepth = $this->deptDepth($this->adminInfo['deptList'] ?? []);
- $type = $this->agentType($this->deptDepth($dept) - $currentDepth);
- $profile = Db::table('sa_agent_profile')->where('agent_id', $id)->find() ?: [];
- $products = $this->decodeList($profile['product_codes'] ?? null, self::PRODUCT_CODES);
- if ($products === []) {
- $products = self::PRODUCT_CODES;
- }
- $profitRates = $this->decodeMap($profile['profit_rates'] ?? null);
- foreach ($products as $product) {
- $profitRates[$product] = (float) ($profitRates[$product] ?? 0);
- }
- return [
- 'id' => (int) $agent['id'],
- 'opening_type' => $type['code'],
- 'opening_type_name' => $type['name'],
- 'business_mode' => in_array($profile['business_mode'] ?? '', ['agent', 'employee'], true)
- ? (string) $profile['business_mode'] : 'agent',
- 'name' => (string) (($agent['realname'] ?? '') ?: $agent['username']),
- 'phone' => (string) ($agent['phone'] ?? ''),
- 'product_codes' => $products,
- 'profit_rates' => $profitRates,
- 'permissions' => $this->decodeList($profile['permissions'] ?? null, self::PERMISSION_CODES),
- 'province' => (string) (($profile['province'] ?? '') ?: ($region[0] === '--' ? '' : $region[0])),
- 'city' => (string) (($profile['city'] ?? '') ?: ($region[1] === '--' ? '' : $region[1])),
- 'district' => (string) (($profile['district'] ?? '') ?: ($region[2] === '--' ? '' : $region[2])),
- 'company_address' => (string) ($profile['company_address'] ?? ''),
- 'status' => (int) ($agent['status'] ?? 1),
- ];
- }
- public function updateAgent(int $id, array $data): bool
- {
- $agent = $this->findAccessibleAgent($id, false);
- $name = trim((string) ($data['name'] ?? ''));
- $phone = trim((string) ($data['phone'] ?? ''));
- if ($name === '') {
- throw new ApiException('请输入代理姓名');
- }
- if (!preg_match('/^1\d{10}$/', $phone)) {
- throw new ApiException('请输入正确的手机号码');
- }
- if ($phone !== (string) ($agent['phone'] ?? '')) {
- $exists = SystemUser::where('phone', $phone)->where('id', '<>', $id)->whereNull('delete_time')->value('id');
- if ($exists) {
- throw new ApiException('该手机号码已被其他账号使用');
- }
- }
- $businessMode = (string) ($data['business_mode'] ?? '');
- if (!in_array($businessMode, ['agent', 'employee'], true)) {
- throw new ApiException('运营模式参数错误');
- }
- $products = $this->normalizeList($data['product_codes'] ?? [], self::PRODUCT_CODES);
- if ($products === []) {
- throw new ApiException('请至少选择一种运营产品');
- }
- $permissions = $this->normalizeList($data['permissions'] ?? [], self::PERMISSION_CODES);
- $rawRates = is_array($data['profit_rates'] ?? null) ? $data['profit_rates'] : [];
- $profitRates = [];
- foreach ($products as $product) {
- $rate = $rawRates[$product] ?? null;
- if (!is_numeric($rate) || (float) $rate < 0 || (float) $rate > 100) {
- throw new ApiException('分润比例必须在 0 到 100 之间');
- }
- $profitRates[$product] = round((float) $rate, 2);
- }
- $operatorId = $this->currentAdminId();
- $now = date('Y-m-d H:i:s');
- $profileData = [
- 'business_mode' => $businessMode,
- 'product_codes' => json_encode($products, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'profit_rates' => json_encode($profitRates, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'permissions' => json_encode($permissions, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'province' => trim((string) ($data['province'] ?? '')),
- 'city' => trim((string) ($data['city'] ?? '')),
- 'district' => trim((string) ($data['district'] ?? '')),
- 'company_address' => trim((string) ($data['company_address'] ?? '')),
- 'updated_by' => $operatorId,
- 'update_time' => $now,
- ];
- if ($profileData['province'] === '' || $profileData['city'] === '' || $profileData['district'] === '') {
- throw new ApiException('请选择完整的运营区域');
- }
- $this->transaction(function () use ($id, $name, $phone, $operatorId, $now, $profileData) {
- Db::table('sa_system_user')->where('id', $id)->update([
- 'realname' => $name,
- 'phone' => $phone,
- 'updated_by' => $operatorId,
- 'update_time' => $now,
- ]);
- $profile = Db::table('sa_agent_profile')->where('agent_id', $id)->lock(true)->find();
- if ($profile) {
- Db::table('sa_agent_profile')->where('agent_id', $id)->update($profileData);
- } else {
- Db::table('sa_agent_profile')->insert(array_merge($profileData, [
- 'agent_id' => $id,
- 'created_by' => $operatorId,
- 'create_time' => $now,
- ]));
- }
- });
- UserInfoCache::clearUserInfo($id);
- return true;
- }
- /**
- * 禁用无关联资产和下级账号的代理。
- *
- * 旧系统的“删除代理”只停用账号,不物理删除历史记录;这里保持该语义,
- * 同时阻止遗留设备、商户或下级账号的孤立关联。
- */
- public function destroyAgent(int $id): void
- {
- $agent = $this->findAccessibleAgent($id, false);
- if ((int) $agent['status'] !== 1) {
- throw new ApiException('代理已删除或已停用');
- }
- $managedDeviceCount = (int) Db::table('sa_device')->whereNull('delete_time')
- ->where('manage_agent_id', $id)->count();
- $ownedDeviceCount = (int) Db::table('sa_device')->whereNull('delete_time')
- ->where('belong_agent_id', $id)->count();
- if ($managedDeviceCount > 0 || $ownedDeviceCount > 0) {
- throw new ApiException('该代理名下仍有设备,请先回收或转移设备后再删除');
- }
- $merchantCount = (int) Db::table('sa_merchant')->whereNull('delete_time')
- ->where('agent_id', $id)->count();
- if ($merchantCount > 0) {
- throw new ApiException('该代理名下仍有商户,请先转移商户后再删除');
- }
- $dept = SystemDept::where('id', (int) $agent['dept_id'])->field('id,level')->find();
- if ($dept) {
- $prefix = (string) $dept->level . (int) $dept->id . ',';
- $descendantDeptIds = SystemDept::whereLike('level', $prefix . '%')->column('id');
- if ($descendantDeptIds !== []) {
- $subordinateCount = (int) SystemUser::whereIn('dept_id', $descendantDeptIds)
- ->whereNull('delete_time')->count();
- if ($subordinateCount > 0) {
- throw new ApiException('该代理仍有下级代理,请先转移或删除下级代理后再删除');
- }
- }
- }
- $updated = Db::table('sa_system_user')->where('id', $id)->where('status', 1)
- ->whereNull('delete_time')->update([
- 'status' => 2,
- 'updated_by' => $this->currentAdminId(),
- 'update_time' => date('Y-m-d H:i:s'),
- ]);
- if ($updated !== 1) {
- throw new ApiException('代理删除失败,请刷新后重试');
- }
- UserInfoCache::clearUserInfo($id);
- UserAuthCache::clearUserAuth($id);
- UserMenuCache::clearUserMenu($id);
- }
- public function readContract(int $id): array
- {
- $agent = $this->findAccessibleAgent($id, false);
- $contract = Db::table('sa_agent_contract')->where('agent_id', $id)->find() ?: [];
- return [
- 'id' => $id,
- 'agent_name' => (string) (($agent['realname'] ?? '') ?: $agent['username']),
- 'phone' => (string) ($agent['phone'] ?? ''),
- 'contract_urls' => $this->decodeAttachmentUrls($contract['contract_urls'] ?? null),
- 'identity_urls' => $this->decodeAttachmentUrls($contract['identity_urls'] ?? null),
- 'extra_urls' => $this->decodeAttachmentUrls($contract['extra_urls'] ?? null),
- ];
- }
- public function updateContract(int $id, array $data): array
- {
- $this->findAccessibleAgent($id, false);
- $contractUrls = $this->normalizeAttachmentUrls($data['contract_urls'] ?? []);
- $identityUrls = $this->normalizeAttachmentUrls($data['identity_urls'] ?? []);
- $extraUrls = $this->normalizeAttachmentUrls($data['extra_urls'] ?? []);
- $operatorId = $this->currentAdminId();
- $now = date('Y-m-d H:i:s');
- $contractData = [
- 'contract_urls' => json_encode($contractUrls, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'identity_urls' => json_encode($identityUrls, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'extra_urls' => json_encode($extraUrls, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
- 'updated_by' => $operatorId,
- 'update_time' => $now,
- ];
- $this->transaction(function () use ($id, $operatorId, $now, $contractData) {
- $contract = Db::table('sa_agent_contract')->where('agent_id', $id)->lock(true)->find();
- if ($contract) {
- Db::table('sa_agent_contract')->where('agent_id', $id)->update($contractData);
- return;
- }
- Db::table('sa_agent_contract')->insert(array_merge($contractData, [
- 'agent_id' => $id,
- 'created_by' => $operatorId,
- 'create_time' => $now,
- ]));
- });
- return $this->readContract($id);
- }
- public function candidateDevices(array $where): array
- {
- $page = max(1, (int) request()->input('page', 1));
- $limit = min(100, max(1, (int) request()->input('limit', 20)));
- $query = Db::table('sa_device')->alias('d')
- ->leftJoin('sa_merchant_device_binding b', 'b.device_id = d.id')
- ->whereNull('d.delete_time')
- ->whereNull('b.device_id')
- ->where('d.manage_agent_id', $this->currentAdminId())
- ->field('d.id,d.goods_name,d.goods_type,d.goods_sn,d.device_code,d.model,d.device_status,d.create_time');
- $category = trim((string) ($where['category'] ?? 'all'));
- if ($category === 'vehicle') {
- $query->where('d.goods_type', 107);
- } elseif ($category === 'power-bank') {
- $query->whereIn('d.goods_type', [1, 100, 101, 102, 103, 104, 105]);
- }
- if (($where['goods_type'] ?? '') !== '') {
- $query->where('d.goods_type', (int) $where['goods_type']);
- }
- $sn = trim((string) ($where['device_sn'] ?? ''));
- if ($sn !== '') {
- $query->whereLike('d.goods_sn', '%' . $sn . '%');
- }
- $code = trim((string) ($where['qr_code'] ?? ''));
- if ($code !== '') {
- $query->whereLike('d.device_code', '%' . $code . '%');
- }
- $result = $query->order('d.id', 'desc')->paginate([
- 'page' => $page,
- 'list_rows' => $limit,
- ])->toArray();
- $result['data'] = array_map(function (array $row) {
- $type = (int) $row['goods_type'];
- return [
- 'id' => (int) $row['id'],
- 'name' => (string) $row['goods_name'],
- 'goods_type' => $type,
- 'type_name' => self::TYPE_NAMES[$type] ?? '其他设备',
- 'device_sn' => (string) $row['goods_sn'],
- 'qr_code' => (string) (($row['device_code'] ?? '') ?: $row['goods_sn']),
- 'model' => (string) (($row['model'] ?? '') ?: '--'),
- 'online' => (int) ($row['device_status'] ?? 0) === 1,
- 'created_at' => (string) ($row['create_time'] ?? ''),
- ];
- }, $result['data'] ?? []);
- return $result;
- }
- public function assignDevices(int $agentId, array $deviceSns): array
- {
- $target = $this->findAccessibleAgent($agentId);
- $operatorId = $this->currentAdminId();
- if ((int) $target['id'] === $operatorId) {
- throw new ApiException('不能给自己分配设备');
- }
- return $this->moveDevices($operatorId, $agentId, $deviceSns, 1, '上级分配设备');
- }
- public function recoverDevices(int $agentId, array $deviceSns): array
- {
- $this->findAccessibleAgent($agentId);
- return $this->moveDevices($agentId, $this->currentAdminId(), $deviceSns, 2, '上级回收设备');
- }
- public function assignmentRecords(int $agentId, array $where): array
- {
- $this->findAccessibleAgent($agentId);
- $currentId = $this->currentAdminId();
- $page = max(1, (int) request()->input('page', 1));
- $limit = min(100, max(1, (int) request()->input('limit', 20)));
- $query = Db::table('sa_device_manager_record')->alias('r')
- ->leftJoin('sa_system_user last_user', 'last_user.id = r.last_agent_id')
- ->leftJoin('sa_system_user now_user', 'now_user.id = r.now_agent_id')
- ->whereIn('r.last_agent_id', [$currentId, $agentId])
- ->whereIn('r.now_agent_id', [$currentId, $agentId])
- ->field(implode(',', [
- 'r.id', 'r.device_id', 'r.device_sn', 'r.goods_type', 'r.last_agent_id',
- 'r.now_agent_id', 'r.action_type', 'r.reason', 'r.operator_id', 'r.action_time',
- 'last_user.realname AS last_realname', 'last_user.username AS last_username',
- 'now_user.realname AS now_realname', 'now_user.username AS now_username',
- ]));
- if (($where['action_type'] ?? '') !== '') {
- $query->where('r.action_type', (int) $where['action_type']);
- }
- if (($where['goods_type'] ?? '') !== '') {
- $query->where('r.goods_type', (int) $where['goods_type']);
- }
- $sn = trim((string) ($where['device_sn'] ?? ''));
- if ($sn !== '') {
- $query->whereLike('r.device_sn', '%' . $sn . '%');
- }
- $result = $query->order('r.id', 'desc')->paginate([
- 'page' => $page,
- 'list_rows' => $limit,
- ])->toArray();
- $result['data'] = array_map(function (array $row) {
- $type = (int) $row['goods_type'];
- return [
- 'id' => (int) $row['id'],
- 'device_id' => (int) $row['device_id'],
- 'device_sn' => (string) $row['device_sn'],
- 'goods_type' => $type,
- 'type_name' => self::TYPE_NAMES[$type] ?? '其他设备',
- 'last_agent_id' => (int) $row['last_agent_id'],
- 'last_agent_name' => (string) (($row['last_realname'] ?? '') ?: ($row['last_username'] ?? '')),
- 'now_agent_id' => (int) $row['now_agent_id'],
- 'now_agent_name' => (string) (($row['now_realname'] ?? '') ?: ($row['now_username'] ?? '')),
- 'action_type' => (int) $row['action_type'],
- 'action_name' => (int) $row['action_type'] === 1 ? '分配' : '回收',
- 'reason' => (string) $row['reason'],
- 'action_time' => (string) $row['action_time'],
- ];
- }, $result['data'] ?? []);
- return $result;
- }
- private function moveDevices(int $fromAgentId, int $toAgentId, array $deviceSns, int $actionType, string $reason): array
- {
- $sns = $this->normalizeSns($deviceSns);
- $operatorId = $this->currentAdminId();
- return $this->transaction(function () use ($fromAgentId, $toAgentId, $sns, $actionType, $reason, $operatorId) {
- $devices = Db::table('sa_device')->whereIn('goods_sn', $sns)
- ->whereNull('delete_time')->lock(true)->select()->toArray();
- $deviceMap = [];
- foreach ($devices as $device) {
- $deviceMap[(string) $device['goods_sn']] = $device;
- }
- $ids = array_map('intval', array_column($devices, 'id'));
- $boundIds = $ids === [] ? [] : array_map('intval', Db::table('sa_merchant_device_binding')
- ->whereIn('device_id', $ids)->column('device_id'));
- $boundMap = array_fill_keys($boundIds, true);
- $success = [];
- $notFound = [];
- $notManaged = [];
- $alreadyTarget = [];
- $distributed = [];
- $records = [];
- $now = date('Y-m-d H:i:s');
- foreach ($sns as $sn) {
- if (!isset($deviceMap[$sn])) {
- $notFound[] = $sn;
- continue;
- }
- $device = $deviceMap[$sn];
- $deviceId = (int) $device['id'];
- $managerId = (int) $device['manage_agent_id'];
- if ($managerId === $toAgentId) {
- $alreadyTarget[] = $sn;
- continue;
- }
- if ($managerId !== $fromAgentId) {
- $notManaged[] = $sn;
- continue;
- }
- if (isset($boundMap[$deviceId])) {
- $distributed[] = $sn;
- continue;
- }
- $updated = Db::table('sa_device')->where('id', $deviceId)
- ->where('manage_agent_id', $fromAgentId)
- ->update(['manage_agent_id' => $toAgentId, 'update_time' => $now]);
- if ($updated !== 1) {
- $notManaged[] = $sn;
- continue;
- }
- $records[] = [
- 'device_id' => $deviceId,
- 'device_sn' => $sn,
- 'goods_type' => (int) $device['goods_type'],
- 'last_agent_id' => $fromAgentId,
- 'now_agent_id' => $toAgentId,
- 'action_type' => $actionType,
- 'reason' => $reason,
- 'operator_id' => $operatorId,
- 'action_time' => $now,
- 'created_by' => $operatorId,
- 'create_time' => $now,
- ];
- $success[] = $sn;
- }
- if ($records !== []) {
- Db::table('sa_device_manager_record')->insertAll($records);
- }
- return [
- 'message' => $success === [] ? '没有可处理的新设备' : ($actionType === 1 ? '成功分配 ' : '成功回收 ') . count($success) . ' 台设备',
- 'success_sn' => $success,
- 'not_find_sn' => $notFound,
- 'not_managed_sn' => $notManaged,
- 'already_target_sn' => $alreadyTarget,
- 'distributed_sn' => $distributed,
- 'all_action_sn' => $sns,
- ];
- });
- }
- private function agentRows(array $where): array
- {
- $query = $this->accessibleAgentQuery();
- $name = trim((string) ($where['name'] ?? ''));
- if ($name !== '') {
- $query->whereLike('u.realname|u.username', '%' . $name . '%');
- }
- $phone = trim((string) ($where['phone'] ?? ''));
- if ($phone !== '') {
- $query->whereLike('u.phone', '%' . $phone . '%');
- }
- if (($where['status'] ?? '') !== '') {
- $query->where('u.status', (int) $where['status']);
- }
- $rawRows = $query->field('u.id,u.username,u.realname,u.phone,u.avatar,u.dept_id,u.status,u.create_time,d.name AS dept_name,d.level AS dept_level')->select()->toArray();
- $ids = array_map('intval', array_column($rawRows, 'id'));
- if ($ids === []) {
- return [];
- }
- $deviceMetrics = [];
- foreach (Db::table('sa_device')->whereIn('manage_agent_id', $ids)->whereNull('delete_time')
- ->field('manage_agent_id,goods_type')->select()->toArray() as $device) {
- $id = (int) $device['manage_agent_id'];
- $deviceMetrics[$id]['total'] = ($deviceMetrics[$id]['total'] ?? 0) + 1;
- if ((int) $device['goods_type'] === 107) {
- $deviceMetrics[$id]['vehicle'] = ($deviceMetrics[$id]['vehicle'] ?? 0) + 1;
- } else {
- $deviceMetrics[$id]['charging'] = ($deviceMetrics[$id]['charging'] ?? 0) + 1;
- }
- }
- $merchantMetrics = [];
- foreach (Db::table('sa_merchant')->whereIn('agent_id', $ids)->whereNull('delete_time')
- ->field('agent_id,total_income,withdrawable_amount,profit_rate')->select()->toArray() as $merchant) {
- $id = (int) $merchant['agent_id'];
- $merchantMetrics[$id]['income'] = ($merchantMetrics[$id]['income'] ?? 0) + (float) $merchant['total_income'];
- $merchantMetrics[$id]['withdrawable'] = ($merchantMetrics[$id]['withdrawable'] ?? 0) + (float) $merchant['withdrawable_amount'];
- $merchantMetrics[$id]['profit_total'] = ($merchantMetrics[$id]['profit_total'] ?? 0) + (float) $merchant['profit_rate'];
- $merchantMetrics[$id]['merchant_count'] = ($merchantMetrics[$id]['merchant_count'] ?? 0) + 1;
- }
- $deptRows = SystemDept::whereNull('delete_time')->field('id,name,level')->select()->toArray();
- $deptMap = [];
- foreach ($deptRows as $dept) {
- $deptMap[(int) $dept['id']] = $dept;
- }
- $allUsers = SystemUser::whereNull('delete_time')->field('id,dept_id')->select()->toArray();
- $currentDepth = $this->deptDepth($this->adminInfo['deptList'] ?? []);
- $rows = [];
- foreach ($rawRows as $row) {
- $id = (int) $row['id'];
- $dept = $deptMap[(int) $row['dept_id']] ?? [];
- $type = $this->agentType($this->deptDepth($dept) - $currentDepth);
- if (($where['agent_type'] ?? '') !== '' && $where['agent_type'] !== $type['code']) {
- continue;
- }
- $region = $this->regionNames($dept, $deptMap);
- $merchant = $merchantMetrics[$id] ?? [];
- $merchantCount = (int) ($merchant['merchant_count'] ?? 0);
- $rows[] = [
- 'id' => $id,
- 'name' => (string) (($row['realname'] ?? '') ?: $row['username']),
- 'username' => (string) $row['username'],
- 'phone' => (string) ($row['phone'] ?? ''),
- 'avatar' => (string) ($row['avatar'] ?? ''),
- 'agent_type' => $type['name'],
- 'agent_type_code' => $type['code'],
- 'province' => $region[0],
- 'city' => $region[1],
- 'district' => $region[2],
- 'dept_name' => (string) ($row['dept_name'] ?? ''),
- 'device_count' => (int) ($deviceMetrics[$id]['total'] ?? 0),
- 'vehicle_count' => (int) ($deviceMetrics[$id]['vehicle'] ?? 0),
- 'charging_count' => (int) ($deviceMetrics[$id]['charging'] ?? 0),
- 'subordinate_count' => $this->descendantUserCount($dept, $allUsers, $deptMap),
- 'income' => number_format((float) ($merchant['income'] ?? 0), 2, '.', ''),
- 'frozen' => '0.00',
- 'withdrawable' => number_format((float) ($merchant['withdrawable'] ?? 0), 2, '.', ''),
- 'profit_rate' => $merchantCount > 0 ? round((float) $merchant['profit_total'] / $merchantCount, 2) : 0,
- 'status' => (int) $row['status'],
- 'status_name' => (int) $row['status'] === 1 ? '有效' : '无效',
- 'created_at' => (string) ($row['create_time'] ?? ''),
- ];
- }
- $sort = (string) ($where['sort'] ?? 'default');
- usort($rows, function (array $a, array $b) use ($sort) {
- return match ($sort) {
- 'income_desc' => (float) $b['income'] <=> (float) $a['income'],
- 'income_asc' => (float) $a['income'] <=> (float) $b['income'],
- 'created_asc' => strcmp($a['created_at'], $b['created_at']),
- default => strcmp($b['created_at'], $a['created_at']) ?: ($b['id'] <=> $a['id']),
- };
- });
- return $rows;
- }
- private function accessibleAgentQuery()
- {
- $query = Db::table('sa_system_user')->alias('u')
- ->leftJoin('sa_system_dept d', 'd.id = u.dept_id')
- ->whereNull('u.delete_time')
- ->where('u.id', '<>', $this->currentAdminId());
- if ($this->currentAdminId() !== 1) {
- $dept = $this->adminInfo['deptList'] ?? [];
- if (empty($dept['id']) || !isset($dept['level'])) {
- return $query->where('u.id', 0);
- }
- $prefix = (string) $dept['level'] . (int) $dept['id'] . ',';
- $deptIds = SystemDept::whereLike('level', $prefix . '%')->column('id');
- if ($deptIds === []) {
- return $query->where('u.id', 0);
- }
- $query->whereIn('u.dept_id', $deptIds);
- }
- return $query;
- }
- private function findAccessibleAgent(int $id, bool $requireEnabled = true): array
- {
- $agent = $this->accessibleAgentQuery()->where('u.id', $id)
- ->when($requireEnabled, static fn ($query) => $query->where('u.status', 1))
- ->field('u.id,u.realname,u.username,u.phone,u.dept_id,u.status')->find();
- if (!$agent) {
- throw new ApiException($requireEnabled ? '代理不存在、已停用或无权操作' : '代理不存在或无权操作');
- }
- return $agent;
- }
- private function normalizeList(mixed $value, array $allowed): array
- {
- if (!is_array($value)) {
- return [];
- }
- $values = array_values(array_unique(array_map('strval', $value)));
- return array_values(array_intersect($allowed, $values));
- }
- private function decodeList(mixed $value, array $allowed): array
- {
- $decoded = is_string($value) ? json_decode($value, true) : $value;
- return $this->normalizeList($decoded, $allowed);
- }
- private function decodeMap(mixed $value): array
- {
- $decoded = is_string($value) ? json_decode($value, true) : $value;
- return is_array($decoded) ? $decoded : [];
- }
- private function normalizeAttachmentUrls(mixed $value): array
- {
- if (!is_array($value)) {
- throw new ApiException('合同附件参数格式错误');
- }
- if (count($value) > 20) {
- throw new ApiException('每类合同资料最多上传 20 张图片');
- }
- $urls = [];
- foreach ($value as $url) {
- if (!is_string($url)) {
- throw new ApiException('合同附件地址格式错误');
- }
- $url = trim($url);
- if ($url === '') {
- continue;
- }
- if (mb_strlen($url) > 2048 || preg_match('/^javascript:/i', $url)) {
- throw new ApiException('合同附件地址不合法');
- }
- $urls[] = $url;
- }
- return array_values(array_unique($urls));
- }
- private function decodeAttachmentUrls(mixed $value): array
- {
- $decoded = is_string($value) ? json_decode($value, true) : $value;
- return is_array($decoded) ? $this->normalizeAttachmentUrls($decoded) : [];
- }
- private function normalizeSns(array $deviceSns): array
- {
- $sns = array_values(array_unique(array_filter(array_map(
- static fn ($sn) => trim((string) $sn),
- $deviceSns
- ))));
- if ($sns === []) {
- throw new ApiException('请至少输入一个设备 SN');
- }
- if (count($sns) > 100) {
- throw new ApiException('单次最多处理 100 台设备');
- }
- return $sns;
- }
- private function deptDepth(array $dept): int
- {
- $level = trim((string) ($dept['level'] ?? ''), ',');
- return ($level === '' ? 0 : count(explode(',', $level))) + (empty($dept['id']) ? 0 : 1);
- }
- private function agentType(int $depthDiff): array
- {
- if ($depthDiff <= 1) return ['code' => 'city-partner', 'name' => '城市合伙人'];
- if ($depthDiff === 2) return ['code' => 'agent', 'name' => '代理商'];
- return ['code' => 'salesperson', 'name' => '业务员'];
- }
- private function regionNames(array $dept, array $deptMap): array
- {
- if ($dept === []) return ['--', '--', '--'];
- $ids = array_filter(array_map('intval', explode(',', trim((string) ($dept['level'] ?? ''), ','))));
- $ids[] = (int) $dept['id'];
- $names = array_values(array_filter(array_map(fn ($id) => $deptMap[$id]['name'] ?? '', $ids)));
- return [$names[0] ?? '--', $names[1] ?? '--', $names[2] ?? '--'];
- }
- private function descendantUserCount(array $dept, array $users, array $deptMap): int
- {
- if ($dept === []) return 0;
- $prefix = (string) ($dept['level'] ?? '') . (int) $dept['id'] . ',';
- $count = 0;
- foreach ($users as $user) {
- $userDept = $deptMap[(int) $user['dept_id']] ?? [];
- if ($userDept !== [] && str_starts_with((string) $userDept['level'], $prefix)) $count++;
- }
- return $count;
- }
- private function currentAdminId(): int
- {
- return (int) ($this->adminInfo['id'] ?? 0);
- }
- private function excelSafe(mixed $value): string
- {
- $value = (string) $value;
- return preg_match('/^[=+\-@]/u', $value) === 1 ? "'" . $value : $value;
- }
- }
|