diff --git a/app/Models/Construction/HandoverReport.php b/app/Models/Construction/HandoverReport.php new file mode 100644 index 0000000..47f05dd --- /dev/null +++ b/app/Models/Construction/HandoverReport.php @@ -0,0 +1,254 @@ + 'integer', + 'contract_amount' => 'decimal:2', + 'contract_date' => 'date:Y-m-d', + 'contract_start_date' => 'date:Y-m-d', + 'contract_end_date' => 'date:Y-m-d', + 'completion_date' => 'date:Y-m-d', + 'has_secondary_piping' => 'boolean', + 'secondary_piping_amount' => 'decimal:2', + 'has_coating' => 'boolean', + 'coating_amount' => 'decimal:2', + 'external_equipment_cost' => 'array', + 'is_active' => 'boolean', + ]; + + protected $attributes = [ + 'is_active' => true, + 'status' => self::STATUS_PENDING, + 'total_sites' => 0, + 'contract_amount' => 0, + 'has_secondary_piping' => false, + 'secondary_piping_amount' => 0, + 'has_coating' => false, + 'coating_amount' => 0, + ]; + + // ========================================================================= + // 관계 정의 + // ========================================================================= + + /** + * 연결된 계약 + */ + public function contract(): BelongsTo + { + return $this->belongsTo(Contract::class, 'contract_id'); + } + + /** + * 계약담당자 + */ + public function contractManager(): BelongsTo + { + return $this->belongsTo(User::class, 'contract_manager_id'); + } + + /** + * 공사PM + */ + public function constructionPm(): BelongsTo + { + return $this->belongsTo(User::class, 'construction_pm_id'); + } + + /** + * 공사담당자 목록 + */ + public function managers(): HasMany + { + return $this->hasMany(HandoverReportManager::class, 'handover_report_id') + ->orderBy('sort_order'); + } + + /** + * 계약 ITEM 목록 + */ + public function items(): HasMany + { + return $this->hasMany(HandoverReportItem::class, 'handover_report_id') + ->orderBy('item_no'); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * 수정자 + */ + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } + + // ========================================================================= + // 스코프 + // ========================================================================= + + /** + * 상태별 필터 + */ + public function scopeStatus($query, string $status) + { + return $query->where('status', $status); + } + + /** + * 거래처별 필터 + */ + public function scopePartner($query, int $partnerId) + { + return $query->where('partner_id', $partnerId); + } + + /** + * 계약별 필터 + */ + public function scopeContract($query, int $contractId) + { + return $query->where('contract_id', $contractId); + } + + // ========================================================================= + // 헬퍼 메서드 + // ========================================================================= + + /** + * 상태 라벨 반환 + */ + public function getStatusLabelAttribute(): string + { + return match ($this->status) { + self::STATUS_PENDING => '인수인계대기', + self::STATUS_COMPLETED => '인수인계완료', + default => $this->status, + }; + } + + /** + * 진행중 여부 + */ + public function isPending(): bool + { + return $this->status === self::STATUS_PENDING; + } + + /** + * 완료 여부 + */ + public function isCompleted(): bool + { + return $this->status === self::STATUS_COMPLETED; + } + + /** + * 장비 외 실행금액 합계 + */ + public function getExternalEquipmentTotalAttribute(): float + { + if (! $this->external_equipment_cost) { + return 0; + } + + return ($this->external_equipment_cost['shipping_cost'] ?? 0) + + ($this->external_equipment_cost['high_altitude_work'] ?? 0) + + ($this->external_equipment_cost['public_expense'] ?? 0); + } +} diff --git a/app/Models/Construction/HandoverReportItem.php b/app/Models/Construction/HandoverReportItem.php new file mode 100644 index 0000000..cdcb5aa --- /dev/null +++ b/app/Models/Construction/HandoverReportItem.php @@ -0,0 +1,81 @@ + 'integer', + 'quantity' => 'integer', + ]; + + protected $attributes = [ + 'item_no' => 0, + 'quantity' => 0, + ]; + + // ========================================================================= + // 관계 정의 + // ========================================================================= + + /** + * 인수인계보고서 + */ + public function handoverReport(): BelongsTo + { + return $this->belongsTo(HandoverReport::class, 'handover_report_id'); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * 수정자 + */ + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } +} diff --git a/app/Models/Construction/HandoverReportManager.php b/app/Models/Construction/HandoverReportManager.php new file mode 100644 index 0000000..42ae25c --- /dev/null +++ b/app/Models/Construction/HandoverReportManager.php @@ -0,0 +1,77 @@ + 'integer', + ]; + + protected $attributes = [ + 'sort_order' => 0, + ]; + + // ========================================================================= + // 관계 정의 + // ========================================================================= + + /** + * 인수인계보고서 + */ + public function handoverReport(): BelongsTo + { + return $this->belongsTo(HandoverReport::class, 'handover_report_id'); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * 수정자 + */ + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } +} diff --git a/app/Models/Construction/StructureReview.php b/app/Models/Construction/StructureReview.php index 7987e4b..21efe2c 100644 --- a/app/Models/Construction/StructureReview.php +++ b/app/Models/Construction/StructureReview.php @@ -167,4 +167,4 @@ public function isCompleted(): bool { return $this->status === self::STATUS_COMPLETED; } -} \ No newline at end of file +} diff --git a/app/Models/FcmSendLog.php b/app/Models/FcmSendLog.php index 4b63695..8302615 100644 --- a/app/Models/FcmSendLog.php +++ b/app/Models/FcmSendLog.php @@ -131,4 +131,4 @@ public function markAsFailed(string $errorMessage): void 'completed_at' => now(), ]); } -} \ No newline at end of file +} diff --git a/app/Models/Orders/Order.php b/app/Models/Orders/Order.php index 5948233..39a0d61 100644 --- a/app/Models/Orders/Order.php +++ b/app/Models/Orders/Order.php @@ -2,7 +2,6 @@ namespace App\Models\Orders; -use App\Models\Clients\Client; use App\Models\Items\Item; use App\Models\Quote\Quote; use App\Traits\BelongsToTenant; diff --git a/app/Models/Tenants/Position.php b/app/Models/Tenants/Position.php index a4f01ee..b9bdbd9 100644 --- a/app/Models/Tenants/Position.php +++ b/app/Models/Tenants/Position.php @@ -74,4 +74,4 @@ public function scopeOrdered($query) { return $query->orderBy('sort_order'); } -} \ No newline at end of file +} diff --git a/app/Models/Tenants/Salary.php b/app/Models/Tenants/Salary.php index 2114ec2..4352ec5 100644 --- a/app/Models/Tenants/Salary.php +++ b/app/Models/Tenants/Salary.php @@ -173,4 +173,4 @@ public function getPeriodLabel(): string { return sprintf('%d년 %d월', $this->year, $this->month); } -} \ No newline at end of file +} diff --git a/app/Models/Tenants/SiteBriefing.php b/app/Models/Tenants/SiteBriefing.php new file mode 100644 index 0000000..187cc3e --- /dev/null +++ b/app/Models/Tenants/SiteBriefing.php @@ -0,0 +1,304 @@ + 'date:Y-m-d', + 'bid_date' => 'date:Y-m-d', + 'construction_start_date' => 'date:Y-m-d', + 'construction_end_date' => 'date:Y-m-d', + 'attendees' => 'array', + 'attendee_count' => 'integer', + 'site_count' => 'integer', + ]; + + protected $attributes = [ + 'briefing_type' => self::TYPE_OFFLINE, + 'status' => self::STATUS_SCHEDULED, + 'bid_status' => self::BID_STATUS_PENDING, + 'attendance_status' => self::ATTENDANCE_SCHEDULED, + 'vat_type' => self::VAT_EXCLUDED, + 'attendee_count' => 0, + 'site_count' => 0, + ]; + + // ========================================================================= + // 관계 정의 + // ========================================================================= + + /** + * 거래처 + */ + public function partner(): BelongsTo + { + return $this->belongsTo(Client::class, 'partner_id'); + } + + /** + * 현장 + */ + public function site(): BelongsTo + { + return $this->belongsTo(Site::class); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * 수정자 + */ + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } + + /** + * 견적들 (현장설명회에서 자동생성된 견적) + */ + public function quotes(): HasMany + { + return $this->hasMany(Quote::class); + } + + // ========================================================================= + // Accessors + // ========================================================================= + + /** + * 거래처명 (partner.name) + */ + public function getPartnerNameAttribute(): ?string + { + return $this->partner?->name; + } + + /** + * 현장명 (site.name) + */ + public function getSiteNameAttribute(): ?string + { + return $this->site?->name; + } + + /** + * API 응답에 자동 추가할 속성 + */ + protected $appends = ['partner_name', 'site_name']; + + // ========================================================================= + // 스코프 + // ========================================================================= + + /** + * 상태별 필터 + */ + public function scopeStatus($query, string $status) + { + return $query->where('status', $status); + } + + /** + * 입찰상태별 필터 + */ + public function scopeBidStatus($query, string $bidStatus) + { + return $query->where('bid_status', $bidStatus); + } + + /** + * 날짜 범위 필터 + */ + public function scopeDateRange($query, ?string $startDate, ?string $endDate) + { + if ($startDate) { + $query->where('briefing_date', '>=', $startDate); + } + if ($endDate) { + $query->where('briefing_date', '<=', $endDate); + } + + return $query; + } + + // ========================================================================= + // 헬퍼 메서드 + // ========================================================================= + + /** + * 현설번호 생성 + * 형식: SB-YYYYMM-XXXX (예: SB-202601-0001) + */ + public static function generateBriefingCode(int $tenantId): string + { + $prefix = 'SB'; + $yearMonth = now()->format('Ym'); + + // 해당 월의 마지막 번호 조회 + $lastCode = self::withoutGlobalScopes() + ->where('tenant_id', $tenantId) + ->where('briefing_code', 'like', "{$prefix}-{$yearMonth}-%") + ->orderBy('briefing_code', 'desc') + ->value('briefing_code'); + + if ($lastCode) { + // 기존 번호에서 시퀀스 추출 + $lastSequence = (int) substr($lastCode, -4); + $newSequence = $lastSequence + 1; + } else { + $newSequence = 1; + } + + return sprintf('%s-%s-%04d', $prefix, $yearMonth, $newSequence); + } +}