diff --git a/app/Http/Requests/Quote/QuoteIndexRequest.php b/app/Http/Requests/Quote/QuoteIndexRequest.php index 726d5fd..6840e52 100644 --- a/app/Http/Requests/Quote/QuoteIndexRequest.php +++ b/app/Http/Requests/Quote/QuoteIndexRequest.php @@ -33,6 +33,12 @@ protected function prepareForValidation(): void 'with_items' => filter_var($this->with_items, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), ]); } + + if ($this->has('for_order')) { + $this->merge([ + 'for_order' => filter_var($this->for_order, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), + ]); + } } public function rules(): array @@ -58,6 +64,7 @@ public function rules(): array 'sort_by' => 'nullable|in:registration_date,quote_number,client_name,total_amount,status,created_at', 'sort_order' => 'nullable|in:asc,desc', 'with_items' => 'nullable|boolean', // 수주 전환용 품목 포함 여부 + 'for_order' => 'nullable|boolean', // 수주 전환용: 이미 수주가 생성된 견적 제외 ]; } } \ No newline at end of file diff --git a/app/Models/Quote/Quote.php b/app/Models/Quote/Quote.php index 339ed4d..82cc1fb 100644 --- a/app/Models/Quote/Quote.php +++ b/app/Models/Quote/Quote.php @@ -175,13 +175,22 @@ public function item(): BelongsTo } /** - * 전환된 수주 + * 전환된 수주 (Quote.order_id 기준) */ public function order(): BelongsTo { return $this->belongsTo(Order::class); } + /** + * 이 견적을 참조하는 수주들 (Order.quote_id 기준) + * 수주 전환 여부 확인 시 사용 + */ + public function orders(): HasMany + { + return $this->hasMany(Order::class, 'quote_id'); + } + /** * 현장설명회 (자동생성 시 연결) */ diff --git a/app/Services/OrderService.php b/app/Services/OrderService.php index f7ab3e5..b2cd981 100644 --- a/app/Services/OrderService.php +++ b/app/Services/OrderService.php @@ -26,11 +26,21 @@ public function index(array $params) $clientId = $params['client_id'] ?? null; $dateFrom = $params['date_from'] ?? null; $dateTo = $params['date_to'] ?? null; + $forWorkOrder = filter_var($params['for_work_order'] ?? false, FILTER_VALIDATE_BOOLEAN); $query = Order::query() ->where('tenant_id', $tenantId) ->with(['client:id,name', 'items', 'quote:id,quote_number']); + // 작업지시 생성 가능한 수주만 필터링 + if ($forWorkOrder) { + // 1. DRAFT(등록) 상태만 (생산지시 전) + $query->where('status_code', Order::STATUS_DRAFT); + + // 2. 작업지시가 아직 없는 수주만 + $query->whereDoesntHave('workOrders'); + } + // 검색어 (수주번호, 현장명, 거래처명) if ($q !== '') { $query->where(function ($qq) use ($q) { @@ -40,8 +50,8 @@ public function index(array $params) }); } - // 상태 필터 - if ($status !== null) { + // 상태 필터 (for_work_order와 함께 사용시 무시) + if ($status !== null && ! $forWorkOrder) { $query->where('status_code', $status); } @@ -393,6 +403,13 @@ public function createFromQuote(int $quoteId, array $data = []) $order->refresh(); $order->recalculateTotals()->save(); + // 견적 상태를 '수주전환완료'로 변경 + $quote->update([ + 'status' => Quote::STATUS_CONVERTED, + 'order_id' => $order->id, + 'updated_by' => $userId, + ]); + return $order->load(['client:id,name', 'items', 'quote:id,quote_number']); }); } diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index be8aa0b..73c2c91 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -40,9 +40,18 @@ public function index(array $params): LengthAwarePaginator $sortBy = $params['sort_by'] ?? 'registration_date'; $sortOrder = $params['sort_order'] ?? 'desc'; $withItems = filter_var($params['with_items'] ?? false, FILTER_VALIDATE_BOOLEAN); + $forOrder = filter_var($params['for_order'] ?? false, FILTER_VALIDATE_BOOLEAN); $query = Quote::query()->where('tenant_id', $tenantId); + // 수주 전환용 조회: 아직 수주가 생성되지 않은 견적만 + if ($forOrder) { + // 1. Quote.order_id가 null인 것 (빠른 체크) + $query->whereNull('order_id'); + // 2. Orders 테이블에 해당 quote_id가 없는 것 (이중 체크, 인덱스 있음) + $query->whereDoesntHave('orders'); + } + // items 포함 (수주 전환용) if ($withItems) { $query->with(['items', 'client:id,name,contact_person,phone']);