<?php
declare(strict_types=1);
namespace App\Domain\Project\Services;
use App\Domain\Project\Enums\ProjectStatusEnum;
use App\Domain\Project\Requests\CreateProjectRequest;
use App\Domain\Project\Requests\UpdateProjectRequest;
use App\Models\Article;
use App\Models\ArticleToProject;
use App\Models\Counterparty;
use App\Models\Payment;
use App\Models\PaymentDistribution;
use App\Models\Project;
use App\Models\ProjectGroup;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ProjectService
{
/**
* Получить список всех проектов.
*
* @param int $modelID
* @param Request $request
* @param bool $withPayments
* @return Builder
*/
public function getAll(int $modelID, Request $request, bool $withPayments = false): Builder
{
$query = Project::query()
->with(['paymentDistributions.article.group', 'paymentDistributions.payment', 'projectGroup'])
->where('model_id', $modelID)
->orderBy('created_at', 'desc');
if ($request->has('project_group_id')) {
$query->where('project_group_id', $request->get('project_group_id'));
}
if ($request->has('search')) {
$search = mb_convert_case($request->get('search'), MB_CASE_LOWER);
$query->where(function ($q) use ($search) {
if (!empty($search)) {
$q->whereRaw('LOWER(short_description) LIKE ?', ["%{$search}%"])
->orWhereRaw('LOWER(offer_number) LIKE ?', ["%{$search}%"])
->orWhereRaw('LOWER(object_address) LIKE ?', ["%{$search}%"]);
$q->orWhereHas('projectGroup', function ($qInner) use ($search) {
$qInner->whereRaw('LOWER(name) LIKE ?', ["%{$search}%"]);
});
$q->orWhereHas('paymentDistributions.article.group', function ($qInner) use ($search) {
$qInner->whereRaw('LOWER(name) LIKE ?', ["%{$search}%"]);
});
}
});
}
if ($request->has('payment_id')) {
if ($request->has('payment_id')) {
/** @var Payment $payment */
$payment = Payment::find($request->get('payment_id'))->with('contragent');
$ids = [];
foreach ($payment->distributions as $distribution) {
$ids[] = $distribution->project->id;
}
$query->whereIn('id', $ids);
}
$query->whereIn('id', $ids);
}
if ($withPayments) {
$query->with(['paymentDistributions', 'paymentDistributions.payment']);
}
return $query;
}
/**
* Получить список всех проектов с группировкой + проекты вне групп
*
* @param int $modelID
* @return Collection
*/
public function getProjectsWithoutGroupsList(int $modelID, $request): Collection
{
$activeTab = $request->query('type');
$statusCondition = $activeTab == ProjectStatusEnum::PROJECT_STATUS_ARCHIVE->value ? '=' : '!=';
return Project::query()->where('model_id', $modelID)
->with(['paymentDistributions.article', 'paymentDistributions.payment'])
->where('status', $statusCondition, ProjectStatusEnum::PROJECT_STATUS_FINISHED->value)
->where(function ($query) use ($request) {
$query->whereNull('project_group_id')
->when($request->filled('search'), function ($query) use ($request) {
$search = mb_convert_case($request->get('search'), MB_CASE_LOWER);
$query->where(function ($query) use ($search) {
$query->whereRaw('LOWER(offer_number) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(short_description) like ?', ["%{$search}%"]);
});
});
})->get();
}
public function getProjectGroupList(int $modelID, $request)
{
$activeTab = $request->query('type');
$statusCondition = $activeTab == ProjectStatusEnum::PROJECT_STATUS_ARCHIVE->value ? '=' : '!=';
if ($statusCondition == '=') {
return ProjectGroup::query()
->with(['projects' => function ($query) use ($statusCondition) {
$query->where('status', $statusCondition, ProjectStatusEnum::PROJECT_STATUS_FINISHED->value)
->with(['paymentDistributions.article', 'paymentDistributions.payment']);
}])
->where('model_id', $modelID)
->where(function ($query) use ($request) {
$query->when($request->filled('search'), function ($query) use ($request) {
$search = mb_convert_case($request->get('search'), MB_CASE_LOWER);
$query->whereRaw('LOWER(name) like ?', ["%{$search}%"])
->orWhereHas('projects', function ($q) use ($search) {
$q->whereRaw('LOWER(offer_number) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(short_description) like ?', ["%{$search}%"]);
});
});
})
->get()
->filter(function ($group) {
return $group->projects->isNotEmpty();
})
->values();
}
return ProjectGroup::query()
->with(['projects' => function ($query) use ($statusCondition) {
$query->where('status', $statusCondition, ProjectStatusEnum::PROJECT_STATUS_FINISHED->value)
->with(['paymentDistributions.article', 'paymentDistributions.payment']);
}])
->where('model_id', $modelID)
->where(function ($query) use ($request) {
$query->when($request->filled('search'), function ($query) use ($request) {
$search = mb_convert_case($request->get('search'), MB_CASE_LOWER);
$query->whereRaw('LOWER(name) like ?', ["%{$search}%"])
->orWhereHas('projects', function ($q) use ($search) {
$q->whereRaw('LOWER(offer_number) like ?', ["%{$search}%"])
->orWhereRaw('LOWER(short_description) like ?', ["%{$search}%"]);
});
});
})->get();
}
public function create(
int $modelId,
CreateProjectRequest $request
): Project
{
/** @var Project $project */
$project = Project::create(array_merge(['model_id' => $modelId], $request->toArray()));
$this->syncDefaultArticles($project);
return $project;
}
/**
* Обновить проект.
*
* @param int $projectID
* @param UpdateProjectRequest $data
* @return Project
*/
public function update(int $projectID, UpdateProjectRequest $data): Project
{
$project = Project::query()->find($projectID);
$project->update($data->toArray());
return $project;
}
public function getListExceptDelete(int $modelID, $projectId)
{
return Project::query()
->with(['paymentDistributions.article', 'paymentDistributions.payment', 'projectGroup'])
->where('model_id', $modelID)
->where('id', '!=', $projectId)
->get();
}
/**
* Найти проект по ID.
*
* @param int $id
* @return Project|null
*/
public function findById(int $id)
{
$project = Project::query()
->where('id', $id)
->with(['paymentDistributions.payment.contragent', 'articleProjectLinks.article'])
->first();
$uniqueCounterparties = $project->paymentDistributions
->pluck('payment.contragent')
->unique('id')
->values();
$counterpartyIds = $uniqueCounterparties->pluck('id')->toArray();
$paymentsIds = PaymentDistribution::query()->where('project_id', $project->id)->get('payment_id')->toArray();
$counterparties = Counterparty::query()
->whereIn('id', $counterpartyIds)
->with(['payments' => function ($query) use ($paymentsIds, $project) {
$query->whereIn('id', $paymentsIds)
->with(['distributions' => function ($subQuery) use ($project) {
$subQuery->where('project_id', $project->id);
}]);
}])
->get();
$articles = PaymentDistribution::query()->where('project_id', $project->id)->with(['article.articleProjectLinks', 'payment'])->get();
$project->articleProjects = $articles;
$project->counterparties = $counterparties;
return $project;
}
public function updatePaymentArticle($projectId, $amountLimit): bool
{
$project = PaymentDistribution::query()->where('project_id', $projectId);
return (bool)$project->update(['amount_limit' => $amountLimit]);
}
public function payments(int $id)
{
return PaymentDistribution::query()->where('project_id', $id)->get();
}
/**
* Удалить проект.
*
* @param int $projectID
* @param int|string|null $newProjectId
* @return bool|null
*/
public function delete(int $projectID, int|string $newProjectId = null): ?bool
{
$project = $this->findById($projectID);
$project->paymentDistributions->each(function (PaymentDistribution $pd) use ($newProjectId): void {
$pd->update([
'project_id' => $newProjectId,
]);
});
return $project->delete();
}
public function updateLimitsProject($id, $operationType, $limit): void
{
$project = Project::query()->find($id);
if ($operationType == 'credit') {
$project->update(['project_limits_credit' => $limit]);
} else {
$project->update(['project_limits_debit' => $limit]);
}
}
public function addArticleToProject($projectId, $articleId)
{
$project = Project::query()->find($projectId);
return $project->articles()->attach($articleId, ['amount_limit' => 1]);
}
public function deletePaymentArticle($projectId, $articleId, $newArticleId)
{
$article = Article::query()->with('paymentDistributions')->where('id', $articleId)->first();
if ($article) {
$article->paymentDistributions
->where('project_id', $projectId)
->each(function (PaymentDistribution $pd) use ($newArticleId) {
$pd->update([
'article_id' => $newArticleId,
]);
});
$articleToProject = ArticleToProject::query()->where(['article_id' => $articleId, 'project_id' => $projectId])->first();
if ($articleToProject) {
$articleToProject->delete();
}
return true;
}
return false;
}
private function syncDefaultArticles(Project $project): void
{
$articleIds = $project->model->articles()
->where('default_in_project', true)
->pluck('id')
->values()
->all();
$project->articles()->sync($articleIds);
}
}