|
@@ -4,6 +4,7 @@ namespace App\Services\Agent;
|
|
|
|
|
|
|
|
use App\Models\Agent\Agent;
|
|
use App\Models\Agent\Agent;
|
|
|
use App\Models\Agent\AgentFlowCommissionProfile;
|
|
use App\Models\Agent\AgentFlowCommissionProfile;
|
|
|
|
|
+use Illuminate\Database\Eloquent\Builder;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
class AgentFlowCommissionProfileService
|
|
class AgentFlowCommissionProfileService
|
|
@@ -12,86 +13,168 @@ class AgentFlowCommissionProfileService
|
|
|
{
|
|
{
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function paginate(array $params): array
|
|
|
|
|
|
|
+ public function paginate(array $params, ?Agent $owner = null): array
|
|
|
{
|
|
{
|
|
|
- $page = max(1, (int)($params['page'] ?? 1));
|
|
|
|
|
- $limit = min(100, max(1, (int)($params['limit'] ?? 20)));
|
|
|
|
|
- $query = AgentFlowCommissionProfile::query();
|
|
|
|
|
|
|
+ $page = max(1, (int) ($params['page'] ?? 1));
|
|
|
|
|
+ $limit = min(100, max(1, (int) ($params['limit'] ?? 20)));
|
|
|
|
|
+ $query = $this->ownedQuery($owner);
|
|
|
if (!empty($params['name'])) $query->where('name', 'like', '%' . $params['name'] . '%');
|
|
if (!empty($params['name'])) $query->where('name', 'like', '%' . $params['name'] . '%');
|
|
|
$total = (clone $query)->count();
|
|
$total = (clone $query)->count();
|
|
|
$list = $query->orderByDesc('is_default')->orderByDesc('id')->forPage($page, $limit)->get();
|
|
$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');
|
|
return compact('total', 'page', 'limit', 'list');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function save(array $data, ?int $adminId = null): AgentFlowCommissionProfile
|
|
|
|
|
|
|
+ public function paginateSelectable(Agent $agent, array $params): array
|
|
|
{
|
|
{
|
|
|
- return DB::transaction(function () use ($data, $adminId) {
|
|
|
|
|
- $profiles = AgentFlowCommissionProfile::query()->orderBy('id')->lockForUpdate()->get(['id', 'is_default']);
|
|
|
|
|
|
|
+ $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'])
|
|
$profile = empty($data['id'])
|
|
|
? new AgentFlowCommissionProfile()
|
|
? new AgentFlowCommissionProfile()
|
|
|
: AgentFlowCommissionProfile::query()->lockForUpdate()->findOrFail($data['id']);
|
|
: 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;
|
|
$oldDefaultId = $profiles->firstWhere('is_default', 1)?->id;
|
|
|
$isDefault = !empty($data['is_default']);
|
|
$isDefault = !empty($data['is_default']);
|
|
|
- if ($profile->exists && (int)$oldDefaultId === (int)$profile->id && !$isDefault) {
|
|
|
|
|
|
|
+ if ($profile->exists && (int) $oldDefaultId === (int) $profile->id && !$isDefault) {
|
|
|
throw new \RuntimeException('默认流水方案必须始终保留一条,请先将其他方案设为默认');
|
|
throw new \RuntimeException('默认流水方案必须始终保留一条,请先将其他方案设为默认');
|
|
|
}
|
|
}
|
|
|
- $name = trim((string)$data['name']);
|
|
|
|
|
|
|
+ $name = trim((string) $data['name']);
|
|
|
if ($name === '') throw new \InvalidArgumentException('流水方案名称不能为空');
|
|
if ($name === '') throw new \InvalidArgumentException('流水方案名称不能为空');
|
|
|
- $duplicate = AgentFlowCommissionProfile::query()->where('name', $name)
|
|
|
|
|
- ->when($profile->exists, fn($query) => $query->where('id', '<>', $profile->id))->exists();
|
|
|
|
|
|
|
+ $duplicate = $this->ownedQuery($owner)->where('name', $name)
|
|
|
|
|
+ ->when($profile->exists, fn ($query) => $query->where('id', '<>', $profile->id))->exists();
|
|
|
if ($duplicate) throw new \InvalidArgumentException('流水方案名称已存在');
|
|
if ($duplicate) throw new \InvalidArgumentException('流水方案名称已存在');
|
|
|
|
|
|
|
|
$profile->name = $name;
|
|
$profile->name = $name;
|
|
|
- foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) $profile->{$field} = (string)$data[$field];
|
|
|
|
|
|
|
+ foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) $profile->{$field} = (string) $data[$field];
|
|
|
|
|
+ if ($owner) $this->assertRatesWithinOwner($owner, $profile);
|
|
|
$willBeDefault = $isDefault || (!$profile->exists && !$oldDefaultId);
|
|
$willBeDefault = $isDefault || (!$profile->exists && !$oldDefaultId);
|
|
|
$profile->is_default = $willBeDefault ? 1 : 0;
|
|
$profile->is_default = $willBeDefault ? 1 : 0;
|
|
|
$snapshotChanged = !$profile->exists || $profile->isDirty(AgentFlowCommissionProfile::RATE_FIELDS);
|
|
$snapshotChanged = !$profile->exists || $profile->isDirty(AgentFlowCommissionProfile::RATE_FIELDS);
|
|
|
$affectedAgentIds = [];
|
|
$affectedAgentIds = [];
|
|
|
if ($snapshotChanged && $profile->exists) {
|
|
if ($snapshotChanged && $profile->exists) {
|
|
|
$affectedAgentIds = Agent::query()->where('flow_commission_profile_id', $profile->id)
|
|
$affectedAgentIds = Agent::query()->where('flow_commission_profile_id', $profile->id)
|
|
|
- ->pluck('id')->map(fn($id) => (int)$id)->all();
|
|
|
|
|
|
|
+ ->pluck('id')->map(fn ($id) => (int) $id)->all();
|
|
|
}
|
|
}
|
|
|
if ($willBeDefault) {
|
|
if ($willBeDefault) {
|
|
|
- $defaultAgentIds = Agent::query()->where(function ($query) use ($oldDefaultId, $profile) {
|
|
|
|
|
- $query->whereNull('flow_commission_profile_id');
|
|
|
|
|
- if ($oldDefaultId && (int)$oldDefaultId !== (int)$profile->id) {
|
|
|
|
|
- $query->orWhere('flow_commission_profile_id', $oldDefaultId);
|
|
|
|
|
- }
|
|
|
|
|
- })->pluck('id')->map(fn($id) => (int)$id)->all();
|
|
|
|
|
|
|
+ $defaultAgentIds = $this->defaultMigrationQuery($oldDefaultId, $profile, $owner)
|
|
|
|
|
+ ->pluck('id')->map(fn ($id) => (int) $id)->all();
|
|
|
$affectedAgentIds = array_values(array_unique(array_merge($affectedAgentIds, $defaultAgentIds)));
|
|
$affectedAgentIds = array_values(array_unique(array_merge($affectedAgentIds, $defaultAgentIds)));
|
|
|
}
|
|
}
|
|
|
$this->assertHierarchy($affectedAgentIds, $profile);
|
|
$this->assertHierarchy($affectedAgentIds, $profile);
|
|
|
$profile->save();
|
|
$profile->save();
|
|
|
|
|
|
|
|
if ($willBeDefault) {
|
|
if ($willBeDefault) {
|
|
|
- AgentFlowCommissionProfile::query()->where('id', '<>', $profile->id)->update(['is_default' => 0]);
|
|
|
|
|
- Agent::query()->where(function ($query) use ($oldDefaultId) {
|
|
|
|
|
- $query->whereNull('flow_commission_profile_id');
|
|
|
|
|
- if ($oldDefaultId) $query->orWhere('flow_commission_profile_id', $oldDefaultId);
|
|
|
|
|
- })->update(['flow_commission_profile_id' => $profile->id]);
|
|
|
|
|
|
|
+ $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);
|
|
$this->assignments->assignMany($affectedAgentIds, $profile, now()->toDateString(), $adminId);
|
|
|
return $profile->fresh();
|
|
return $profile->fresh();
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function delete(int $id, ?int $adminId = null): void
|
|
|
|
|
|
|
+ public function delete(int $id, ?int $adminId = null, ?Agent $owner = null): void
|
|
|
{
|
|
{
|
|
|
- DB::transaction(function () use ($id, $adminId) {
|
|
|
|
|
|
|
+ DB::transaction(function () use ($id, $adminId, $owner) {
|
|
|
$profile = AgentFlowCommissionProfile::query()->lockForUpdate()->findOrFail($id);
|
|
$profile = AgentFlowCommissionProfile::query()->lockForUpdate()->findOrFail($id);
|
|
|
- if ((int)$profile->is_default === 1) throw new \RuntimeException('默认流水方案不能删除');
|
|
|
|
|
- $default = AgentFlowCommissionProfile::query()->where('is_default', 1)->lockForUpdate()->first();
|
|
|
|
|
|
|
+ $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('缺少默认流水方案,不能删除');
|
|
if (!$default) throw new \RuntimeException('缺少默认流水方案,不能删除');
|
|
|
- $agentIds = Agent::query()->where('flow_commission_profile_id', $profile->id)
|
|
|
|
|
- ->pluck('id')->map(fn($agentId) => (int)$agentId)->all();
|
|
|
|
|
|
|
+ $agentIds = $this->agentsUsingProfile($profile->id, $owner);
|
|
|
$this->assertHierarchy($agentIds, $default);
|
|
$this->assertHierarchy($agentIds, $default);
|
|
|
$this->assignments->assignMany($agentIds, $default, now()->toDateString(), $adminId);
|
|
$this->assignments->assignMany($agentIds, $default, now()->toDateString(), $adminId);
|
|
|
Agent::query()->where('flow_commission_profile_id', $profile->id)
|
|
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]);
|
|
->update(['flow_commission_profile_id' => $default->id]);
|
|
|
$profile->delete();
|
|
$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
|
|
private function assertHierarchy(array $agentIds, AgentFlowCommissionProfile $candidate): void
|
|
|
{
|
|
{
|
|
|
$agentIds = array_values(array_unique(array_map('intval', $agentIds)));
|
|
$agentIds = array_values(array_unique(array_map('intval', $agentIds)));
|
|
@@ -104,26 +187,26 @@ class AgentFlowCommissionProfileService
|
|
|
foreach ($agents as $agent) {
|
|
foreach ($agents as $agent) {
|
|
|
if ($agent->parent) {
|
|
if ($agent->parent) {
|
|
|
foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) {
|
|
foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) {
|
|
|
- if (!isset($affected[(int)$agent->parent->id]) && !$agent->parent->flowCommissionProfile) {
|
|
|
|
|
|
|
+ if (!isset($affected[(int) $agent->parent->id]) && !$agent->parent->flowCommissionProfile) {
|
|
|
throw new \RuntimeException('上级代理未关联流水佣金方案');
|
|
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) {
|
|
|
|
|
|
|
+ $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('修改后的流水方案会高于上级代理方案');
|
|
throw new \RuntimeException('修改后的流水方案会高于上级代理方案');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
foreach ($agent->children as $child) {
|
|
foreach ($agent->children as $child) {
|
|
|
foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) {
|
|
foreach (AgentFlowCommissionProfile::RATE_FIELDS as $field) {
|
|
|
- if (!isset($affected[(int)$child->id]) && !$child->flowCommissionProfile) {
|
|
|
|
|
|
|
+ if (!isset($affected[(int) $child->id]) && !$child->flowCommissionProfile) {
|
|
|
throw new \RuntimeException('下级代理未关联流水佣金方案');
|
|
throw new \RuntimeException('下级代理未关联流水佣金方案');
|
|
|
}
|
|
}
|
|
|
- $childRate = isset($affected[(int)$child->id])
|
|
|
|
|
- ? (string)$candidate->{$field}
|
|
|
|
|
- : (string)$child->flowCommissionProfile->{$field};
|
|
|
|
|
- if (bccomp((string)$candidate->{$field}, $childRate, 4) < 0) {
|
|
|
|
|
|
|
+ $childRate = isset($affected[(int) $child->id])
|
|
|
|
|
+ ? (string) $candidate->{$field}
|
|
|
|
|
+ : (string) $child->flowCommissionProfile->{$field};
|
|
|
|
|
+ if (bccomp((string) $candidate->{$field}, $childRate, 4) < 0) {
|
|
|
throw new \RuntimeException('修改后的流水方案会低于下级代理方案');
|
|
throw new \RuntimeException('修改后的流水方案会低于下级代理方案');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|