'date', 'receipt_date' => 'date', 'completion_date' => 'date', 'finalized_at' => 'datetime', 'is_final' => 'boolean', 'calculation_inputs' => 'array', 'material_cost' => 'decimal:2', 'labor_cost' => 'decimal:2', 'install_cost' => 'decimal:2', 'subtotal' => 'decimal:2', 'discount_rate' => 'decimal:2', 'discount_amount' => 'decimal:2', 'total_amount' => 'decimal:2', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; /** * 제품 카테고리 상수 */ public const CATEGORY_SCREEN = 'SCREEN'; public const CATEGORY_STEEL = 'STEEL'; /** * 상태 상수 */ public const STATUS_DRAFT = 'draft'; public const STATUS_SENT = 'sent'; public const STATUS_APPROVED = 'approved'; public const STATUS_REJECTED = 'rejected'; public const STATUS_FINALIZED = 'finalized'; public const STATUS_CONVERTED = 'converted'; /** * 견적 품목들 */ public function items(): HasMany { return $this->hasMany(QuoteItem::class)->orderBy('sort_order'); } /** * 수정 이력들 */ public function revisions(): HasMany { return $this->hasMany(QuoteRevision::class)->orderByDesc('revision_number'); } /** * 거래처 */ public function client(): BelongsTo { return $this->belongsTo(Client::class); } /** * 품목 (통합 items 테이블) */ public function item(): BelongsTo { return $this->belongsTo(Item::class, 'item_id'); } /** * 전환된 수주 */ public function order(): BelongsTo { return $this->belongsTo(Order::class); } /** * 확정자 */ public function finalizer(): BelongsTo { return $this->belongsTo(User::class, 'finalized_by'); } /** * 생성자 */ public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } /** * 수정자 */ public function updater(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); } /** * 상태별 스코프 */ public function scopeDraft($query) { return $query->where('status', self::STATUS_DRAFT); } public function scopeSent($query) { return $query->where('status', self::STATUS_SENT); } public function scopeApproved($query) { return $query->where('status', self::STATUS_APPROVED); } public function scopeFinalized($query) { return $query->where('status', self::STATUS_FINALIZED); } public function scopeConverted($query) { return $query->where('status', self::STATUS_CONVERTED); } /** * 확정된 견적 스코프 */ public function scopeIsFinal($query) { return $query->where('is_final', true); } /** * 제품 카테고리별 스코프 */ public function scopeScreen($query) { return $query->where('product_category', self::CATEGORY_SCREEN); } public function scopeSteel($query) { return $query->where('product_category', self::CATEGORY_STEEL); } /** * 날짜 범위 스코프 */ public function scopeDateRange($query, ?string $from, ?string $to) { if ($from) { $query->where('registration_date', '>=', $from); } if ($to) { $query->where('registration_date', '<=', $to); } return $query; } /** * 검색 스코프 */ public function scopeSearch($query, ?string $keyword) { if (! $keyword) { return $query; } return $query->where(function ($q) use ($keyword) { $q->where('quote_number', 'like', "%{$keyword}%") ->orWhere('client_name', 'like', "%{$keyword}%") ->orWhere('manager', 'like', "%{$keyword}%") ->orWhere('site_name', 'like', "%{$keyword}%") ->orWhere('product_name', 'like', "%{$keyword}%"); }); } /** * 수정 가능 여부 확인 */ public function isEditable(): bool { return ! in_array($this->status, [self::STATUS_FINALIZED, self::STATUS_CONVERTED]); } /** * 삭제 가능 여부 확인 */ public function isDeletable(): bool { return ! in_array($this->status, [self::STATUS_FINALIZED, self::STATUS_CONVERTED]); } /** * 확정 가능 여부 확인 */ public function isFinalizable(): bool { return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_SENT, self::STATUS_APPROVED]) && ! $this->is_final; } /** * 수주 전환 가능 여부 확인 */ public function isConvertible(): bool { return $this->status === self::STATUS_FINALIZED && $this->is_final; } }