feat: [pmis] PMIS 자료실/안전관리/품질관리 기능 추가 및 개선

- 자료실 하위 3개 메뉴: 자료보관함, 매뉴얼, 공지사항
- 자료보관함: 폴더 트리 + 파일 업로드/다운로드/삭제
- 매뉴얼/공지사항: 게시판형 CRUD + 첨부파일
- 안전관리: 안전보건교육, TBM현황, 위험성평가, 재해예방조치
- 품질관리: 시정조치 UI 페이지
- 대시보드: 슈퍼관리자 전용 레거시 사이트 참고 카드
- 작업일보/출면일보 오류 수정 및 기능 개선
- 설비 사진 업로드, 근로계약서 종료일 수정
This commit is contained in:
김보곤
2026-03-12 21:11:21 +09:00
parent 31911ddf25
commit 267255bbe6
33 changed files with 5503 additions and 233 deletions

View File

@@ -113,6 +113,80 @@ public function pmisDailyReport(Request $request): View|Response
return view('juil.pmis-daily-report');
}
public function pmisCorrectiveAction(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.corrective-action'));
}
return view('juil.pmis-corrective-action');
}
// ── 안전관리 ──
public function pmisSafetyEducation(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.safety-education'));
}
return view('juil.pmis-safety-education');
}
public function pmisTbm(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.tbm'));
}
return view('juil.pmis-tbm');
}
public function pmisRiskAssessment(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.risk-assessment'));
}
return view('juil.pmis-risk-assessment');
}
public function pmisDisasterPrevention(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.disaster-prevention'));
}
return view('juil.pmis-disaster-prevention');
}
public function pmisArchiveFiles(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.archive-files'));
}
return view('juil.pmis-archive-files');
}
public function pmisArchiveManual(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.archive-manual'));
}
return view('juil.pmis-archive-manual');
}
public function pmisArchiveNotice(Request $request): View|Response
{
if ($request->header('HX-Request')) {
return response('', 200)->header('HX-Redirect', route('juil.construction-pmis.archive-notice'));
}
return view('juil.pmis-archive-notice');
}
public function pmisWeather(WeatherService $weatherService): JsonResponse
{
$forecasts = $weatherService->getWeeklyForecast();
@@ -142,6 +216,8 @@ public function pmisProfile(): JsonResponse
'created_at' => $worker->created_at?->format('Y-m-d'),
'last_login_at' => $worker->last_login_at?->format('Y-m-d H:i')
?? $user->last_login_at?->format('Y-m-d H:i'),
'is_admin' => $user->isAdmin(),
'is_super_admin' => $user->isSuperAdmin(),
],
]);
}

View File

@@ -0,0 +1,201 @@
<?php
namespace App\Http\Controllers\Juil;
use App\Http\Controllers\Controller;
use App\Models\Juil\PmisArchiveFile;
use App\Models\Juil\PmisArchiveFolder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class PmisArchiveController extends Controller
{
private function tenantId(): int
{
return (int) session('current_tenant_id', 1);
}
/* ─── 폴더 트리 ─── */
public function folderTree(): JsonResponse
{
$folders = PmisArchiveFolder::tenant($this->tenantId())
->whereNull('parent_id')
->with('children.children.children')
->orderBy('sort_order')
->get();
return response()->json(['folders' => $folders]);
}
public function folderStore(Request $request): JsonResponse
{
$request->validate([
'name' => 'required|string|max:200',
'parent_id' => 'nullable|integer',
]);
$folder = PmisArchiveFolder::create([
'tenant_id' => $this->tenantId(),
'parent_id' => $request->parent_id,
'name' => $request->name,
'sort_order' => PmisArchiveFolder::tenant($this->tenantId())
->where('parent_id', $request->parent_id)
->count(),
]);
return response()->json(['folder' => $folder], 201);
}
public function folderUpdate(Request $request, int $id): JsonResponse
{
$folder = PmisArchiveFolder::tenant($this->tenantId())->findOrFail($id);
$request->validate(['name' => 'required|string|max:200']);
$folder->update(['name' => $request->name]);
return response()->json(['folder' => $folder]);
}
public function folderDestroy(int $id): JsonResponse
{
$folder = PmisArchiveFolder::tenant($this->tenantId())->findOrFail($id);
$descendantIds = $folder->allDescendantIds();
PmisArchiveFile::tenant($this->tenantId())
->whereIn('folder_id', $descendantIds)
->each(function ($file) {
Storage::disk('public')->delete($file->file_path);
$file->delete();
});
PmisArchiveFolder::tenant($this->tenantId())
->whereIn('id', $descendantIds)
->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
/* ─── 파일 목록 ─── */
public function fileList(Request $request): JsonResponse
{
$folderId = $request->query('folder_id');
$tab = $request->query('tab', '전체');
$search = $request->query('search', '');
$dateFrom = $request->query('date_from');
$dateTo = $request->query('date_to');
$includeSubfolder = $request->boolean('include_subfolder', true);
$query = PmisArchiveFile::tenant($this->tenantId());
if ($folderId) {
if ($includeSubfolder) {
$folder = PmisArchiveFolder::tenant($this->tenantId())->find($folderId);
if ($folder) {
$folderIds = $folder->allDescendantIds();
$query->whereIn('folder_id', $folderIds);
}
} else {
$query->where('folder_id', $folderId);
}
}
if ($tab !== '전체') {
$query->where('file_type', $tab);
}
if ($search) {
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('original_name', 'like', "%{$search}%")
->orWhereHas('folder', fn ($fq) => $fq->where('name', 'like', "%{$search}%"));
});
}
if ($dateFrom) {
$query->whereDate('created_at', '>=', $dateFrom);
}
if ($dateTo) {
$query->whereDate('created_at', '<=', $dateTo);
}
$files = $query->orderByDesc('created_at')->get();
$result = $files->map(fn ($f) => [
'id' => $f->id,
'title' => $f->title ?: $f->original_name,
'fileName' => $f->original_name,
'filePath' => Storage::disk('public')->url($f->file_path),
'fileType' => $f->file_type,
'siteName' => $f->site_name,
'size' => PmisArchiveFile::formatSize($f->file_size),
'sizeRaw' => $f->file_size,
'registrant' => $f->registeredByUser?->name ?? '-',
'registeredAt' => $f->created_at->format('Y-m-d'),
'folderId' => $f->folder_id,
]);
return response()->json(['files' => $result]);
}
/* ─── 파일 업로드 ─── */
public function fileStore(Request $request): JsonResponse
{
$request->validate([
'folder_id' => 'required|integer|exists:pmis_archive_folders,id',
'files' => 'required|array|min:1',
'files.*' => 'file|max:51200',
'title' => 'nullable|string|max:300',
'site_name' => 'nullable|string|max:200',
]);
$user = auth()->user();
$uploaded = [];
foreach ($request->file('files') as $file) {
$ext = $file->getClientOriginalExtension();
$path = $file->store('pmis/archive', 'public');
$record = PmisArchiveFile::create([
'tenant_id' => $this->tenantId(),
'folder_id' => $request->folder_id,
'title' => $request->title ?: '',
'original_name' => $file->getClientOriginalName(),
'file_path' => $path,
'file_type' => PmisArchiveFile::detectFileType($ext),
'file_size' => $file->getSize(),
'site_name' => $request->site_name ?: '',
'registered_by' => $user?->id,
]);
$uploaded[] = $record;
}
return response()->json(['files' => $uploaded, 'message' => count($uploaded).'개 파일이 업로드되었습니다.'], 201);
}
/* ─── 파일 삭제 ─── */
public function fileDestroy(int $id): JsonResponse
{
$file = PmisArchiveFile::tenant($this->tenantId())->findOrFail($id);
Storage::disk('public')->delete($file->file_path);
$file->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
/* ─── 파일 다운로드 ─── */
public function fileDownload(int $id)
{
$file = PmisArchiveFile::tenant($this->tenantId())->findOrFail($id);
$fullPath = Storage::disk('public')->path($file->file_path);
return response()->download($fullPath, $file->original_name);
}
}

View File

@@ -0,0 +1,322 @@
<?php
namespace App\Http\Controllers\Juil;
use App\Http\Controllers\Controller;
use App\Models\Juil\PmisDailyWorkReport;
use App\Models\Juil\PmisWorkReportEquipment;
use App\Models\Juil\PmisWorkReportMaterial;
use App\Models\Juil\PmisWorkReportPhoto;
use App\Models\Juil\PmisWorkReportVolume;
use App\Models\Juil\PmisWorkReportWorker;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class PmisDailyWorkReportController extends Controller
{
private function tenantId(): int
{
return (int) session('current_tenant_id', 1);
}
public function show(Request $request): JsonResponse
{
$date = $request->input('date', now()->toDateString());
$company = $request->input('company') ?? '';
$report = PmisDailyWorkReport::tenant($this->tenantId())
->where('date', $date)
->when($company, fn ($q) => $q->where('company_name', $company))
->first();
if (! $report) {
$report = PmisDailyWorkReport::create([
'tenant_id' => $this->tenantId(),
'date' => $date,
'company_name' => $company,
'weather' => '맑음',
'status' => 'draft',
]);
}
$report->load(['workers', 'equipments', 'materials', 'volumes', 'photos']);
return response()->json($report);
}
public function monthStatus(Request $request): JsonResponse
{
$year = $request->integer('year', now()->year);
$month = $request->integer('month', now()->month);
$company = $request->input('company') ?? '';
$reports = PmisDailyWorkReport::tenant($this->tenantId())
->whereYear('date', $year)
->whereMonth('date', $month)
->when($company, fn ($q) => $q->where('company_name', $company))
->withCount(['workers', 'equipments', 'materials', 'volumes', 'photos'])
->get();
$result = [];
foreach ($reports as $r) {
$day = (int) $r->date->format('d');
$hasData = $r->workers_count > 0 || $r->equipments_count > 0
|| $r->materials_count > 0 || $r->volumes_count > 0
|| $r->photos_count > 0
|| $r->work_content_today;
if ($hasData) {
$result[$day] = $r->status;
}
}
return response()->json($result);
}
public function update(Request $request, int $id): JsonResponse
{
$report = PmisDailyWorkReport::tenant($this->tenantId())->findOrFail($id);
$validated = $request->validate([
'weather' => 'sometimes|string|max:50',
'temp_low' => 'sometimes|nullable|numeric',
'temp_high' => 'sometimes|nullable|numeric',
'precipitation' => 'sometimes|nullable|numeric',
'snowfall' => 'sometimes|nullable|numeric',
'fine_dust' => 'sometimes|nullable|string|max:50',
'ultra_fine_dust' => 'sometimes|nullable|string|max:50',
'work_content_today' => 'sometimes|nullable|string',
'work_content_tomorrow' => 'sometimes|nullable|string',
'notes' => 'sometimes|nullable|string',
'status' => 'sometimes|in:draft,review,approved',
'options' => 'sometimes|nullable|array',
]);
// NOT NULL string 컬럼의 null → 빈 문자열 변환
foreach (['fine_dust', 'ultra_fine_dust', 'weather'] as $col) {
if (array_key_exists($col, $validated) && $validated[$col] === null) {
$validated[$col] = '';
}
}
$report->update($validated);
$report->load(['workers', 'equipments', 'materials', 'volumes', 'photos']);
return response()->json($report);
}
public function destroy(int $id): JsonResponse
{
$report = PmisDailyWorkReport::tenant($this->tenantId())->findOrFail($id);
$report->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
public function saveReviewers(Request $request, int $id): JsonResponse
{
$report = PmisDailyWorkReport::tenant($this->tenantId())->findOrFail($id);
$reviewers = $request->input('reviewers', []);
$options = $report->options ?? [];
$options['reviewers'] = $reviewers;
$report->update(['options' => $options]);
return response()->json(['message' => '검토자가 저장되었습니다.']);
}
// ─── Worker CRUD ───
public function workerStore(Request $request): JsonResponse
{
$v = $request->validate([
'report_id' => 'required|integer|exists:pmis_daily_work_reports,id',
'work_type' => 'required|string|max:200',
'job_type' => 'required|string|max:200',
'prev_cumulative' => 'nullable|integer|min:0',
'today_count' => 'nullable|integer|min:0',
]);
$v['tenant_id'] = $this->tenantId();
$v['prev_cumulative'] = $v['prev_cumulative'] ?? 0;
$v['today_count'] = $v['today_count'] ?? 0;
$v['sort_order'] = (PmisWorkReportWorker::where('report_id', $v['report_id'])->max('sort_order') ?? 0) + 1;
return response()->json(PmisWorkReportWorker::create($v), 201);
}
public function workerUpdate(Request $request, int $id): JsonResponse
{
$w = PmisWorkReportWorker::where('tenant_id', $this->tenantId())->findOrFail($id);
$w->update($request->validate([
'work_type' => 'sometimes|string|max:200',
'job_type' => 'sometimes|string|max:200',
'prev_cumulative' => 'nullable|integer|min:0',
'today_count' => 'nullable|integer|min:0',
]));
return response()->json($w);
}
public function workerDestroy(int $id): JsonResponse
{
PmisWorkReportWorker::where('tenant_id', $this->tenantId())->findOrFail($id)->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
// ─── Equipment CRUD ───
public function equipmentStore(Request $request): JsonResponse
{
$v = $request->validate([
'report_id' => 'required|integer|exists:pmis_daily_work_reports,id',
'equipment_name' => 'required|string|max:200',
'specification' => 'nullable|string|max:300',
'prev_cumulative' => 'nullable|integer|min:0',
'today_count' => 'nullable|integer|min:0',
]);
$v['tenant_id'] = $this->tenantId();
$v['specification'] = $v['specification'] ?? '';
$v['prev_cumulative'] = $v['prev_cumulative'] ?? 0;
$v['today_count'] = $v['today_count'] ?? 0;
$v['sort_order'] = (PmisWorkReportEquipment::where('report_id', $v['report_id'])->max('sort_order') ?? 0) + 1;
return response()->json(PmisWorkReportEquipment::create($v), 201);
}
public function equipmentUpdate(Request $request, int $id): JsonResponse
{
$e = PmisWorkReportEquipment::where('tenant_id', $this->tenantId())->findOrFail($id);
$e->update($request->validate([
'equipment_name' => 'sometimes|string|max:200',
'specification' => 'nullable|string|max:300',
'prev_cumulative' => 'nullable|integer|min:0',
'today_count' => 'nullable|integer|min:0',
]));
return response()->json($e);
}
public function equipmentDestroy(int $id): JsonResponse
{
PmisWorkReportEquipment::where('tenant_id', $this->tenantId())->findOrFail($id)->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
// ─── Material CRUD ───
public function materialStore(Request $request): JsonResponse
{
$v = $request->validate([
'report_id' => 'required|integer|exists:pmis_daily_work_reports,id',
'material_name' => 'required|string|max:200',
'specification' => 'nullable|string|max:300',
'unit' => 'nullable|string|max:50',
'design_qty' => 'nullable|numeric|min:0',
'prev_cumulative' => 'nullable|numeric|min:0',
'today_count' => 'nullable|numeric|min:0',
]);
$v['tenant_id'] = $this->tenantId();
$v['sort_order'] = (PmisWorkReportMaterial::where('report_id', $v['report_id'])->max('sort_order') ?? 0) + 1;
return response()->json(PmisWorkReportMaterial::create($v), 201);
}
public function materialUpdate(Request $request, int $id): JsonResponse
{
$m = PmisWorkReportMaterial::where('tenant_id', $this->tenantId())->findOrFail($id);
$m->update($request->validate([
'material_name' => 'sometimes|string|max:200',
'specification' => 'nullable|string|max:300',
'unit' => 'nullable|string|max:50',
'design_qty' => 'nullable|numeric|min:0',
'prev_cumulative' => 'nullable|numeric|min:0',
'today_count' => 'nullable|numeric|min:0',
]));
return response()->json($m);
}
public function materialDestroy(int $id): JsonResponse
{
PmisWorkReportMaterial::where('tenant_id', $this->tenantId())->findOrFail($id)->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
// ─── Volume CRUD ───
public function volumeStore(Request $request): JsonResponse
{
$v = $request->validate([
'report_id' => 'required|integer|exists:pmis_daily_work_reports,id',
'work_type' => 'required|string|max:200',
'sub_work_type' => 'nullable|string|max:200',
'unit' => 'nullable|string|max:50',
'design_qty' => 'nullable|numeric|min:0',
'prev_cumulative' => 'nullable|numeric|min:0',
'today_count' => 'nullable|numeric|min:0',
]);
$v['tenant_id'] = $this->tenantId();
$v['sort_order'] = (PmisWorkReportVolume::where('report_id', $v['report_id'])->max('sort_order') ?? 0) + 1;
return response()->json(PmisWorkReportVolume::create($v), 201);
}
public function volumeUpdate(Request $request, int $id): JsonResponse
{
$vol = PmisWorkReportVolume::where('tenant_id', $this->tenantId())->findOrFail($id);
$vol->update($request->validate([
'work_type' => 'sometimes|string|max:200',
'sub_work_type' => 'nullable|string|max:200',
'unit' => 'nullable|string|max:50',
'design_qty' => 'nullable|numeric|min:0',
'prev_cumulative' => 'nullable|numeric|min:0',
'today_count' => 'nullable|numeric|min:0',
]));
return response()->json($vol);
}
public function volumeDestroy(int $id): JsonResponse
{
PmisWorkReportVolume::where('tenant_id', $this->tenantId())->findOrFail($id)->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
// ─── Photo CRUD ───
public function photoStore(Request $request): JsonResponse
{
$v = $request->validate([
'report_id' => 'required|integer|exists:pmis_daily_work_reports,id',
'location' => 'nullable|string|max:200',
'content' => 'nullable|string|max:500',
'photo' => 'nullable|image|max:10240',
]);
$path = '';
if ($request->hasFile('photo')) {
$path = $request->file('photo')->store('pmis/work-report-photos', 'public');
}
$photo = PmisWorkReportPhoto::create([
'tenant_id' => $this->tenantId(),
'report_id' => $v['report_id'],
'photo_path' => $path,
'location' => $v['location'] ?? '',
'content' => $v['content'] ?? '',
'photo_date' => now()->toDateString(),
'sort_order' => (PmisWorkReportPhoto::where('report_id', $v['report_id'])->max('sort_order') ?? 0) + 1,
]);
return response()->json($photo, 201);
}
public function photoDestroy(int $id): JsonResponse
{
PmisWorkReportPhoto::where('tenant_id', $this->tenantId())->findOrFail($id)->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace App\Http\Controllers\Juil;
use App\Http\Controllers\Controller;
use App\Models\Juil\PmisManual;
use App\Models\Juil\PmisManualAttachment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class PmisManualController extends Controller
{
private function tenantId(): int
{
return (int) session('current_tenant_id', 1);
}
public function list(Request $request): JsonResponse
{
$search = $request->query('search', '');
$query = PmisManual::tenant($this->tenantId())
->with('author:id,name')
->withCount('attachments')
->orderByDesc('id');
if ($search) {
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('tags', 'like', "%{$search}%");
});
}
$manuals = $query->get()->map(fn ($m) => [
'id' => $m->id,
'title' => $m->title,
'author' => $m->author?->name ?? '관리자',
'createdAt' => $m->created_at->format('Y-m-d'),
'views' => $m->views,
'hasAttachment' => $m->attachments_count > 0,
]);
return response()->json(['manuals' => $manuals]);
}
public function show(int $id): JsonResponse
{
$manual = PmisManual::tenant($this->tenantId())
->with(['author:id,name', 'attachments'])
->findOrFail($id);
$manual->increment('views');
return response()->json([
'manual' => [
'id' => $manual->id,
'title' => $manual->title,
'content' => $manual->content,
'tags' => $manual->tags,
'author' => $manual->author?->name ?? '관리자',
'createdAt' => $manual->created_at->format('Y-m-d'),
'views' => $manual->views,
'attachments' => $manual->attachments->map(fn ($a) => [
'id' => $a->id,
'fileName' => $a->original_name,
'size' => PmisManualAttachment::formatSize($a->file_size),
'downloadUrl' => "/juil/construction-pmis/api/manuals/attachments/{$a->id}/download",
]),
],
]);
}
public function store(Request $request): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$request->validate([
'title' => 'required|string|max:300',
'content' => 'nullable|string',
'tags' => 'nullable|string|max:500',
'files' => 'nullable|array',
'files.*' => 'file|max:51200',
]);
$manual = PmisManual::create([
'tenant_id' => $this->tenantId(),
'title' => $request->title,
'content' => $request->content ?? '',
'tags' => $request->tags ?? '',
'author_id' => $user->id,
]);
if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
$path = $file->store('pmis/manuals', 'public');
$manual->attachments()->create([
'original_name' => $file->getClientOriginalName(),
'file_path' => $path,
'file_size' => $file->getSize(),
]);
}
}
return response()->json(['manual' => $manual->load('attachments'), 'message' => '등록되었습니다.'], 201);
}
public function update(Request $request, int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$manual = PmisManual::tenant($this->tenantId())->findOrFail($id);
$request->validate([
'title' => 'required|string|max:300',
'content' => 'nullable|string',
'tags' => 'nullable|string|max:500',
'files' => 'nullable|array',
'files.*' => 'file|max:51200',
]);
$manual->update([
'title' => $request->title,
'content' => $request->content ?? '',
'tags' => $request->tags ?? '',
]);
if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
$path = $file->store('pmis/manuals', 'public');
$manual->attachments()->create([
'original_name' => $file->getClientOriginalName(),
'file_path' => $path,
'file_size' => $file->getSize(),
]);
}
}
return response()->json(['manual' => $manual->load('attachments'), 'message' => '수정되었습니다.']);
}
public function destroy(int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$manual = PmisManual::tenant($this->tenantId())->findOrFail($id);
foreach ($manual->attachments as $att) {
Storage::disk('public')->delete($att->file_path);
}
$manual->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
public function attachmentDownload(int $id)
{
$att = PmisManualAttachment::findOrFail($id);
$fullPath = Storage::disk('public')->path($att->file_path);
return response()->download($fullPath, $att->original_name);
}
public function attachmentDestroy(int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$att = PmisManualAttachment::findOrFail($id);
Storage::disk('public')->delete($att->file_path);
$att->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
}

View File

@@ -0,0 +1,185 @@
<?php
namespace App\Http\Controllers\Juil;
use App\Http\Controllers\Controller;
use App\Models\Juil\PmisNotice;
use App\Models\Juil\PmisNoticeAttachment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class PmisNoticeController extends Controller
{
private function tenantId(): int
{
return (int) session('current_tenant_id', 1);
}
public function list(Request $request): JsonResponse
{
$search = $request->query('search', '');
$withTrashed = $request->boolean('with_trashed', false);
$query = PmisNotice::tenant($this->tenantId())
->with('author:id,name')
->withCount('attachments')
->orderByDesc('id');
if ($withTrashed && auth()->user()->isAdmin()) {
$query->withTrashed();
}
if ($search) {
$query->where('title', 'like', "%{$search}%");
}
$notices = $query->get()->map(fn ($n) => [
'id' => $n->id,
'title' => $n->title,
'author' => $n->author?->name ?? '관리자',
'createdAt' => $n->created_at->format('Y-m-d H:i:s'),
'views' => $n->views,
'hasAttachment' => $n->attachments_count > 0,
'isDeleted' => $n->trashed(),
]);
return response()->json(['notices' => $notices]);
}
public function show(int $id): JsonResponse
{
$notice = PmisNotice::tenant($this->tenantId())
->with(['author:id,name', 'attachments'])
->findOrFail($id);
$notice->increment('views');
return response()->json([
'notice' => [
'id' => $notice->id,
'title' => $notice->title,
'content' => $notice->content,
'author' => $notice->author?->name ?? '관리자',
'createdAt' => $notice->created_at->format('Y-m-d H:i:s'),
'views' => $notice->views,
'attachments' => $notice->attachments->map(fn ($a) => [
'id' => $a->id,
'fileName' => $a->original_name,
'size' => PmisNoticeAttachment::formatSize($a->file_size),
'downloadUrl' => "/juil/construction-pmis/api/notices/attachments/{$a->id}/download",
]),
],
]);
}
public function store(Request $request): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$request->validate([
'title' => 'required|string|max:300',
'content' => 'nullable|string',
'files' => 'nullable|array',
'files.*' => 'file|max:51200',
]);
$notice = PmisNotice::create([
'tenant_id' => $this->tenantId(),
'title' => $request->title,
'content' => $request->content ?? '',
'author_id' => $user->id,
]);
if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
$path = $file->store('pmis/notices', 'public');
$notice->attachments()->create([
'original_name' => $file->getClientOriginalName(),
'file_path' => $path,
'file_size' => $file->getSize(),
]);
}
}
return response()->json(['notice' => $notice->load('attachments'), 'message' => '등록되었습니다.'], 201);
}
public function update(Request $request, int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$notice = PmisNotice::tenant($this->tenantId())->findOrFail($id);
$request->validate([
'title' => 'required|string|max:300',
'content' => 'nullable|string',
'files' => 'nullable|array',
'files.*' => 'file|max:51200',
]);
$notice->update([
'title' => $request->title,
'content' => $request->content ?? '',
]);
if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
$path = $file->store('pmis/notices', 'public');
$notice->attachments()->create([
'original_name' => $file->getClientOriginalName(),
'file_path' => $path,
'file_size' => $file->getSize(),
]);
}
}
return response()->json(['notice' => $notice->load('attachments'), 'message' => '수정되었습니다.']);
}
public function destroy(int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$notice = PmisNotice::tenant($this->tenantId())->findOrFail($id);
foreach ($notice->attachments as $att) {
Storage::disk('public')->delete($att->file_path);
}
$notice->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
public function attachmentDownload(int $id)
{
$att = PmisNoticeAttachment::findOrFail($id);
$fullPath = Storage::disk('public')->path($att->file_path);
return response()->download($fullPath, $att->original_name);
}
public function attachmentDestroy(int $id): JsonResponse
{
$user = auth()->user();
if (! $user->isAdmin()) {
return response()->json(['message' => '권한이 없습니다.'], 403);
}
$att = PmisNoticeAttachment::findOrFail($id);
Storage::disk('public')->delete($att->file_path);
$att->delete();
return response()->json(['message' => '삭제되었습니다.']);
}
}