Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
peripherad
/
back
/
app
/
Http
/
Controllers
/
Api
/
V1
:
ProjectController.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php declare(strict_types=1); namespace App\Http\Controllers\Api\V1; use App\Attributes\OpenApiResponse; use App\Domain\Payment\Enums\PaymentStatusEnum; use App\Domain\Payment\Enums\PaymentTypeEnum; use App\Domain\Project\Enums\ProjectStatusEnum; use App\Domain\Project\Requests\CreateProjectRequest; use App\Domain\Project\Requests\UpdateProjectRequest; use App\Domain\Project\Services\ProjectService; use App\Exports\PaymentsExport; use App\Http\Controllers\Api\ApiController; use App\Http\Resources\ProjectGroupResource; use App\Http\Resources\ProjectResource; use App\Http\Resources\ProjectWithoutGroupsResource; use App\Http\Resources\ShowProjectResource; use App\Http\Resources\StatsProjectResource; use App\Imports\ProjectsImport; use App\Models\Payment; use App\Models\PaymentDistribution; use App\Models\Project; use App\Responses\ResponseDto; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Knuckles\Scribe\Attributes\Group; use Maatwebsite\Excel\Facades\Excel; /** * Контроллер для управления проектами. */ class ProjectController extends ApiController { protected ProjectService $projectService; public function __construct(ProjectService $projectService) { $this->projectService = $projectService; } /** * Получить список всех проектов с платежами. * * @queryParam search string Поисковый запрос для фильтрации проектов. Пример: "Laravel" * @queryParam project_group_id int ID категории для фильтрации проектов. Пример: 3 * @queryParam payment_id int ID платежа. Пример: 3 */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function projectsWithPayments(int $modelID, Request $request): ResponseDto { $projects = $this->projectService->getAll($modelID, $request, true) ->with(['paymentDistributions', 'paymentDistributions.payment.contragent']) ->get(); $totalProjects = 0; $totalIncome = 0; $totalOutcome = 0; $result = []; foreach ($projects as $i => $project) { $income = $outcome = $paymentsNum = $process = 0; $payments = []; /** @var Project $project */ foreach ($project->paymentDistributions as $pd) { $totalProjects++; if ($pd->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_RECEPTION->value) { $totalIncome += $pd->payment->amount; $income += $pd->payment->amount; } elseif ($pd->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_PAYMENT->value) { $outcome += $pd->payment->amount; $totalOutcome += $pd->payment->amount; } if (!in_array($pd->payment->status, [ PaymentStatusEnum::STATUS_PAID->value, PaymentStatusEnum::STATUS_RECEIVED->value, ], true)) { $paymentsNum++; $process += $pd->payment->amount; } $payments[] = [ 'name' => $pd->payment->name, 'article' => $pd->payment->articleTitle, 'amount' => $pd->payment->amount, 'contragent' => $pd->payment->contragent, ]; } $project->forceFill([ 'total_income' => $income, 'total_outcome' => $outcome, 'in_progress' => $process, 'payments' => $payments, 'payments_num' => $paymentsNum, ]); $project = $project->toArray(); unset($project['payment_distributions']); $result[] = $project; } return new ResponseDto( data: [ 'projects' => $result, 'overall' => [ 'income' => $totalIncome, 'outcome' => $totalOutcome, 'projects_num' => $totalProjects, ] ], status: true ); } /** * Получить список всех проектов. * * @queryParam search string Поисковый запрос для фильтрации проектов. Пример: "Laravel" * @queryParam project_group_id int ID категории для фильтрации проектов. Пример: 3 * @queryParam payment_id int ID платежа. Пример: 3 */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function index(int $modelID, Request $request): ResponseDto { return new ResponseDto( data: ProjectResource::collection($this->projectService->getAll($modelID, $request)->paginate(4000)), status: true ); } public function listExceptDelete(int $modelId, Request $request) { $projectId = $request->query('projectId'); return new ResponseDto( data: ProjectResource::collection($this->projectService->getListExceptDelete($modelId, $projectId)), status: true ); } /** * Экспортировать платежи в Excel, результат = url для скачивания * * @queryParam ids array - список ID проектов для экспорта */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function excelExport(int $modelID, Request $request): ResponseDto { $ids = $request->get('ids', []); $export = new PaymentsExport(Payment::whereHas('distributions', function ($query) use ($ids): void { $query->whereIn('project_id', $ids); }), $ids); $filename = 'payments-' . date('Y-m-d-H-i-s') . '.xlsx'; Excel::store($export, $filename, 'public'); return new ResponseDto( data: ['url' => asset("storage/{$filename}")], status: true ); } /** * Получить список всех проектов с группировкой по группам и еще список проектов вне групп (через generic группу) * * @queryParam search string Поисковый запрос для фильтрации проектов. Пример: "Laravel" * @queryParam sorting array сортировка, пример sort=article&sort_direction=ASC */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function groupedList(int $modelID, Request $request): ResponseDto { $projectsGroups = $this->projectService->getProjectGroupList($modelID, $request); $projectsWithoutGroups = $this->projectService->getProjectsWithoutGroupsList($modelID, $request); $stats = [ 'in_work' => 0, 'income' => 0, 'outcome' => 0, ]; $sort = $request->get('sort'); $direction = $request->get('sort_direction', 'ASC'); if ($sort) { if ($sort == 'name') { $projectsGroups = $projectsGroups->map(function ($group) use ($direction) { if (!$group->projects->isEmpty()) { $group->projects = collect($group->projects)->sort(function ($a, $b) use ($direction) { $getFirstRussianLetters = fn($s) => preg_match('/[А-Яа-яЁё]/u', $s, $m) ? mb_strtolower($m[0], 'UTF-8') : 'я'; $getNum = fn($s) => preg_match('/\d+/', $s, $m) ? (int)$m[0] : 999999; $letters1 = $getFirstRussianLetters($a->offer_number); $letters2 = $getFirstRussianLetters($b->offer_number); $n1 = $getNum($a->offer_number); $n2 = $getNum($b->offer_number); if ($letters1 !== $letters2) { return $direction === 'DESC' ? strcmp($letters2, $letters1) : strcmp($letters1, $letters2); } if ($n1 !== $n2) { return $direction === 'DESC' ? ($n2 <=> $n1) : ($n1 <=> $n2); } return $direction === 'DESC' ? strcmp($b->offer_number, $a->offer_number) : strcmp($a->offer_number, $b->offer_number); })->values(); } return $group; }); $projectsGroups = $projectsGroups->sortBy(function ($group) { if (!$group->projects->isEmpty()) { $project = $group->projects->first(); return $project->offer_number; } return ''; }, SORT_NATURAL, $direction == 'DESC'); $projectsWithoutGroups = collect($projectsWithoutGroups)->sort(function ($a, $b) use ($direction) { $getFirstRussianLetters = fn($s) => preg_match('/[А-Яа-яЁё]/u', $s, $m) ? mb_strtolower($m[0], 'UTF-8') : 'я'; $getNum = fn($s) => preg_match('/\d+/', $s, $m) ? (int)$m[0] : 999999; $letters1 = $getFirstRussianLetters($a->offer_number); $letters2 = $getFirstRussianLetters($b->offer_number); $n1 = $getNum($a->offer_number); $n2 = $getNum($b->offer_number); if ($letters1 !== $letters2) { return $direction === 'DESC' ? strcmp($letters2, $letters1) : strcmp($letters1, $letters2); } if ($n1 !== $n2) { return $direction === 'DESC' ? ($n2 <=> $n1) : ($n1 <=> $n2); } return $direction === 'DESC' ? strcmp($b->offer_number, $a->offer_number) : strcmp($a->offer_number, $b->offer_number); }); } if ($sort == 'created_at') { $projectsGroups = $projectsGroups->map(function ($group) use ($direction) { if ($group->projects->isNotEmpty()) { $sortedProjects = $group->projects->sortBy( fn($project) => $project->created_at?->getTimestamp() ?? PHP_INT_MIN, SORT_NUMERIC, $direction == 'DESC' )->values(); $group->setRelation('projects', $sortedProjects); } return $group; }); $projectsGroups = $projectsGroups->sortBy( fn($group) => $group->projects->first()?->created_at?->getTimestamp() ?? PHP_INT_MIN, SORT_NUMERIC, $direction == 'DESC' )->values(); $projectsWithoutGroups = $projectsWithoutGroups->sortBy('created_at', SORT_NATURAL, $direction === 'DESC'); } if ($sort == 'last_update') { $projectsGroups = $projectsGroups->map(function ($group) use ($direction) { if ($group && $group->projects && !$group->projects->isEmpty()) { $group->projects = $group->projects->sortBy(function ($project) use ($direction) { if ($project && !$project->paymentDistributions->isEmpty()) { return $project->paymentDistributions->sortBy(function ($distribution) use ($direction) { return $distribution->payment->payment_date; }, SORT_REGULAR, $direction == 'DESC')->values(); } return '0000-00-00'; }, SORT_REGULAR, $direction === 'DESC')->values(); } return $group; }); $projectsGroups = $projectsGroups->sortBy(function ($group) use ($direction) { if ($group && $group->projects && !$group->projects->isEmpty()) { $maxPaymentDate = $group->projects->map(function ($project) { if ($project && !$project->paymentDistributions->isEmpty()) { return $project->paymentDistributions->max(function ($distribution) { return $distribution->payment->payment_date; }); } return null; })->filter()->max(); return $maxPaymentDate; } return '0000-00-00'; }, SORT_REGULAR, $direction === 'DESC')->values(); $projectsWithoutGroups = $projectsWithoutGroups->sortBy(function ($project) use ($direction) { if ($project && !$project->paymentDistributions->isEmpty()) { return $project->paymentDistributions->max(function ($distribution) { return $distribution->payment->payment_date; }); } return null; }, SORT_REGULAR, $direction === 'DESC')->values(); } } $projectsWithoutGroups->each(function ($projectsWithoutGroup) use (&$stats) { $income = $outcome = $in_work = 0; if ($projectsWithoutGroup->status == ProjectStatusEnum::PROJECT_STATUS_ACTIVE->value) { $in_work += 1; } if (!empty($projectsWithoutGroup->paymentDistributions)) { $projectsWithoutGroup->paymentDistributions->each(function (PaymentDistribution $paymentDistribution) use (&$income, &$outcome) { if (($paymentDistribution->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_RECEPTION->value) || ($paymentDistribution->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_RECEPTION_FROM_1C->value)) { $income += $paymentDistribution->amount; } else { $outcome += $paymentDistribution->amount; } }); } $stats['in_work'] += $in_work; $stats['income'] += $income; $stats['outcome'] += $outcome; }); $projectsGroups->each(function ($projectGroup) use (&$stats) { $income = $outcome = $in_work = 0; $projectGroup['projects']->each(function ($project) use (&$income, &$outcome, &$in_work) { if ($project->status == ProjectStatusEnum::PROJECT_STATUS_ACTIVE->value) { $in_work += 1; } if ($project->paymentDistributions) { $project->paymentDistributions->each(function (PaymentDistribution $paymentDistribution) use (&$income, &$outcome) { if (($paymentDistribution->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_RECEPTION->value) || ($paymentDistribution->payment->payment_type == PaymentTypeEnum::PAYMENT_TYPE_RECEPTION_FROM_1C->value)) { $income += $paymentDistribution->amount; } else { $outcome += $paymentDistribution->amount; } }); } }); $stats['in_work'] += $in_work; $stats['income'] += $income; $stats['outcome'] += $outcome; }); return new ResponseDto( data: [ 'header' => new StatsProjectResource($stats), 'projects_groups' => ProjectGroupResource::collection($projectsGroups), 'projects_without_groups' => ProjectWithoutGroupsResource::collection($projectsWithoutGroups), ], status: true ); } /** * Получить проект по ID. */ #[ Group('projects')] #[OpenApiResponse(Project::class)] public function show(int $modelID, int $id): ResponseDto { $data = $this->projectService->findById($id); return new ResponseDto( data: new ShowProjectResource($data), status: true ); } /** * Сумма платежей по проекту */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function payments(int $modelID, int $id): ResponseDto { return new ResponseDto( data: $this->projectService->payments($id), status: true ); } /** * Создать новый проект. */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function store(int $modelID, CreateProjectRequest $request): ResponseDto { return new ResponseDto( data: $this->projectService->create($modelID, $request), status: true ); } public function updatePaymentArticle(int $modelId, $projectId, Request $request) { $amountLimit = $request->query('amount_limit'); return new ResponseDto( status: $this->projectService->updatePaymentArticle($projectId, $amountLimit), ); } public function updateLimitsProject(int $modelId, $id, Request $request) { $limit = $request->input('limit'); $operationType = $request->query('operation_type'); $this->projectService->updateLimitsProject($id, $operationType, $limit); } /** * Обновить существующий проект. */ #[Group('projects')] #[OpenApiResponse(Project::class)] public function update(int $modelID, int $id, UpdateProjectRequest $request): ResponseDto { return new ResponseDto( status: (bool)$this->projectService->update($id, $request) ); } /** * Удалить проект. * * @queryParam new_project_id int - ID проекта для переноса платежей (опционально) */ #[Group('projects')] #[OpenApiResponse(ResponseDto::class)] public function destroy(int $modelID, int $id, Request $request): ResponseDto { return new ResponseDto( status: (bool)$this->projectService->delete($id, $request->get('new_project_id', null)) ); } public function addArticleToProject(int $modelId, Request $request) { return new ResponseDto( status: (bool)$this->projectService->addArticleToProject($request->query('projectId'), $request->query('articleId')) ); } public function deletePaymentArticle($projectId, Request $request) { $articleId = $request->query('articleId'); $newArticleId = $request->query('newArticleId'); return new ResponseDto( status: $this->projectService->deletePaymentArticle($projectId, $articleId, $newArticleId) ); } public function import(Request $request) { Excel::import(new ProjectsImport(), $request->file('file')); } }