feat(quote): quote_type 컬럼 추가 및 건설 견적 필터링 구현
- quotes 테이블에 quote_type 컬럼 추가 (manufacturing/construction) - Quote 모델에 TYPE 상수 및 스코프 메서드 추가 - QuoteService.index()에 quote_type 필터 적용 - QuoteService.upsertFromSiteBriefing()에 construction 타입 설정 - QuoteIndexRequest에 quote_type 유효성 검증 추가 - 기존 site_briefing_id 있는 데이터는 construction으로 자동 업데이트 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
use App\Models\Quote\Quote;
|
||||
use App\Models\Quote\QuoteItem;
|
||||
use App\Models\Quote\QuoteRevision;
|
||||
use App\Models\Tenants\SiteBriefing;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -30,6 +31,7 @@ public function index(array $params): LengthAwarePaginator
|
||||
$page = (int) ($params['page'] ?? 1);
|
||||
$size = (int) ($params['size'] ?? 20);
|
||||
$q = trim((string) ($params['q'] ?? ''));
|
||||
$quoteType = $params['quote_type'] ?? null;
|
||||
$status = $params['status'] ?? null;
|
||||
$productCategory = $params['product_category'] ?? null;
|
||||
$clientId = $params['client_id'] ?? null;
|
||||
@@ -37,14 +39,25 @@ public function index(array $params): LengthAwarePaginator
|
||||
$dateTo = $params['date_to'] ?? null;
|
||||
$sortBy = $params['sort_by'] ?? 'registration_date';
|
||||
$sortOrder = $params['sort_order'] ?? 'desc';
|
||||
$withItems = filter_var($params['with_items'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$query = Quote::query()->where('tenant_id', $tenantId);
|
||||
|
||||
// items 포함 (수주 전환용)
|
||||
if ($withItems) {
|
||||
$query->with(['items', 'client:id,name,contact_person,phone']);
|
||||
}
|
||||
|
||||
// 검색어
|
||||
if ($q !== '') {
|
||||
$query->search($q);
|
||||
}
|
||||
|
||||
// 견적 유형 필터
|
||||
if ($quoteType) {
|
||||
$query->where('quote_type', $quoteType);
|
||||
}
|
||||
|
||||
// 상태 필터
|
||||
if ($status) {
|
||||
$query->where('status', $status);
|
||||
@@ -585,4 +598,95 @@ private function createRevision(Quote $quote, int $userId): QuoteRevision
|
||||
'previous_data' => $previousData,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 현장설명회에서 견적 Upsert (생성 또는 업데이트)
|
||||
*
|
||||
* 참석완료 상태일 때 견적을 자동 생성하거나 업데이트합니다.
|
||||
* - 견적이 없으면: 견적대기(pending) 상태로 신규 생성
|
||||
* - 견적이 있으면: 현장설명회 정보로 업데이트 (거래처, 현장 등)
|
||||
*/
|
||||
public function upsertFromSiteBriefing(SiteBriefing $siteBriefing): Quote
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
// 기존 견적 조회
|
||||
$existingQuote = Quote::where('tenant_id', $tenantId)
|
||||
->where('site_briefing_id', $siteBriefing->id)
|
||||
->first();
|
||||
|
||||
return DB::transaction(function () use ($siteBriefing, $tenantId, $userId, $existingQuote) {
|
||||
if ($existingQuote) {
|
||||
// 기존 견적 업데이트 (현장설명회 정보 동기화)
|
||||
$existingQuote->update([
|
||||
// 발주처 정보 동기화
|
||||
'client_id' => $siteBriefing->partner_id,
|
||||
'client_name' => $siteBriefing->partner?->name,
|
||||
// 현장 정보 동기화
|
||||
'site_id' => $siteBriefing->site_id,
|
||||
'site_name' => $siteBriefing->title,
|
||||
// 감사
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
return $existingQuote->refresh();
|
||||
}
|
||||
|
||||
// 신규 견적 생성
|
||||
$quoteNumber = $this->numberService->generate('SCREEN');
|
||||
|
||||
return Quote::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'quote_type' => Quote::TYPE_CONSTRUCTION,
|
||||
'site_briefing_id' => $siteBriefing->id,
|
||||
'quote_number' => $quoteNumber,
|
||||
'registration_date' => now()->toDateString(),
|
||||
// 발주처 정보 (현장설명회에서 복사)
|
||||
'client_id' => $siteBriefing->partner_id,
|
||||
'client_name' => $siteBriefing->partner?->name,
|
||||
// 현장 정보 (현장설명회에서 복사)
|
||||
'site_id' => $siteBriefing->site_id,
|
||||
'site_name' => $siteBriefing->title,
|
||||
// 제품 카테고리 없음 (pending 상태이므로)
|
||||
'product_category' => null,
|
||||
// 금액 정보 (빈 값)
|
||||
'material_cost' => 0,
|
||||
'labor_cost' => 0,
|
||||
'install_cost' => 0,
|
||||
'subtotal' => 0,
|
||||
'discount_rate' => 0,
|
||||
'discount_amount' => 0,
|
||||
'total_amount' => 0,
|
||||
// 상태 관리 (견적대기)
|
||||
'status' => Quote::STATUS_PENDING,
|
||||
'current_revision' => 0,
|
||||
'is_final' => false,
|
||||
// 비고 (현장설명회 정보 기록)
|
||||
'remarks' => "현장설명회 참석완료로 자동생성 (현설번호: {$siteBriefing->briefing_code})",
|
||||
// 감사
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use upsertFromSiteBriefing() instead
|
||||
*/
|
||||
public function createFromSiteBriefing(SiteBriefing $siteBriefing): ?Quote
|
||||
{
|
||||
return $this->upsertFromSiteBriefing($siteBriefing);
|
||||
}
|
||||
|
||||
/**
|
||||
* 현장설명회 ID로 연결된 견적 조회
|
||||
*/
|
||||
public function findBySiteBriefingId(int $siteBriefingId): ?Quote
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
return Quote::where('tenant_id', $tenantId)
|
||||
->where('site_briefing_id', $siteBriefingId)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user