diff --git a/app/Http/Controllers/Api/V1/ExpectedExpenseController.php b/app/Http/Controllers/Api/V1/ExpectedExpenseController.php new file mode 100644 index 0000000..468ddf9 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ExpectedExpenseController.php @@ -0,0 +1,127 @@ +only([ + 'search', + 'start_date', + 'end_date', + 'client_id', + 'transaction_type', + 'payment_status', + 'approval_status', + 'sort_by', + 'sort_dir', + 'per_page', + 'page', + ]); + + $expenses = $this->service->index($params); + + return ApiResponse::success($expenses, __('message.fetched')); + } + + /** + * 미지급비용 등록 + */ + public function store(StoreExpectedExpenseRequest $request) + { + $expense = $this->service->store($request->validated()); + + return ApiResponse::success($expense, __('message.created'), [], 201); + } + + /** + * 미지급비용 상세 + */ + public function show(int $id) + { + $expense = $this->service->show($id); + + return ApiResponse::success($expense, __('message.fetched')); + } + + /** + * 미지급비용 수정 + */ + public function update(int $id, UpdateExpectedExpenseRequest $request) + { + $expense = $this->service->update($id, $request->validated()); + + return ApiResponse::success($expense, __('message.updated')); + } + + /** + * 미지급비용 삭제 + */ + public function destroy(int $id) + { + $this->service->destroy($id); + + return ApiResponse::success(null, __('message.deleted')); + } + + /** + * 미지급비용 일괄 삭제 + */ + public function destroyMany(Request $request) + { + $ids = $request->input('ids', []); + + if (empty($ids)) { + return ApiResponse::error(__('error.no_ids_provided'), 400); + } + + $count = $this->service->destroyMany($ids); + + return ApiResponse::success(['deleted_count' => $count], __('message.deleted')); + } + + /** + * 예상 지급일 일괄 변경 + */ + public function updateExpectedPaymentDate(UpdateExpectedPaymentDateRequest $request) + { + $count = $this->service->updateExpectedPaymentDate( + $request->input('ids'), + $request->input('expected_payment_date') + ); + + return ApiResponse::success(['updated_count' => $count], __('message.updated')); + } + + /** + * 미지급비용 요약 (기간별 합계) + */ + public function summary(Request $request) + { + $params = $request->only([ + 'start_date', + 'end_date', + 'payment_status', + ]); + + $summary = $this->service->summary($params); + + return ApiResponse::success($summary, __('message.fetched')); + } +} diff --git a/app/Http/Requests/V1/ExpectedExpense/StoreExpectedExpenseRequest.php b/app/Http/Requests/V1/ExpectedExpense/StoreExpectedExpenseRequest.php new file mode 100644 index 0000000..ed7ed7e --- /dev/null +++ b/app/Http/Requests/V1/ExpectedExpense/StoreExpectedExpenseRequest.php @@ -0,0 +1,58 @@ + ['required', 'date'], + 'settlement_date' => ['nullable', 'date'], + 'transaction_type' => ['required', 'string', 'in:purchase,advance,suspense,rent,salary,insurance,tax,utilities,other'], + 'amount' => ['required', 'numeric', 'min:0'], + 'client_id' => ['nullable', 'integer', 'exists:clients,id'], + 'client_name' => ['nullable', 'string', 'max:100'], + 'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'], + 'account_code' => ['nullable', 'string', 'max:50'], + 'payment_status' => ['nullable', 'string', 'in:pending,partial,paid,overdue'], + 'approval_status' => ['nullable', 'string', 'in:none,pending,approved,rejected'], + 'description' => ['nullable', 'string', 'max:1000'], + ]; + } + + public function messages(): array + { + return [ + 'expected_payment_date.required' => __('validation.required', ['attribute' => '예상 지급일']), + 'transaction_type.required' => __('validation.required', ['attribute' => '거래유형']), + 'transaction_type.in' => __('validation.in', ['attribute' => '거래유형']), + 'amount.required' => __('validation.required', ['attribute' => '금액']), + 'amount.min' => __('validation.min.numeric', ['attribute' => '금액', 'min' => 0]), + ]; + } + + public function attributes(): array + { + return [ + 'expected_payment_date' => '예상 지급일', + 'settlement_date' => '결제일', + 'transaction_type' => '거래유형', + 'amount' => '금액', + 'client_id' => '거래처', + 'client_name' => '거래처명', + 'bank_account_id' => '계좌', + 'account_code' => '계정과목', + 'payment_status' => '지급상태', + 'approval_status' => '결재상태', + 'description' => '적요', + ]; + } +} diff --git a/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedExpenseRequest.php b/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedExpenseRequest.php new file mode 100644 index 0000000..00d7461 --- /dev/null +++ b/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedExpenseRequest.php @@ -0,0 +1,47 @@ + ['sometimes', 'date'], + 'settlement_date' => ['nullable', 'date'], + 'transaction_type' => ['sometimes', 'string', 'in:purchase,advance,suspense,rent,salary,insurance,tax,utilities,other'], + 'amount' => ['sometimes', 'numeric', 'min:0'], + 'client_id' => ['nullable', 'integer', 'exists:clients,id'], + 'client_name' => ['nullable', 'string', 'max:100'], + 'bank_account_id' => ['nullable', 'integer', 'exists:bank_accounts,id'], + 'account_code' => ['nullable', 'string', 'max:50'], + 'payment_status' => ['sometimes', 'string', 'in:pending,partial,paid,overdue'], + 'approval_status' => ['sometimes', 'string', 'in:none,pending,approved,rejected'], + 'description' => ['nullable', 'string', 'max:1000'], + ]; + } + + public function attributes(): array + { + return [ + 'expected_payment_date' => '예상 지급일', + 'settlement_date' => '결제일', + 'transaction_type' => '거래유형', + 'amount' => '금액', + 'client_id' => '거래처', + 'client_name' => '거래처명', + 'bank_account_id' => '계좌', + 'account_code' => '계정과목', + 'payment_status' => '지급상태', + 'approval_status' => '결재상태', + 'description' => '적요', + ]; + } +} diff --git a/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedPaymentDateRequest.php b/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedPaymentDateRequest.php new file mode 100644 index 0000000..0d9d539 --- /dev/null +++ b/app/Http/Requests/V1/ExpectedExpense/UpdateExpectedPaymentDateRequest.php @@ -0,0 +1,39 @@ + ['required', 'array', 'min:1'], + 'ids.*' => ['required', 'integer'], + 'expected_payment_date' => ['required', 'date'], + ]; + } + + public function messages(): array + { + return [ + 'ids.required' => __('validation.required', ['attribute' => '대상 ID']), + 'ids.min' => __('validation.min.array', ['attribute' => '대상 ID', 'min' => 1]), + 'expected_payment_date.required' => __('validation.required', ['attribute' => '예상 지급일']), + ]; + } + + public function attributes(): array + { + return [ + 'ids' => '대상 ID', + 'expected_payment_date' => '예상 지급일', + ]; + } +} diff --git a/app/Models/Tenants/ExpectedExpense.php b/app/Models/Tenants/ExpectedExpense.php new file mode 100644 index 0000000..7ad94e1 --- /dev/null +++ b/app/Models/Tenants/ExpectedExpense.php @@ -0,0 +1,134 @@ + 'date', + 'settlement_date' => 'date', + 'amount' => 'decimal:2', + 'client_id' => 'integer', + 'bank_account_id' => 'integer', + ]; + + /** + * 거래유형 목록 + */ + public const TRANSACTION_TYPES = [ + 'purchase' => '매입', + 'advance' => '선급금', + 'suspense' => '가지급금', + 'rent' => '임대료', + 'salary' => '급여', + 'insurance' => '보험료', + 'tax' => '세금', + 'utilities' => '공과금', + 'other' => '기타', + ]; + + /** + * 지급상태 목록 + */ + public const PAYMENT_STATUSES = [ + 'pending' => '미지급', + 'partial' => '부분지급', + 'paid' => '지급완료', + 'overdue' => '연체', + ]; + + /** + * 결재상태 목록 + */ + public const APPROVAL_STATUSES = [ + 'none' => '미신청', + 'pending' => '결재대기', + 'approved' => '결재완료', + 'rejected' => '반려', + ]; + + /** + * 거래처 관계 + */ + public function client(): BelongsTo + { + return $this->belongsTo(\App\Models\Orders\Client::class); + } + + /** + * 계좌 관계 + */ + public function bankAccount(): BelongsTo + { + return $this->belongsTo(BankAccount::class); + } + + /** + * 생성자 관계 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(\App\Models\Members\User::class, 'created_by'); + } + + /** + * 거래처명 조회 (회원/비회원 통합) + */ + public function getDisplayClientNameAttribute(): string + { + if ($this->client) { + return $this->client->name; + } + + return $this->client_name ?? ''; + } + + /** + * 거래유형 라벨 + */ + public function getTransactionTypeLabelAttribute(): string + { + return self::TRANSACTION_TYPES[$this->transaction_type] ?? $this->transaction_type; + } + + /** + * 지급상태 라벨 + */ + public function getPaymentStatusLabelAttribute(): string + { + return self::PAYMENT_STATUSES[$this->payment_status] ?? $this->payment_status; + } + + /** + * 결재상태 라벨 + */ + public function getApprovalStatusLabelAttribute(): string + { + return self::APPROVAL_STATUSES[$this->approval_status] ?? $this->approval_status; + } +} diff --git a/app/Services/ExpectedExpenseService.php b/app/Services/ExpectedExpenseService.php new file mode 100644 index 0000000..75f90ab --- /dev/null +++ b/app/Services/ExpectedExpenseService.php @@ -0,0 +1,302 @@ +tenantId(); + + $query = ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->with(['client:id,name', 'bankAccount:id,bank_name,account_name']); + + // 검색어 필터 + if (! empty($params['search'])) { + $search = $params['search']; + $query->where(function ($q) use ($search) { + $q->where('client_name', 'like', "%{$search}%") + ->orWhere('account_code', 'like', "%{$search}%") + ->orWhere('description', 'like', "%{$search}%") + ->orWhereHas('client', function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%"); + }); + }); + } + + // 날짜 범위 필터 (예상 지급일 기준) + if (! empty($params['start_date'])) { + $query->where('expected_payment_date', '>=', $params['start_date']); + } + if (! empty($params['end_date'])) { + $query->where('expected_payment_date', '<=', $params['end_date']); + } + + // 거래처 필터 + if (! empty($params['client_id'])) { + $query->where('client_id', $params['client_id']); + } + + // 거래유형 필터 + if (! empty($params['transaction_type'])) { + $query->where('transaction_type', $params['transaction_type']); + } + + // 지급상태 필터 + if (! empty($params['payment_status'])) { + $query->where('payment_status', $params['payment_status']); + } + + // 결재상태 필터 + if (! empty($params['approval_status'])) { + $query->where('approval_status', $params['approval_status']); + } + + // 정렬 + $sortBy = $params['sort_by'] ?? 'expected_payment_date'; + $sortDir = $params['sort_dir'] ?? 'asc'; + $query->orderBy($sortBy, $sortDir); + + // 페이지네이션 + $perPage = $params['per_page'] ?? 50; + + return $query->paginate($perPage); + } + + /** + * 미지급비용 상세 조회 + */ + public function show(int $id): ExpectedExpense + { + $tenantId = $this->tenantId(); + + return ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->with(['client:id,name', 'bankAccount:id,bank_name,account_name', 'creator:id,name']) + ->findOrFail($id); + } + + /** + * 미지급비용 등록 + */ + public function store(array $data): ExpectedExpense + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + return DB::transaction(function () use ($data, $tenantId, $userId) { + $expense = new ExpectedExpense; + $expense->tenant_id = $tenantId; + $expense->expected_payment_date = $data['expected_payment_date']; + $expense->settlement_date = $data['settlement_date'] ?? null; + $expense->transaction_type = $data['transaction_type']; + $expense->amount = $data['amount']; + $expense->client_id = $data['client_id'] ?? null; + $expense->client_name = $data['client_name'] ?? null; + $expense->bank_account_id = $data['bank_account_id'] ?? null; + $expense->account_code = $data['account_code'] ?? null; + $expense->payment_status = $data['payment_status'] ?? 'pending'; + $expense->approval_status = $data['approval_status'] ?? 'none'; + $expense->description = $data['description'] ?? null; + $expense->created_by = $userId; + $expense->updated_by = $userId; + $expense->save(); + + return $expense->load(['client:id,name', 'bankAccount:id,bank_name,account_name']); + }); + } + + /** + * 미지급비용 수정 + */ + public function update(int $id, array $data): ExpectedExpense + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + return DB::transaction(function () use ($id, $data, $tenantId, $userId) { + $expense = ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->findOrFail($id); + + if (isset($data['expected_payment_date'])) { + $expense->expected_payment_date = $data['expected_payment_date']; + } + if (array_key_exists('settlement_date', $data)) { + $expense->settlement_date = $data['settlement_date']; + } + if (isset($data['transaction_type'])) { + $expense->transaction_type = $data['transaction_type']; + } + if (isset($data['amount'])) { + $expense->amount = $data['amount']; + } + if (array_key_exists('client_id', $data)) { + $expense->client_id = $data['client_id']; + } + if (array_key_exists('client_name', $data)) { + $expense->client_name = $data['client_name']; + } + if (array_key_exists('bank_account_id', $data)) { + $expense->bank_account_id = $data['bank_account_id']; + } + if (array_key_exists('account_code', $data)) { + $expense->account_code = $data['account_code']; + } + if (isset($data['payment_status'])) { + $expense->payment_status = $data['payment_status']; + } + if (isset($data['approval_status'])) { + $expense->approval_status = $data['approval_status']; + } + if (array_key_exists('description', $data)) { + $expense->description = $data['description']; + } + + $expense->updated_by = $userId; + $expense->save(); + + return $expense->fresh(['client:id,name', 'bankAccount:id,bank_name,account_name']); + }); + } + + /** + * 미지급비용 삭제 + */ + public function destroy(int $id): bool + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + return DB::transaction(function () use ($id, $tenantId, $userId) { + $expense = ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->findOrFail($id); + + $expense->deleted_by = $userId; + $expense->save(); + $expense->delete(); + + return true; + }); + } + + /** + * 일괄 삭제 + */ + public function destroyMany(array $ids): int + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + return DB::transaction(function () use ($ids, $tenantId, $userId) { + $count = 0; + foreach ($ids as $id) { + $expense = ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->find($id); + + if ($expense) { + $expense->deleted_by = $userId; + $expense->save(); + $expense->delete(); + $count++; + } + } + + return $count; + }); + } + + /** + * 예상 지급일 일괄 변경 + */ + public function updateExpectedPaymentDate(array $ids, string $newDate): int + { + $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + + return DB::transaction(function () use ($ids, $newDate, $tenantId, $userId) { + return ExpectedExpense::query() + ->where('tenant_id', $tenantId) + ->whereIn('id', $ids) + ->update([ + 'expected_payment_date' => $newDate, + 'updated_by' => $userId, + ]); + }); + } + + /** + * 미지급비용 요약 (기간별 합계) + */ + public function summary(array $params): array + { + $tenantId = $this->tenantId(); + + $query = ExpectedExpense::query() + ->where('tenant_id', $tenantId); + + // 날짜 범위 필터 + if (! empty($params['start_date'])) { + $query->where('expected_payment_date', '>=', $params['start_date']); + } + if (! empty($params['end_date'])) { + $query->where('expected_payment_date', '<=', $params['end_date']); + } + + // 지급상태 필터 + if (! empty($params['payment_status'])) { + $query->where('payment_status', $params['payment_status']); + } + + // 전체 합계 + $total = (clone $query)->sum('amount'); + $count = (clone $query)->count(); + + // 지급상태별 합계 + $byPaymentStatus = (clone $query) + ->select('payment_status', DB::raw('SUM(amount) as total'), DB::raw('COUNT(*) as count')) + ->groupBy('payment_status') + ->get() + ->keyBy('payment_status') + ->toArray(); + + // 거래유형별 합계 + $byTransactionType = (clone $query) + ->select('transaction_type', DB::raw('SUM(amount) as total'), DB::raw('COUNT(*) as count')) + ->groupBy('transaction_type') + ->get() + ->keyBy('transaction_type') + ->toArray(); + + // 월별 합계 + $byMonth = (clone $query) + ->select( + DB::raw("DATE_FORMAT(expected_payment_date, '%Y-%m') as month"), + DB::raw('SUM(amount) as total'), + DB::raw('COUNT(*) as count') + ) + ->groupBy('month') + ->orderBy('month') + ->get() + ->keyBy('month') + ->toArray(); + + return [ + 'total_amount' => (float) $total, + 'total_count' => $count, + 'by_payment_status' => $byPaymentStatus, + 'by_transaction_type' => $byTransactionType, + 'by_month' => $byMonth, + ]; + } +} diff --git a/app/Swagger/v1/ExpectedExpenseApi.php b/app/Swagger/v1/ExpectedExpenseApi.php new file mode 100644 index 0000000..c503ad7 --- /dev/null +++ b/app/Swagger/v1/ExpectedExpenseApi.php @@ -0,0 +1,300 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->date('expected_payment_date')->comment('예상 지급일'); + $table->date('settlement_date')->nullable()->comment('결제일'); + $table->string('transaction_type', 30)->comment('거래유형: purchase/advance/suspense/rent/salary/insurance/tax/utilities/other'); + $table->decimal('amount', 15, 2)->comment('지출금액'); + $table->unsignedBigInteger('client_id')->nullable()->comment('거래처 ID'); + $table->string('client_name', 100)->nullable()->comment('비회원 거래처명'); + $table->unsignedBigInteger('bank_account_id')->nullable()->comment('계좌 ID'); + $table->string('account_code', 50)->nullable()->comment('계정과목'); + $table->string('payment_status', 20)->default('pending')->comment('지급상태: pending/partial/paid/overdue'); + $table->string('approval_status', 20)->default('none')->comment('결재상태: none/pending/approved/rejected'); + $table->text('description')->nullable()->comment('적요/메모'); + $table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID'); + $table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID'); + $table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID'); + $table->softDeletes(); + $table->timestamps(); + + $table->index(['tenant_id', 'expected_payment_date'], 'idx_tenant_expected_date'); + $table->index(['tenant_id', 'payment_status'], 'idx_tenant_payment_status'); + $table->index('client_id', 'idx_client'); + $table->index('transaction_type', 'idx_transaction_type'); + }); + } + + public function down(): void + { + Schema::dropIfExists('expected_expenses'); + } +}; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 23befaf..553ac69 100644 --- a/routes/api.php +++ b/routes/api.php @@ -34,6 +34,7 @@ use App\Http\Controllers\Api\V1\Design\DesignModelController; use App\Http\Controllers\Api\V1\Design\ModelVersionController as DesignModelVersionController; use App\Http\Controllers\Api\V1\EmployeeController; +use App\Http\Controllers\Api\V1\ExpectedExpenseController; use App\Http\Controllers\Api\V1\EstimateController; use App\Http\Controllers\Api\V1\FileStorageController; use App\Http\Controllers\Api\V1\FolderController; @@ -66,6 +67,7 @@ use App\Http\Controllers\Api\V1\PostController; use App\Http\Controllers\Api\V1\PricingController; use App\Http\Controllers\Api\V1\PurchaseController; +use App\Http\Controllers\Api\V1\ReceivingController; use App\Http\Controllers\Api\V1\PushNotificationController; use App\Http\Controllers\Api\V1\QuoteController; use App\Http\Controllers\Api\V1\RefreshController; @@ -89,6 +91,7 @@ use App\Http\Controllers\Api\V1\UserInvitationController; use App\Http\Controllers\Api\V1\UserRoleController; use App\Http\Controllers\Api\V1\WithdrawalController; +use App\Http\Controllers\Api\V1\WorkOrderController; use App\Http\Controllers\Api\V1\WorkSettingController; use Illuminate\Support\Facades\Route; @@ -445,6 +448,18 @@ Route::patch('/{id}/status', [SalaryController::class, 'updateStatus'])->whereNumber('id')->name('v1.salaries.update-status'); }); + // Expected Expense API (미지급비용 관리) + Route::prefix('expected-expenses')->group(function () { + Route::get('', [ExpectedExpenseController::class, 'index'])->name('v1.expected-expenses.index'); + Route::post('', [ExpectedExpenseController::class, 'store'])->name('v1.expected-expenses.store'); + Route::get('/summary', [ExpectedExpenseController::class, 'summary'])->name('v1.expected-expenses.summary'); + Route::delete('', [ExpectedExpenseController::class, 'destroyMany'])->name('v1.expected-expenses.destroy-many'); + Route::put('/update-payment-date', [ExpectedExpenseController::class, 'updateExpectedPaymentDate'])->name('v1.expected-expenses.update-payment-date'); + Route::get('/{id}', [ExpectedExpenseController::class, 'show'])->whereNumber('id')->name('v1.expected-expenses.show'); + Route::put('/{id}', [ExpectedExpenseController::class, 'update'])->whereNumber('id')->name('v1.expected-expenses.update'); + Route::delete('/{id}', [ExpectedExpenseController::class, 'destroy'])->whereNumber('id')->name('v1.expected-expenses.destroy'); + }); + // Loan API (가지급금 관리) Route::prefix('loans')->group(function () { Route::get('', [LoanController::class, 'index'])->name('v1.loans.index'); @@ -533,6 +548,17 @@ Route::post('/{id}/confirm', [PurchaseController::class, 'confirm'])->whereNumber('id')->name('v1.purchases.confirm'); }); + // Receiving API (입고 관리) + Route::prefix('receivings')->group(function () { + Route::get('', [ReceivingController::class, 'index'])->name('v1.receivings.index'); + Route::post('', [ReceivingController::class, 'store'])->name('v1.receivings.store'); + Route::get('/stats', [ReceivingController::class, 'stats'])->name('v1.receivings.stats'); + Route::get('/{id}', [ReceivingController::class, 'show'])->whereNumber('id')->name('v1.receivings.show'); + Route::put('/{id}', [ReceivingController::class, 'update'])->whereNumber('id')->name('v1.receivings.update'); + Route::delete('/{id}', [ReceivingController::class, 'destroy'])->whereNumber('id')->name('v1.receivings.destroy'); + Route::post('/{id}/process', [ReceivingController::class, 'process'])->whereNumber('id')->name('v1.receivings.process'); + }); + // Barobill Setting API (바로빌 설정) Route::prefix('barobill-settings')->group(function () { Route::get('', [BarobillSettingController::class, 'show'])->name('v1.barobill-settings.show'); @@ -926,6 +952,28 @@ Route::post('/preview/{model_set_id}', [EstimateController::class, 'previewCalculation'])->name('v1.estimates.preview'); // 견적 계산 미리보기 }); + // 작업지시 관리 API (Production) + Route::prefix('work-orders')->group(function () { + // 기본 CRUD + Route::get('', [WorkOrderController::class, 'index'])->name('v1.work-orders.index'); // 목록 + Route::get('/stats', [WorkOrderController::class, 'stats'])->name('v1.work-orders.stats'); // 통계 + Route::post('', [WorkOrderController::class, 'store'])->name('v1.work-orders.store'); // 생성 + Route::get('/{id}', [WorkOrderController::class, 'show'])->whereNumber('id')->name('v1.work-orders.show'); // 상세 + Route::put('/{id}', [WorkOrderController::class, 'update'])->whereNumber('id')->name('v1.work-orders.update'); // 수정 + Route::delete('/{id}', [WorkOrderController::class, 'destroy'])->whereNumber('id')->name('v1.work-orders.destroy'); // 삭제 + + // 상태 및 담당자 관리 + Route::patch('/{id}/status', [WorkOrderController::class, 'updateStatus'])->whereNumber('id')->name('v1.work-orders.status'); // 상태 변경 + Route::patch('/{id}/assign', [WorkOrderController::class, 'assign'])->whereNumber('id')->name('v1.work-orders.assign'); // 담당자 배정 + + // 벤딩 공정 상세 토글 + Route::patch('/{id}/bending/toggle', [WorkOrderController::class, 'toggleBendingField'])->whereNumber('id')->name('v1.work-orders.bending-toggle'); + + // 이슈 관리 + Route::post('/{id}/issues', [WorkOrderController::class, 'addIssue'])->whereNumber('id')->name('v1.work-orders.issues.store'); // 이슈 등록 + Route::patch('/{id}/issues/{issueId}/resolve', [WorkOrderController::class, 'resolveIssue'])->whereNumber('id')->name('v1.work-orders.issues.resolve'); // 이슈 해결 + }); + // 파일 저장소 API Route::prefix('files')->group(function () { Route::post('/upload', [FileStorageController::class, 'upload'])->name('v1.files.upload'); // 파일 업로드 (임시)