242 lines
8.4 KiB
PHP
242 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Exports\SegmentContractsExport;
|
|
use App\Http\Requests\ExportSegmentContractsRequest;
|
|
use App\Http\Requests\StoreSegmentRequest;
|
|
use App\Http\Requests\UpdateSegmentRequest;
|
|
use App\Models\Client;
|
|
use App\Models\Contract;
|
|
use App\Models\Segment;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Inertia;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
|
|
class SegmentController extends Controller
|
|
{
|
|
public function index()
|
|
{
|
|
// Fetch active segments with number of active contracts and total balance sum of those contracts
|
|
// A contract is considered in a segment when the pivot is active=true.
|
|
$segments = Segment::query()
|
|
->where('active', true)
|
|
->withCount(['contracts as contracts_count' => function ($q) {
|
|
// On some drivers, wherePivot can compile oddly inside withCount; target the pivot table directly
|
|
$q->where('contract_segment.active', '=', 1);
|
|
}])
|
|
->get(['id', 'name', 'description']);
|
|
|
|
// Compute total balance per segment for active contracts
|
|
$balances = DB::table('segments')
|
|
->join('contract_segment', 'contract_segment.segment_id', '=', 'segments.id')
|
|
->join('contracts', 'contracts.id', '=', 'contract_segment.contract_id')
|
|
->leftJoin('accounts', 'accounts.contract_id', '=', 'contracts.id')
|
|
->where('segments.active', '=', 1)
|
|
->where('contract_segment.active', '=', 1)
|
|
->groupBy('segments.id')
|
|
->pluck(DB::raw('COALESCE(SUM(accounts.balance_amount),0) as total_balance'), 'segments.id');
|
|
|
|
$segments = $segments->map(function ($seg) use ($balances) {
|
|
$seg->total_balance = (string) ($balances[$seg->id] ?? 0);
|
|
|
|
return $seg;
|
|
});
|
|
|
|
return Inertia::render('Segments/Index', [
|
|
'segments' => $segments,
|
|
]);
|
|
}
|
|
|
|
public function show(Segment $segment)
|
|
{
|
|
$search = request('search');
|
|
$clientFilter = request('client') ?? request('client_id');
|
|
$perPage = request()->integer('perPage', request()->integer('per_page', 15));
|
|
$perPage = max(1, min(200, $perPage));
|
|
|
|
$contracts = $this->buildContractsQuery($segment, $search, $clientFilter)
|
|
->paginate($perPage)
|
|
->withQueryString();
|
|
|
|
$contracts = $this->hydrateClientShortcut($contracts);
|
|
|
|
$clients = Client::query()
|
|
->whereHas('clientCases.contracts.segments', function ($q) use ($segment) {
|
|
$q->where('segments.id', $segment->id)
|
|
->where('contract_segment.active', '=', 1);
|
|
})
|
|
->with(['person:id,full_name'])
|
|
->get(['uuid', 'person_id'])
|
|
->map(function ($c) {
|
|
return [
|
|
'uuid' => (string) $c->uuid,
|
|
'name' => (string) optional($c->person)->full_name,
|
|
];
|
|
})
|
|
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
|
|
->values();
|
|
|
|
return Inertia::render('Segments/Show', [
|
|
'segment' => $segment->only(['id', 'name', 'description']),
|
|
'contracts' => $contracts,
|
|
'clients' => $clients,
|
|
]);
|
|
}
|
|
|
|
public function export(ExportSegmentContractsRequest $request, Segment $segment)
|
|
{
|
|
$data = $request->validated();
|
|
$client = $this->resolveClient($data['client'] ?? null);
|
|
$columns = array_values(array_unique($data['columns']));
|
|
$query = $this->buildContractsQuery(
|
|
$segment,
|
|
$data['search'] ?? null,
|
|
$data['client'] ?? null
|
|
);
|
|
|
|
if (($data['scope'] ?? ExportSegmentContractsRequest::SCOPE_ALL) === ExportSegmentContractsRequest::SCOPE_CURRENT) {
|
|
$page = max(1, (int) ($data['page'] ?? 1));
|
|
$perPage = max(1, min(200, (int) ($data['per_page'] ?? 15)));
|
|
$query->forPage($page, $perPage);
|
|
}
|
|
|
|
$filename = $this->buildExportFilename($segment, $client);
|
|
|
|
return Excel::download(new SegmentContractsExport($query, $columns), $filename);
|
|
}
|
|
|
|
private function resolveClient(?string $identifier): ?Client
|
|
{
|
|
if (empty($identifier)) {
|
|
return null;
|
|
}
|
|
|
|
$query = Client::query()->with(['person:id,full_name']);
|
|
|
|
if (Str::isUuid($identifier)) {
|
|
$query->where('uuid', $identifier);
|
|
} elseif (is_numeric($identifier)) {
|
|
$query->where('id', (int) $identifier);
|
|
} else {
|
|
$query->where('uuid', $identifier);
|
|
}
|
|
|
|
return $query->first();
|
|
}
|
|
|
|
private function buildExportFilename(Segment $segment, ?Client $client): string
|
|
{
|
|
$datePrefix = now()->format('dmy');
|
|
$segmentName = $this->slugify($segment->name ?? 'segment');
|
|
$base = sprintf('%s_%s-Pogodbe', $datePrefix, $segmentName);
|
|
|
|
if ($client && $client->person?->full_name) {
|
|
$clientName = $this->slugify($client->person->full_name);
|
|
|
|
return sprintf('%s_%s.xlsx', $base, $clientName);
|
|
}
|
|
|
|
return sprintf('%s.xlsx', $base);
|
|
}
|
|
|
|
private function slugify(string $value): string
|
|
{
|
|
$slug = trim(preg_replace('/[^a-zA-Z0-9]+/', '-', $value), '-');
|
|
|
|
return $slug !== '' ? $slug : 'data';
|
|
}
|
|
|
|
public function settings(Request $request)
|
|
{
|
|
return Inertia::render('Settings/Segments/Index', [
|
|
'segments' => Segment::query()->get(),
|
|
]);
|
|
}
|
|
|
|
public function store(StoreSegmentRequest $request)
|
|
{
|
|
$data = $request->validated();
|
|
Segment::create([
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
'active' => $data['active'] ?? true,
|
|
]);
|
|
|
|
return to_route('settings.segments')->with('success', 'Segment created');
|
|
}
|
|
|
|
public function update(UpdateSegmentRequest $request, Segment $segment)
|
|
{
|
|
$data = $request->validated();
|
|
$segment->update([
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
'active' => $data['active'] ?? $segment->active,
|
|
'exclude' => $data['exclude'] ?? $segment->exclude,
|
|
]);
|
|
|
|
return to_route('settings.segments')->with('success', 'Segment updated');
|
|
}
|
|
|
|
private function buildContractsQuery(Segment $segment, ?string $search, ?string $clientFilter): Builder
|
|
{
|
|
$query = Contract::query()
|
|
->whereHas('segments', function ($q) use ($segment) {
|
|
$q->where('segments.id', $segment->id)
|
|
->where('contract_segment.active', '=', 1);
|
|
})
|
|
->with([
|
|
'clientCase.person',
|
|
'clientCase.client.person',
|
|
'type',
|
|
'account',
|
|
])
|
|
->latest('id');
|
|
|
|
if (! empty($clientFilter)) {
|
|
$query->whereHas('clientCase.client', function ($q) use ($clientFilter) {
|
|
if (is_numeric($clientFilter)) {
|
|
$q->where('clients.id', (int) $clientFilter);
|
|
} else {
|
|
$q->where('clients.uuid', $clientFilter);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (! empty($search)) {
|
|
$query->where(function ($qq) use ($search) {
|
|
$qq->where('contracts.reference', 'ilike', '%'.$search.'%')
|
|
->orWhereHas('clientCase.person', function ($p) use ($search) {
|
|
$p->where('full_name', 'ilike', '%'.$search.'%');
|
|
})
|
|
->orWhereHas('clientCase.client.person', function ($p) use ($search) {
|
|
$p->where('full_name', 'ilike', '%'.$search.'%');
|
|
});
|
|
});
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function hydrateClientShortcut(LengthAwarePaginator $contracts): LengthAwarePaginator
|
|
{
|
|
$items = collect($contracts->items());
|
|
$items->each(function (Contract $contract) {
|
|
if ($contract->relationLoaded('clientCase') && $contract->clientCase) {
|
|
$contract->setRelation('client', $contract->clientCase->client);
|
|
}
|
|
});
|
|
|
|
if (method_exists($contracts, 'setCollection')) {
|
|
$contracts->setCollection($items);
|
|
}
|
|
|
|
return $contracts;
|
|
}
|
|
}
|