ownedQuery($owner); if (!empty($params['name'])) $query->where('name', 'like', '%' . $params['name'] . '%'); $total = (clone $query)->count(); $list = $query->orderByDesc('is_default')->orderByDesc('id')->forPage($page, $limit)->get(); if ($owner) { $list->each(fn (AgentFlowCommissionProfile $profile) => $profile->setAttribute('editable', true)); } return compact('total', 'page', 'limit', 'list'); } public function paginateSelectable(Agent $agent, array $params): array { $agent->loadMissing('flowCommissionProfile'); if (!$agent->flowCommissionProfile) throw new \RuntimeException('当前代理未关联流水佣金方案'); $page = max(1, (int) ($params['page'] ?? 1)); $limit = min(100, max(1, (int) ($params['limit'] ?? 20))); $query = AgentFlowCommissionProfile::query()->where(function (Builder $query) use ($agent) { $query->whereNull('owner_agent_id')->orWhere('owner_agent_id', $agent->id); }); foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) { $query->where($field, '<=', (string) $agent->flowCommissionProfile->{$field}); } if (!empty($params['name'])) $query->where('name', 'like', '%' . $params['name'] . '%'); $total = (clone $query)->count(); $list = $query->orderByRaw('owner_agent_id is null')->orderByDesc('is_default')->orderByDesc('id') ->forPage($page, $limit)->get(); $list->each(function (AgentFlowCommissionProfile $profile) use ($agent) { $profile->setAttribute('editable', $profile->isOwnedBy((int) $agent->id)); }); return compact('total', 'page', 'limit', 'list'); } public function save(array $data, ?int $adminId = null, ?Agent $owner = null): AgentFlowCommissionProfile { return DB::transaction(function () use ($data, $adminId, $owner) { $profiles = $this->ownedQuery($owner)->orderBy('id')->lockForUpdate()->get(['id', 'is_default']); $profile = empty($data['id']) ? new AgentFlowCommissionProfile() : AgentFlowCommissionProfile::query()->lockForUpdate()->findOrFail($data['id']); if ($profile->exists) $this->assertOwned($profile, $owner); else $profile->owner_agent_id = $owner?->id; $oldDefaultId = $profiles->firstWhere('is_default', 1)?->id; $isDefault = !empty($data['is_default']); if ($profile->exists && (int) $oldDefaultId === (int) $profile->id && !$isDefault) { throw new \RuntimeException('默认流水方案必须始终保留一条,请先将其他方案设为默认'); } $name = trim((string) $data['name']); if ($name === '') throw new \InvalidArgumentException('流水方案名称不能为空'); $duplicate = $this->ownedQuery($owner)->where('name', $name) ->when($profile->exists, fn ($query) => $query->where('id', '<>', $profile->id))->exists(); if ($duplicate) throw new \InvalidArgumentException('流水方案名称已存在'); $profile->name = $name; foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) $profile->{$field} = (string) $data[$field]; if ($owner) $this->assertRatesWithinOwner($owner, $profile); $willBeDefault = $isDefault || (!$profile->exists && !$oldDefaultId); $profile->is_default = $willBeDefault ? 1 : 0; $snapshotChanged = !$profile->exists || $profile->isDirty(AgentFlowCommissionProfile::RATE_FIELDS); $affectedAgentIds = []; if ($snapshotChanged && $profile->exists) { $affectedAgentIds = Agent::query()->where('flow_commission_profile_id', $profile->id) ->pluck('id')->map(fn ($id) => (int) $id)->all(); } if ($willBeDefault) { $defaultAgentIds = $this->defaultMigrationQuery($oldDefaultId, $profile, $owner) ->pluck('id')->map(fn ($id) => (int) $id)->all(); $affectedAgentIds = array_values(array_unique(array_merge($affectedAgentIds, $defaultAgentIds))); } $this->assertHierarchy($affectedAgentIds, $profile); $profile->save(); if ($willBeDefault) { $this->ownedQuery($owner)->where('id', '<>', $profile->id)->update(['is_default' => 0]); $this->defaultMigrationQuery($oldDefaultId, $profile, $owner) ->update(['flow_commission_profile_id' => $profile->id]); } $this->assignments->assignMany($affectedAgentIds, $profile, now()->toDateString(), $adminId); return $profile->fresh(); }); } public function delete(int $id, ?int $adminId = null, ?Agent $owner = null): void { DB::transaction(function () use ($id, $adminId, $owner) { $profile = AgentFlowCommissionProfile::query()->lockForUpdate()->findOrFail($id); $this->assertOwned($profile, $owner); if ((int) $profile->is_default === 1) throw new \RuntimeException('默认流水方案不能删除'); $default = $this->ownedQuery($owner)->where('is_default', 1)->lockForUpdate()->first(); if (!$default) throw new \RuntimeException('缺少默认流水方案,不能删除'); $agentIds = $this->agentsUsingProfile($profile->id, $owner); $this->assertHierarchy($agentIds, $default); $this->assignments->assignMany($agentIds, $default, now()->toDateString(), $adminId); Agent::query()->where('flow_commission_profile_id', $profile->id) ->when($owner, fn ($query) => $query->where('parent_id', $owner->id)) ->update(['flow_commission_profile_id' => $default->id]); $profile->delete(); }); } private function ownedQuery(?Agent $owner): Builder { $query = AgentFlowCommissionProfile::query(); return $owner ? $query->where('owner_agent_id', $owner->id) : $query->whereNull('owner_agent_id'); } private function defaultMigrationQuery(?int $oldDefaultId, AgentFlowCommissionProfile $profile, ?Agent $owner): Builder { $query = Agent::query()->where(function (Builder $query) use ($oldDefaultId, $profile) { $query->whereNull('flow_commission_profile_id'); if ($oldDefaultId && (int) $oldDefaultId !== (int) $profile->id) { $query->orWhere('flow_commission_profile_id', $oldDefaultId); } }); return $owner ? $query->where('parent_id', $owner->id) : $query; } private function agentsUsingProfile(int $profileId, ?Agent $owner): array { $ids = Agent::query()->where('flow_commission_profile_id', $profileId) ->pluck('id')->map(fn ($agentId) => (int) $agentId); if ($owner) { $childIds = Agent::query()->where('parent_id', $owner->id) ->pluck('id')->map(fn ($agentId) => (int) $agentId); if ($ids->diff($childIds)->isNotEmpty()) { throw new \RuntimeException('该流水方案仍被非直属下级使用,不能删除'); } return $ids->intersect($childIds)->values()->all(); } return $ids->all(); } private function assertOwned(AgentFlowCommissionProfile $profile, ?Agent $owner): void { if ($owner) { if (!$profile->isOwnedBy((int) $owner->id)) { throw new \RuntimeException('无权操作该流水方案'); } return; } if (!$profile->isOfficial()) { throw new \RuntimeException('总后台只能维护官方流水方案'); } } private function assertRatesWithinOwner(Agent $owner, AgentFlowCommissionProfile $profile): void { $owner->loadMissing('flowCommissionProfile'); if (!$owner->flowCommissionProfile) throw new \RuntimeException('当前代理未关联流水佣金方案'); foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) { if (bccomp((string) $profile->{$field}, (string) $owner->flowCommissionProfile->{$field}, 4) > 0) { throw new \RuntimeException('下级流水方案不能高于当前代理方案'); } } } private function assertHierarchy(array $agentIds, AgentFlowCommissionProfile $candidate): void { $agentIds = array_values(array_unique(array_map('intval', $agentIds))); if ($agentIds === []) return; $affected = array_fill_keys($agentIds, true); $agents = Agent::query()->with([ 'parent.flowCommissionProfile', 'children.flowCommissionProfile', ])->whereIn('id', $agentIds)->get(); foreach ($agents as $agent) { if ($agent->parent) { foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) { if (!isset($affected[(int) $agent->parent->id]) && !$agent->parent->flowCommissionProfile) { throw new \RuntimeException('上级代理未关联流水佣金方案'); } $parentRate = isset($affected[(int) $agent->parent->id]) ? (string) $candidate->{$field} : (string) $agent->parent->flowCommissionProfile->{$field}; if (bccomp((string) $candidate->{$field}, $parentRate, 4) > 0) { throw new \RuntimeException('修改后的流水方案会高于上级代理方案'); } } } foreach ($agent->children as $child) { foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) { if (!isset($affected[(int) $child->id]) && !$child->flowCommissionProfile) { throw new \RuntimeException('下级代理未关联流水佣金方案'); } $childRate = isset($affected[(int) $child->id]) ? (string) $candidate->{$field} : (string) $child->flowCommissionProfile->{$field}; if (bccomp((string) $candidate->{$field}, $childRate, 4) < 0) { throw new \RuntimeException('修改后的流水方案会低于下级代理方案'); } } } } } }