File "ArticleService.php"

Full Path: /var/www/html/back/app/Domain/Article/ArticleService.php
File size: 6.72 KB
MIME-type: text/x-php
Charset: utf-8

<?php

declare(strict_types=1);

namespace App\Domain\Article\Services;

use App\BaseClasses\BaseService;
use App\Domain\Article\Requests\CreateArticleRequest;
use App\Domain\Article\Requests\GetArticleListRequest;
use App\Domain\Article\Requests\UpdateArticleRequest;
use App\Models\Article;
use App\Models\ArticleGroup;
use App\Models\Payment;
use App\Models\PaymentDistribution;
use App\Models\Project;
use App\Services\CashFlowService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\DB;

class ArticleService extends BaseService
{
    public function __construct(protected CashFlowService $cashFlowService)
    {
    }

    /**
     * Получить все статьи.
     *
     * @return Builder
     */
    public function getAll(int $modelID, ?GetArticleListRequest $request): Builder
    {
        $query = Article::query()
            ->where('model_id', $modelID)
            ->with([
                'group',
                'articleProjectLinks'
            ]);

        if ($request->has('search')) {
            $search = mb_convert_case($request->get('search'), MB_CASE_LOWER);

            $query->whereRaw('LOWER(name) LIKE ?', ["%{$search}%"])
                ->orWhereHas('group', function ($q) use ($search, $modelID) {
                    $q->where('model_id', $modelID);
                    $q->whereRaw('LOWER(name) LIKE ?', ["%{$search}%"]);
                });
        }
        if ($request->has('article_group_id')) {
            $query->where('article_group_id', $request->get('article_group_id'));
        }
        if ($request->has('article_type')) {
            $query->where('article_type', $request->get('article_type'));
        }

        if ($request->has('payment_id')) {
            /** @var Payment $payment */
            $payment = Payment::find($request->get('payment_id'));
            $ids = [];
            foreach ($payment->distributions as $distribution) {
                $ids[] = $distribution->article->id;
            }
            $query->whereIn('id', $ids);
        }

        return $query;
    }

    /**
     * Создать новую статью.
     *
     * @param CreateArticleRequest $request
     * @return Article
     */
    public function create(int $modelID, CreateArticleRequest $request): Article
    {
        return DB::transaction(function () use ($modelID, $request) {
            if (!empty($request->article_group_id)) {
                ArticleGroup::query()
                    ->where('model_id', $modelID)
                    ->where('id', $request->article_group_id)
                    ->firstOrFail();
            }

            $article = Article::create(array_merge($request->toArray(), ['model_id' => $modelID]));

            if ($request->sort) {
                $this->cashFlowService->moveArticleOrArticleGroupInCashFlow($article->id, $request->sort, $request->article_group_id);
            }

            return $article;
        });
    }

    /**
     * Обновить существующую статью.
     *
     * @param int $id
     * @param UpdateArticleRequest $request
     * @return Article
     * @throws ModelNotFoundException
     */
    public function update(int $id, UpdateArticleRequest $request): Article
    {
        $article = $this->findById($id);
        if (!empty($request->article_group_id)) {
            ArticleGroup::query()
                ->where('model_id', $article->model_id)
                ->where('id', $request->article_group_id)->firstOrFail();
        }
        $article->update($request->toArray());
        $article->fresh(['group']);

        return $article;
    }

    /**
     * Найти статью по ID.
     *
     * @param int $id
     * @return Article
     * @throws ModelNotFoundException
     */
    public function findById(int $id): Article
    {
        return Article::query()
            ->with('group')
            ->with('projects')
            ->with('articleProjectLinks')
            ->findOrFail($id);
    }

    /**
     * Платежи по статье.
     *
     */
    public function payments(int $id)
    {
        return PaymentDistribution::query()->where('article_id', $id)->sum('amount');
    }

    /**
     * Удалить статью.
     *
     * @param int $id
     * @param int|null|string $newArticleId
     * @return bool
     */
    public function delete(int $id, int|string $newArticleId = null): bool
    {
        return DB::transaction(function () use ($id, $newArticleId) {
            $article = $this->findById($id);

            if ($article->article_group_id) {
                $this->cashFlowService->sortArticleInGroupAfterDelete($article->article_group_id, $article->sort);
            } else {
                $this->cashFlowService->sortArticleWithoutGroupAfterDelete($article->sort);
            }

            $projectIds = $article->paymentDistributions->each(function (PaymentDistribution $pd) use ($newArticleId): void {
                $pd->update([
                    'article_id' => $newArticleId,
                ]);
            })
            ->map(fn (PaymentDistribution $pd) => $pd->project_id)
            ->filter()->unique()->values();

            if (!empty($projectIds)) {
                $newArticle = $this->findById((int)$newArticleId);
                $this->attachToProjects($newArticle, $projectIds);
            }

            return $article->delete();
        });
    }

    public function setDefault(int $articleID, bool $isDefault): bool
    {
        $article = $this->findById($articleID);

        if ($isDefault) {
            $projects = $article->model->projects->pluck('id');
            $this->attachToProjects($article, $projects);
        } else {
            $this->detachProjectsWithoutPayments($article);
        }

        return $article->update(['default_in_project' => $isDefault]);
    }

    private function attachToProjects(Article $article, iterable $projects): void
    {
        $projectIds = collect($projects)
            ->map(function (int|Project $project) {
                return $project instanceof Project ? $project->id : $project;
            })
            ->unique()
            ->values()
            ->all();
            

        if (empty($projectIds)) {
            return;
        }

        $article->projects()->syncWithoutDetaching($projectIds);
    }

    public function detachProjectsWithoutPayments(Article $article): void
    {
        $projectIds = $article->projects()
            ->whereDoesntHave('paymentDistributions', function ($query) use ($article) {
                $query->where('article_id', $article->id);
            })
            ->pluck('projects.id')
            ->all();

        if ($projectIds !== []) {
            $article->projects()->detach($projectIds);
        }
    }
}