Files
sam-api/routes/api/v1/sales.php
권혁성 a96499a66d feat: API 라우터 분리 및 버전 폴백 시스템 구현
- api.php를 13개 도메인별 파일로 분리 (1,479줄 → 61줄)
- ApiVersionMiddleware 생성 (헤더/쿼리 기반 버전 선택)
- v2 요청 시 v2 없으면 v1으로 자동 폴백
- 지원 헤더: Accept-Version, X-API-Version, api_version 쿼리

분리된 도메인:
auth, admin, users, tenants, hr, finance, sales,
inventory, production, design, files, boards, common

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:30:19 +09:00

162 lines
12 KiB
PHP

<?php
/**
* 영업 관리 API 라우트 (v1)
*
* - 거래처/거래처그룹 관리
* - 견적/입찰 관리
* - 수주 관리
* - 매출 관리
* - 회사 추가 관리
*/
use App\Http\Controllers\Api\V1\BiddingController;
use App\Http\Controllers\Api\V1\ClientController;
use App\Http\Controllers\Api\V1\ClientGroupController;
use App\Http\Controllers\Api\V1\CompanyController;
use App\Http\Controllers\Api\V1\EstimateController;
use App\Http\Controllers\Api\V1\OrderController;
use App\Http\Controllers\Api\V1\PricingController;
use App\Http\Controllers\Api\V1\QuoteController;
use App\Http\Controllers\Api\V1\SaleController;
use Illuminate\Support\Facades\Route;
// Client API (거래처 관리)
Route::prefix('clients')->group(function () {
Route::get('', [ClientController::class, 'index'])->name('v1.clients.index');
Route::post('', [ClientController::class, 'store'])->name('v1.clients.store');
Route::get('/active', [ClientController::class, 'active'])->name('v1.clients.active');
Route::get('/stats', [ClientController::class, 'stats'])->name('v1.clients.stats');
Route::get('/{id}', [ClientController::class, 'show'])->whereNumber('id')->name('v1.clients.show');
Route::put('/{id}', [ClientController::class, 'update'])->whereNumber('id')->name('v1.clients.update');
Route::delete('/{id}', [ClientController::class, 'destroy'])->whereNumber('id')->name('v1.clients.destroy');
Route::patch('/{id}/toggle', [ClientController::class, 'toggle'])->whereNumber('id')->name('v1.clients.toggle');
});
// Client Group API (거래처 그룹 관리)
Route::prefix('client-groups')->group(function () {
Route::get('', [ClientGroupController::class, 'index'])->name('v1.client-groups.index');
Route::post('', [ClientGroupController::class, 'store'])->name('v1.client-groups.store');
Route::get('/{id}', [ClientGroupController::class, 'show'])->whereNumber('id')->name('v1.client-groups.show');
Route::put('/{id}', [ClientGroupController::class, 'update'])->whereNumber('id')->name('v1.client-groups.update');
Route::delete('/{id}', [ClientGroupController::class, 'destroy'])->whereNumber('id')->name('v1.client-groups.destroy');
});
// Quote API (견적 관리)
Route::prefix('quotes')->group(function () {
Route::get('', [QuoteController::class, 'index'])->name('v1.quotes.index');
Route::get('/stats', [QuoteController::class, 'stats'])->name('v1.quotes.stats');
Route::get('/stage-counts', [QuoteController::class, 'stageCounts'])->name('v1.quotes.stage-counts');
Route::post('', [QuoteController::class, 'store'])->name('v1.quotes.store');
Route::delete('/bulk', [QuoteController::class, 'bulkDestroy'])->name('v1.quotes.bulk-destroy');
Route::get('/{id}', [QuoteController::class, 'show'])->whereNumber('id')->name('v1.quotes.show');
Route::put('/{id}', [QuoteController::class, 'update'])->whereNumber('id')->name('v1.quotes.update');
Route::delete('/{id}', [QuoteController::class, 'destroy'])->whereNumber('id')->name('v1.quotes.destroy');
Route::post('/{id}/clone', [QuoteController::class, 'clone'])->whereNumber('id')->name('v1.quotes.clone');
Route::put('/{id}/stage', [QuoteController::class, 'updateStage'])->whereNumber('id')->name('v1.quotes.stage');
Route::put('/{id}/items', [QuoteController::class, 'updateItems'])->whereNumber('id')->name('v1.quotes.items');
Route::get('/{id}/histories', [QuoteController::class, 'histories'])->whereNumber('id')->name('v1.quotes.histories');
Route::post('/{id}/histories', [QuoteController::class, 'addHistory'])->whereNumber('id')->name('v1.quotes.histories.store');
Route::put('/{id}/histories/{historyId}', [QuoteController::class, 'updateHistory'])->whereNumber('id')->whereNumber('historyId')->name('v1.quotes.histories.update');
Route::delete('/{id}/histories/{historyId}', [QuoteController::class, 'deleteHistory'])->whereNumber('id')->whereNumber('historyId')->name('v1.quotes.histories.destroy');
// 견적서 관련 API
Route::get('/{id}/document', [QuoteController::class, 'getDocument'])->whereNumber('id')->name('v1.quotes.document.show');
Route::post('/{id}/document/issue', [QuoteController::class, 'issueDocument'])->whereNumber('id')->name('v1.quotes.document.issue');
Route::post('/{id}/document/send', [QuoteController::class, 'sendDocument'])->whereNumber('id')->name('v1.quotes.document.send');
Route::post('/bulk-issue-document', [QuoteController::class, 'bulkIssueDocument'])->name('v1.quotes.bulk-issue-document');
});
// Bidding API (입찰 관리)
Route::prefix('biddings')->group(function () {
Route::get('', [BiddingController::class, 'index'])->name('v1.biddings.index');
Route::post('', [BiddingController::class, 'store'])->name('v1.biddings.store');
Route::get('/stats', [BiddingController::class, 'stats'])->name('v1.biddings.stats');
Route::delete('/bulk', [BiddingController::class, 'bulkDestroy'])->name('v1.biddings.bulk-destroy');
Route::get('/{id}', [BiddingController::class, 'show'])->whereNumber('id')->name('v1.biddings.show');
Route::put('/{id}', [BiddingController::class, 'update'])->whereNumber('id')->name('v1.biddings.update');
Route::delete('/{id}', [BiddingController::class, 'destroy'])->whereNumber('id')->name('v1.biddings.destroy');
});
// Pricing API (단가 관리)
Route::prefix('pricing')->group(function () {
Route::get('', [PricingController::class, 'index'])->name('v1.pricing.index');
Route::post('', [PricingController::class, 'store'])->name('v1.pricing.store');
Route::get('/types', [PricingController::class, 'types'])->name('v1.pricing.types');
Route::get('/summary', [PricingController::class, 'summary'])->name('v1.pricing.summary');
Route::get('/history/{itemId}', [PricingController::class, 'history'])->whereNumber('itemId')->name('v1.pricing.history');
Route::post('/bulk-upsert', [PricingController::class, 'bulkUpsert'])->name('v1.pricing.bulk-upsert');
Route::get('/{id}', [PricingController::class, 'show'])->whereNumber('id')->name('v1.pricing.show');
Route::put('/{id}', [PricingController::class, 'update'])->whereNumber('id')->name('v1.pricing.update');
Route::delete('/{id}', [PricingController::class, 'destroy'])->whereNumber('id')->name('v1.pricing.destroy');
});
// Estimate API (견적/설계)
Route::prefix('estimates')->group(function () {
Route::get('', [EstimateController::class, 'index'])->name('v1.estimates.index'); // 견적 목록
Route::get('/stats', [EstimateController::class, 'stats'])->name('v1.estimates.stats'); // 견적 통계
Route::post('', [EstimateController::class, 'store'])->name('v1.estimates.store'); // 견적 생성
Route::get('/{id}', [EstimateController::class, 'show'])->whereNumber('id')->name('v1.estimates.show'); // 견적 상세
Route::put('/{id}', [EstimateController::class, 'update'])->whereNumber('id')->name('v1.estimates.update'); // 견적 수정
Route::delete('/{id}', [EstimateController::class, 'destroy'])->whereNumber('id')->name('v1.estimates.destroy'); // 견적 삭제
Route::post('/{id}/clone', [EstimateController::class, 'clone'])->name('v1.estimates.clone'); // 견적 복제
Route::put('/{id}/status', [EstimateController::class, 'changeStatus'])->name('v1.estimates.status'); // 견적 상태 변경
// 견적 폼 및 계산 기능
Route::get('/form-schema/{model_set_id}', [EstimateController::class, 'getFormSchema'])->name('v1.estimates.form-schema'); // 견적 폼 스키마
Route::post('/preview/{model_set_id}', [EstimateController::class, 'previewCalculation'])->name('v1.estimates.preview'); // 견적 계산 미리보기
});
// Order API (수주관리)
Route::prefix('orders')->group(function () {
// 기본 CRUD
Route::get('', [OrderController::class, 'index'])->name('v1.orders.index'); // 목록
Route::get('/stats', [OrderController::class, 'stats'])->name('v1.orders.stats'); // 통계
Route::post('', [OrderController::class, 'store'])->name('v1.orders.store'); // 생성
Route::get('/{id}', [OrderController::class, 'show'])->whereNumber('id')->name('v1.orders.show'); // 상세
Route::put('/{id}', [OrderController::class, 'update'])->whereNumber('id')->name('v1.orders.update'); // 수정
Route::delete('/{id}', [OrderController::class, 'destroy'])->whereNumber('id')->name('v1.orders.destroy'); // 삭제
// 상태 관리
Route::patch('/{id}/status', [OrderController::class, 'updateStatus'])->whereNumber('id')->name('v1.orders.status'); // 상태 변경
// 견적에서 수주 생성
Route::post('/from-quote/{quoteId}', [OrderController::class, 'createFromQuote'])->whereNumber('quoteId')->name('v1.orders.from-quote');
// 생산지시 생성
Route::post('/{id}/production-order', [OrderController::class, 'createProductionOrder'])->whereNumber('id')->name('v1.orders.production-order');
// 수주확정 되돌리기
Route::post('/{id}/revert-confirmation', [OrderController::class, 'revertOrderConfirmation'])->whereNumber('id')->name('v1.orders.revert-confirmation');
// 생산지시 되돌리기
Route::post('/{id}/revert-production', [OrderController::class, 'revertProductionOrder'])->whereNumber('id')->name('v1.orders.revert-production');
});
// Sale API (매출 관리)
Route::prefix('sales')->group(function () {
Route::get('', [SaleController::class, 'index'])->name('v1.sales.index');
Route::post('', [SaleController::class, 'store'])->name('v1.sales.store');
Route::get('/summary', [SaleController::class, 'summary'])->name('v1.sales.summary');
Route::get('/{id}', [SaleController::class, 'show'])->whereNumber('id')->name('v1.sales.show');
Route::put('/{id}', [SaleController::class, 'update'])->whereNumber('id')->name('v1.sales.update');
Route::delete('/{id}', [SaleController::class, 'destroy'])->whereNumber('id')->name('v1.sales.destroy');
Route::post('/{id}/confirm', [SaleController::class, 'confirm'])->whereNumber('id')->name('v1.sales.confirm');
Route::put('/bulk-update-account', [SaleController::class, 'bulkUpdateAccountCode'])->name('v1.sales.bulk-update-account');
// 거래명세서 API
Route::get('/{id}/statement', [SaleController::class, 'getStatement'])->whereNumber('id')->name('v1.sales.statement.show');
Route::post('/{id}/statement/issue', [SaleController::class, 'issueStatement'])->whereNumber('id')->name('v1.sales.statement.issue');
Route::post('/{id}/statement/send', [SaleController::class, 'sendStatement'])->whereNumber('id')->name('v1.sales.statement.send');
Route::post('/bulk-issue-statement', [SaleController::class, 'bulkIssueStatement'])->name('v1.sales.bulk-issue-statement');
});
// Company API (회사 추가 관리)
Route::prefix('companies')->group(function () {
Route::post('/check', [CompanyController::class, 'check'])->name('v1.companies.check'); // 사업자등록번호 검증
Route::post('/request', [CompanyController::class, 'request'])->name('v1.companies.request'); // 회사 추가 신청
Route::get('/requests', [CompanyController::class, 'requests'])->name('v1.companies.requests.index'); // 신청 목록 (관리자)
Route::get('/requests/{id}', [CompanyController::class, 'showRequest'])->whereNumber('id')->name('v1.companies.requests.show'); // 신청 상세
Route::post('/requests/{id}/approve', [CompanyController::class, 'approve'])->whereNumber('id')->name('v1.companies.requests.approve'); // 승인
Route::post('/requests/{id}/reject', [CompanyController::class, 'reject'])->whereNumber('id')->name('v1.companies.requests.reject'); // 반려
Route::get('/my-requests', [CompanyController::class, 'myRequests'])->name('v1.companies.my-requests'); // 내 신청 목록
});