feat: enhance agent settlement features and improve data access controls
- Added new section in AGENTS.md detailing learned workspace facts for better understanding of settlement processes. - Updated AgentNodeDestroyController to remove unnecessary checks for admin users. - Enhanced AgentSettlement controllers to assert permissions for finance adjustments and bill operations. - Improved query scopes in AgentSettlement services to ensure proper data access based on admin roles. - Refactored methods in SettlementPartyEnrichment for better bill row enrichment and data handling. - Introduced new methods in AdminAgentSettlementScope for managing agent node visibility and finance adjustments.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\AgentSettlement;
|
||||
|
||||
use App\Models\AgentNode;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/** 代理账单:汇总下级代理在本期保留的占成。 */
|
||||
final class SettlementBillDownlineShareBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SettlementPartyEnrichment $partyEnrichment,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* total: int,
|
||||
* items: list<array{owner_id: int, owner_label: string, share_profit: int}>
|
||||
* }
|
||||
*/
|
||||
public function forBill(object $bill): array
|
||||
{
|
||||
if ((string) $bill->bill_type !== 'agent' || (string) $bill->owner_type !== 'agent') {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$ownerId = (int) $bill->owner_id;
|
||||
$periodId = (int) $bill->settlement_period_id;
|
||||
if ($ownerId <= 0 || $periodId <= 0) {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$owner = AgentNode::query()->find($ownerId);
|
||||
if ($owner === null) {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$descendantIds = AgentNode::query()
|
||||
->where('admin_site_id', (int) $owner->admin_site_id)
|
||||
->where('id', '!=', $ownerId)
|
||||
->where('path', 'like', $owner->path.'%')
|
||||
->pluck('id')
|
||||
->map(static fn ($id): int => (int) $id)
|
||||
->all();
|
||||
|
||||
if ($descendantIds === []) {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$rows = DB::table('settlement_bills')
|
||||
->where('settlement_period_id', $periodId)
|
||||
->where('bill_type', 'agent')
|
||||
->where('owner_type', 'agent')
|
||||
->whereIn('owner_id', $descendantIds)
|
||||
->orderBy('owner_id')
|
||||
->get(['owner_id', 'meta_json']);
|
||||
|
||||
if ($rows->isEmpty()) {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$agentIds = $rows->pluck('owner_id')->map(static fn ($id): int => (int) $id)->all();
|
||||
$agents = $this->partyEnrichment->loadAgents($agentIds);
|
||||
|
||||
$items = [];
|
||||
$total = 0;
|
||||
foreach ($rows as $row) {
|
||||
$shareProfit = $this->shareProfitFromMeta($row->meta_json ?? null);
|
||||
if ($shareProfit === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$agentId = (int) $row->owner_id;
|
||||
$items[] = [
|
||||
'owner_id' => $agentId,
|
||||
'owner_label' => $this->partyEnrichment->formatAgent($agents->get($agentId), $agentId),
|
||||
'share_profit' => $shareProfit,
|
||||
];
|
||||
$total += $shareProfit;
|
||||
}
|
||||
|
||||
usort($items, static fn (array $a, array $b): int => $b['share_profit'] <=> $a['share_profit']
|
||||
?: $a['owner_label'] <=> $b['owner_label']);
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
private function shareProfitFromMeta(mixed $metaJson): int
|
||||
{
|
||||
if ($metaJson === null || $metaJson === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$decoded = is_string($metaJson) ? json_decode($metaJson, true) : $metaJson;
|
||||
|
||||
return is_array($decoded) ? (int) ($decoded['share_profit'] ?? 0) : 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user