Files
sam-manage/app/Http/Controllers/BendingBaseController.php

268 lines
10 KiB
PHP
Raw Normal View History

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View;
class BendingBaseController extends Controller
{
private function api(): \Illuminate\Http\Client\PendingRequest
{
$baseUrl = config('services.api.base_url', 'https://api.sam.kr');
$token = session('api_access_token', '');
return Http::baseUrl($baseUrl)
->withoutVerifying()
->withHeaders([
'X-API-KEY' => config('services.api.key') ?: '42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a',
'X-TENANT-ID' => session('selected_tenant_id', 1),
])
->withToken($token)
->timeout(10);
}
public function index(Request $request): View|\Illuminate\Http\Response
{
$params = $request->only(['item_sep', 'item_bending', 'material', 'model_UA', 'item_name', 'search', 'page', 'size']);
$params['size'] = $params['size'] ?? 30;
$response = $this->api()->get('/api/v1/bending-items', $params);
$body = $response->successful() ? $response->json('data', []) : [];
$data = [
'data' => $body['data'] ?? [],
'total' => $body['total'] ?? 0,
'current_page' => $body['current_page'] ?? 1,
'last_page' => $body['last_page'] ?? 1,
];
$filterResponse = $this->api()->get('/api/v1/bending-items/filters');
$filterOptions = $filterResponse->successful() ? $filterResponse->json('data', []) : [];
if ($request->header('HX-Request')) {
if ($request->header('HX-Target') === 'items-table') {
return view('bending.base.partials.table', ['items' => $data]);
}
return response('', 200)->header('HX-Redirect', route('bending.base.index', $request->query()));
}
return view('bending.base.index', [
'items' => $data,
'filterOptions' => $filterOptions,
]);
}
public function show(int $id): View
{
$response = $this->api()->get("/api/v1/bending-items/{$id}");
$item = $response->successful() ? $response->json('data') : null;
abort_unless($item, 404);
$imageFile = $this->getImageFile($id);
return view('bending.base.form', ['item' => $item, 'mode' => 'view', 'imageFile' => $imageFile]);
}
public function create(): View
{
return view('bending.base.form', ['item' => null, 'mode' => 'create', 'imageFile' => null]);
}
public function edit(int $id): View
{
$response = $this->api()->get("/api/v1/bending-items/{$id}");
$item = $response->successful() ? $response->json('data') : null;
abort_unless($item, 404);
$imageFile = $this->getImageFile($id);
return view('bending.base.form', ['item' => $item, 'mode' => 'edit', 'imageFile' => $imageFile]);
}
public function store(Request $request)
{
$validated = $request->validate([
'code' => 'required|string|max:100',
'name' => 'required|string|max:200',
'item_sep' => 'required|in:스크린,철재',
'item_bending' => 'required|string|max:50',
'item_name' => 'required|string|max:50',
'material' => 'required|string|max:50',
'model_UA' => 'nullable|in:인정,비인정',
], [
'code.required' => '코드를 입력하세요.',
'name.required' => '이름을 입력하세요.',
'item_sep.required' => '대분류를 선택하세요.',
'item_sep.in' => '대분류는 스크린 또는 철재만 선택 가능합니다.',
'item_bending.required' => '분류를 선택하세요.',
'item_name.required' => '품명을 입력하세요.',
'material.required' => '재질을 입력하세요.',
'model_UA.in' => '인정여부는 인정 또는 비인정만 선택 가능합니다.',
]);
$data = $this->prepareApiData($request);
$response = $this->api()->post('/api/v1/bending-items', $data);
if (! $response->successful()) {
$body = $response->json();
\Log::error('BendingBase store API error', ['status' => $response->status(), 'body' => $body, 'sent_data' => $data]);
$apiErrors = $body['errors'] ?? $body['error']['details'] ?? $body['data']['errors'] ?? [];
$apiMessage = $body['message'] ?? 'API 오류';
$errorBag = ['api' => "[{$response->status()}] {$apiMessage}"];
foreach ($apiErrors as $field => $msgs) {
$fieldLabel = match($field) {
'code' => '코드', 'name' => '이름', 'item_sep' => '대분류',
'item_bending' => '분류', 'item_name' => '품명', 'material' => '재질',
default => $field,
};
$errorBag["api_{$field}"] = "[{$fieldLabel}] " . (is_array($msgs) ? implode(', ', $msgs) : $msgs);
}
return back()->withErrors($errorBag)->withInput();
}
$itemId = $response->json('data.id');
if ($itemId) {
$this->handleImageUpload($request, $itemId);
}
return redirect()->route('bending.base.index')->with('success', '절곡품이 등록되었습니다.');
}
public function update(Request $request, int $id)
{
$validated = $request->validate([
'code' => 'required|string|max:100',
'name' => 'required|string|max:200',
'item_sep' => 'required|in:스크린,철재',
'item_bending' => 'required|string|max:50',
'item_name' => 'required|string|max:50',
'material' => 'required|string|max:50',
'model_UA' => 'nullable|in:인정,비인정',
], [
'code.required' => '코드를 입력하세요.',
'name.required' => '이름을 입력하세요.',
'item_sep.required' => '대분류를 선택하세요.',
'item_sep.in' => '대분류는 스크린 또는 철재만 선택 가능합니다.',
'item_bending.required' => '분류를 선택하세요.',
'item_name.required' => '품명을 입력하세요.',
'material.required' => '재질을 입력하세요.',
'model_UA.in' => '인정여부는 인정 또는 비인정만 선택 가능합니다.',
]);
$data = $this->prepareApiData($request);
$response = $this->api()->put("/api/v1/bending-items/{$id}", $data);
if (! $response->successful()) {
return back()->withErrors(['api' => $response->json('message', 'API 오류')])->withInput();
}
$this->handleImageUpload($request, $id);
return redirect()->route('bending.base.show', $id)->with('success', '절곡품이 수정되었습니다.');
}
public function destroy(int $id)
{
$this->api()->delete("/api/v1/bending-items/{$id}");
return redirect()->route('bending.base.index')->with('success', '절곡품이 삭제되었습니다.');
}
private function getImageFile(int $itemId): ?array
{
$response = $this->api()->get("/api/v1/items/{$itemId}/files", ['field_key' => 'bending_diagram']);
if (! $response->successful()) {
return null;
}
$files = $response->json('data.bending_diagram', []);
return ! empty($files) ? $files[0] : null;
}
private function uploadImage(int $itemId, \Illuminate\Http\UploadedFile $file): ?array
{
$existing = $this->getImageFile($itemId);
$postData = ['field_key' => 'bending_diagram'];
if ($existing) {
$postData['file_id'] = $existing['id'];
}
$response = $this->api()
->attach('file', $file->getContent(), $file->getClientOriginalName())
->post("/api/v1/items/{$itemId}/files", $postData);
if (! $response->successful()) {
\Log::error('Bending image upload failed', [
'itemId' => $itemId,
'status' => $response->status(),
'body' => $response->json(),
]);
}
return $response->successful() ? $response->json('data') : null;
}
/**
* 파일 업로드 또는 Canvas Base64 이미지 처리
*/
private function handleImageUpload(Request $request, int $itemId): void
{
if ($request->hasFile('image')) {
$this->uploadImage($itemId, $request->file('image'));
} elseif ($request->filled('canvas_image')) {
$this->uploadCanvasImage($itemId, $request->input('canvas_image'));
}
}
/**
* Canvas Base64 DataURL 임시 파일 API 업로드
*/
private function uploadCanvasImage(int $itemId, string $dataURL): ?array
{
// data:image/png;base64,... 형식 파싱
if (! preg_match('/^data:image\/(\w+);base64,/', $dataURL, $matches)) {
return null;
}
$ext = $matches[1] === 'jpeg' ? 'jpg' : $matches[1];
$base64 = substr($dataURL, strpos($dataURL, ',') + 1);
$binary = base64_decode($base64);
if ($binary === false) {
return null;
}
// 임시 파일 생성
$tmpPath = tempnam(sys_get_temp_dir(), 'canvas_') . '.' . $ext;
file_put_contents($tmpPath, $binary);
try {
$file = new \Illuminate\Http\UploadedFile($tmpPath, "canvas.{$ext}", "image/{$ext}", null, true);
return $this->uploadImage($itemId, $file);
} finally {
@unlink($tmpPath);
}
}
private function prepareApiData(Request $request): array
{
$data = $request->except(['_token', '_method', 'image', 'canvas_image']);
if (isset($data['bendingData']) && is_string($data['bendingData'])) {
$decoded = json_decode($data['bendingData'], true);
$data['bendingData'] = is_array($decoded) ? $decoded : null;
}
// 빈 문자열도 전송 (기존 값 삭제 가능하도록) — null만 제거
return array_filter($data, fn ($v) => $v !== null);
}
}