- checklist_templates 테이블 마이그레이션 + 기본 시딩 - ChecklistTemplate 모델 (BelongsToTenant, Auditable, SoftDeletes) - ChecklistTemplateService: 조회/저장/파일 업로드/삭제 - SaveChecklistTemplateRequest: 중첩 JSON 검증 - ChecklistTemplateController: 5개 엔드포인트 - 라우트 등록 (quality/checklist-templates, quality/qms-documents)
215 lines
6.7 KiB
PHP
215 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Commons\File;
|
|
use App\Models\Qualitys\ChecklistTemplate;
|
|
use App\Services\Audit\AuditLogger;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
class ChecklistTemplateService extends Service
|
|
{
|
|
private const AUDIT_TARGET = 'checklist_template';
|
|
|
|
private const DOCUMENT_TYPE = 'checklist_template';
|
|
|
|
public function __construct(
|
|
private readonly AuditLogger $auditLogger
|
|
) {}
|
|
|
|
/**
|
|
* 템플릿 조회 (type별)
|
|
*/
|
|
public function getByType(string $type): array
|
|
{
|
|
$template = ChecklistTemplate::query()
|
|
->where('type', $type)
|
|
->first();
|
|
|
|
if (! $template) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
// 각 항목별 파일 수 포함
|
|
$fileCounts = File::query()
|
|
->where('document_type', self::DOCUMENT_TYPE)
|
|
->where('document_id', $template->id)
|
|
->whereNull('deleted_at')
|
|
->selectRaw('field_key, COUNT(*) as count')
|
|
->groupBy('field_key')
|
|
->pluck('count', 'field_key')
|
|
->toArray();
|
|
|
|
return [
|
|
'id' => $template->id,
|
|
'name' => $template->name,
|
|
'type' => $template->type,
|
|
'categories' => $template->categories,
|
|
'options' => $template->options,
|
|
'file_counts' => $fileCounts,
|
|
'updated_at' => $template->updated_at?->toIso8601String(),
|
|
'updated_by' => $template->updater?->name,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 템플릿 저장 (전체 덮어쓰기)
|
|
*/
|
|
public function save(int $id, array $data): array
|
|
{
|
|
$template = ChecklistTemplate::findOrFail($id);
|
|
$before = $template->toArray();
|
|
|
|
// 삭제된 항목의 파일 처리
|
|
$oldSubItemIds = $template->getAllSubItemIds();
|
|
$newSubItemIds = $this->extractSubItemIds($data['categories']);
|
|
$removedIds = array_diff($oldSubItemIds, $newSubItemIds);
|
|
|
|
DB::transaction(function () use ($template, $data, $removedIds) {
|
|
// 템플릿 업데이트
|
|
$template->update([
|
|
'name' => $data['name'] ?? $template->name,
|
|
'categories' => $data['categories'],
|
|
'options' => $data['options'] ?? $template->options,
|
|
'updated_by' => $this->apiUserId(),
|
|
]);
|
|
|
|
// 삭제된 항목의 파일 → soft delete
|
|
if (! empty($removedIds)) {
|
|
$orphanFiles = File::query()
|
|
->where('document_type', self::DOCUMENT_TYPE)
|
|
->where('document_id', $template->id)
|
|
->whereIn('field_key', $removedIds)
|
|
->get();
|
|
|
|
foreach ($orphanFiles as $file) {
|
|
$file->softDeleteFile($this->apiUserId());
|
|
}
|
|
}
|
|
});
|
|
|
|
$template->refresh();
|
|
|
|
$this->auditLogger->log(
|
|
self::AUDIT_TARGET,
|
|
$template->id,
|
|
'updated',
|
|
$before,
|
|
$template->toArray(),
|
|
$this->apiUserId()
|
|
);
|
|
|
|
return $this->getByType($template->type);
|
|
}
|
|
|
|
/**
|
|
* 항목별 파일 목록 조회
|
|
*/
|
|
public function getDocuments(int $templateId, ?string $subItemId = null): array
|
|
{
|
|
$query = File::query()
|
|
->where('document_type', self::DOCUMENT_TYPE)
|
|
->where('document_id', $templateId)
|
|
->with('uploader:id,name');
|
|
|
|
if ($subItemId) {
|
|
$query->where('field_key', $subItemId);
|
|
}
|
|
|
|
$files = $query->orderBy('field_key')->orderByDesc('id')->get();
|
|
|
|
return $files->map(fn (File $file) => [
|
|
'id' => $file->id,
|
|
'field_key' => $file->field_key,
|
|
'display_name' => $file->display_name ?? $file->original_name,
|
|
'file_size' => $file->file_size,
|
|
'mime_type' => $file->mime_type,
|
|
'uploaded_by' => $file->uploader?->name,
|
|
'created_at' => $file->created_at?->toIso8601String(),
|
|
])->toArray();
|
|
}
|
|
|
|
/**
|
|
* 파일 업로드 (polymorphic)
|
|
*/
|
|
public function uploadDocument(int $templateId, string $subItemId, $uploadedFile): array
|
|
{
|
|
$template = ChecklistTemplate::findOrFail($templateId);
|
|
$tenantId = $this->tenantId();
|
|
$userId = $this->apiUserId();
|
|
|
|
// 저장 경로: {tenant_id}/checklist-templates/{year}/{month}/{stored_name}
|
|
$date = now();
|
|
$storedName = bin2hex(random_bytes(16)).'.'.$uploadedFile->getClientOriginalExtension();
|
|
$filePath = sprintf(
|
|
'%d/checklist-templates/%s/%s/%s',
|
|
$tenantId,
|
|
$date->format('Y'),
|
|
$date->format('m'),
|
|
$storedName
|
|
);
|
|
|
|
// 파일 저장
|
|
Storage::disk('tenant')->put($filePath, file_get_contents($uploadedFile->getPathname()));
|
|
|
|
// DB 레코드 생성
|
|
$file = File::create([
|
|
'tenant_id' => $tenantId,
|
|
'document_type' => self::DOCUMENT_TYPE,
|
|
'document_id' => $template->id,
|
|
'field_key' => $subItemId,
|
|
'display_name' => $uploadedFile->getClientOriginalName(),
|
|
'stored_name' => $storedName,
|
|
'file_path' => $filePath,
|
|
'file_size' => $uploadedFile->getSize(),
|
|
'mime_type' => $uploadedFile->getClientMimeType(),
|
|
'uploaded_by' => $userId,
|
|
'created_by' => $userId,
|
|
]);
|
|
|
|
return [
|
|
'id' => $file->id,
|
|
'field_key' => $file->field_key,
|
|
'display_name' => $file->display_name,
|
|
'file_size' => $file->file_size,
|
|
'mime_type' => $file->mime_type,
|
|
'created_at' => $file->created_at?->toIso8601String(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 파일 삭제
|
|
* - 교체(replace=true): hard delete (물리 파일 + DB)
|
|
* - 일반 삭제: soft delete (휴지통)
|
|
*/
|
|
public function deleteDocument(int $fileId, bool $replace = false): void
|
|
{
|
|
$file = File::query()
|
|
->where('document_type', self::DOCUMENT_TYPE)
|
|
->findOrFail($fileId);
|
|
|
|
if ($replace) {
|
|
$file->permanentDelete();
|
|
} else {
|
|
$file->softDeleteFile($this->apiUserId());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* categories JSON에서 sub_item_id 목록 추출
|
|
*/
|
|
private function extractSubItemIds(array $categories): array
|
|
{
|
|
$ids = [];
|
|
foreach ($categories as $category) {
|
|
foreach ($category['subItems'] ?? [] as $subItem) {
|
|
$ids[] = $subItem['id'];
|
|
}
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
}
|