diff --git a/app/Models/Documents/Document.php b/app/Models/Documents/Document.php new file mode 100644 index 0000000..8823054 --- /dev/null +++ b/app/Models/Documents/Document.php @@ -0,0 +1,246 @@ + 'datetime', + 'completed_at' => 'datetime', + ]; + + protected $attributes = [ + 'status' => self::STATUS_DRAFT, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + /** + * 템플릿 + */ + public function template(): BelongsTo + { + return $this->belongsTo(\App\Models\DocumentTemplate::class, 'template_id'); + } + + /** + * 결재 목록 + */ + public function approvals(): HasMany + { + return $this->hasMany(DocumentApproval::class)->orderBy('step'); + } + + /** + * 문서 데이터 + */ + public function data(): HasMany + { + return $this->hasMany(DocumentData::class); + } + + /** + * 첨부파일 + */ + public function attachments(): HasMany + { + return $this->hasMany(DocumentAttachment::class); + } + + /** + * 연결된 엔티티 (다형성) + */ + public function linkable(): MorphTo + { + return $this->morphTo(); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * 수정자 + */ + public function updater(): BelongsTo + { + return $this->belongsTo(User::class, 'updated_by'); + } + + // ========================================================================= + // Scopes + // ========================================================================= + + /** + * 상태로 필터링 + */ + public function scopeStatus($query, string $status) + { + return $query->where('status', $status); + } + + /** + * 임시저장 문서 + */ + public function scopeDraft($query) + { + return $query->where('status', self::STATUS_DRAFT); + } + + /** + * 결재 대기 문서 + */ + public function scopePending($query) + { + return $query->where('status', self::STATUS_PENDING); + } + + /** + * 승인된 문서 + */ + public function scopeApproved($query) + { + return $query->where('status', self::STATUS_APPROVED); + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + /** + * 편집 가능 여부 + */ + public function canEdit(): bool + { + return $this->status === self::STATUS_DRAFT + || $this->status === self::STATUS_REJECTED; + } + + /** + * 결재 요청 가능 여부 + */ + public function canSubmit(): bool + { + return $this->status === self::STATUS_DRAFT + || $this->status === self::STATUS_REJECTED; + } + + /** + * 결재 처리 가능 여부 + */ + public function canApprove(): bool + { + return $this->status === self::STATUS_PENDING; + } + + /** + * 취소 가능 여부 + */ + public function canCancel(): bool + { + return in_array($this->status, [ + self::STATUS_DRAFT, + self::STATUS_PENDING, + ]); + } + + /** + * 현재 결재 단계 가져오기 + */ + public function getCurrentApprovalStep(): ?DocumentApproval + { + return $this->approvals() + ->where('status', DocumentApproval::STATUS_PENDING) + ->orderBy('step') + ->first(); + } + + /** + * 특정 사용자의 결재 차례 확인 + */ + public function isUserTurn(int $userId): bool + { + $currentStep = $this->getCurrentApprovalStep(); + + return $currentStep && $currentStep->user_id === $userId; + } +} diff --git a/app/Models/Documents/DocumentApproval.php b/app/Models/Documents/DocumentApproval.php new file mode 100644 index 0000000..d3b42bc --- /dev/null +++ b/app/Models/Documents/DocumentApproval.php @@ -0,0 +1,180 @@ + 'integer', + 'acted_at' => 'datetime', + ]; + + protected $attributes = [ + 'step' => 1, + 'status' => self::STATUS_PENDING, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + /** + * 문서 + */ + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + /** + * 결재자 + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + // ========================================================================= + // Scopes + // ========================================================================= + + /** + * 대기 중인 결재 + */ + public function scopePending($query) + { + return $query->where('status', self::STATUS_PENDING); + } + + /** + * 승인된 결재 + */ + public function scopeApproved($query) + { + return $query->where('status', self::STATUS_APPROVED); + } + + /** + * 반려된 결재 + */ + public function scopeRejected($query) + { + return $query->where('status', self::STATUS_REJECTED); + } + + /** + * 특정 사용자의 결재 + */ + public function scopeForUser($query, int $userId) + { + return $query->where('user_id', $userId); + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + /** + * 대기 상태 여부 + */ + public function isPending(): bool + { + return $this->status === self::STATUS_PENDING; + } + + /** + * 승인 상태 여부 + */ + public function isApproved(): bool + { + return $this->status === self::STATUS_APPROVED; + } + + /** + * 반려 상태 여부 + */ + public function isRejected(): bool + { + return $this->status === self::STATUS_REJECTED; + } + + /** + * 결재 처리 완료 여부 + */ + public function isProcessed(): bool + { + return ! $this->isPending(); + } +} diff --git a/app/Models/Documents/DocumentAttachment.php b/app/Models/Documents/DocumentAttachment.php new file mode 100644 index 0000000..b05a2bf --- /dev/null +++ b/app/Models/Documents/DocumentAttachment.php @@ -0,0 +1,144 @@ + self::TYPE_GENERAL, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + /** + * 문서 + */ + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + /** + * 파일 + */ + public function file(): BelongsTo + { + return $this->belongsTo(File::class); + } + + /** + * 생성자 + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + // ========================================================================= + // Scopes + // ========================================================================= + + /** + * 특정 유형의 첨부파일 + */ + public function scopeOfType($query, string $type) + { + return $query->where('attachment_type', $type); + } + + /** + * 일반 첨부파일 + */ + public function scopeGeneral($query) + { + return $query->where('attachment_type', self::TYPE_GENERAL); + } + + /** + * 서명 첨부파일 + */ + public function scopeSignatures($query) + { + return $query->where('attachment_type', self::TYPE_SIGNATURE); + } + + /** + * 이미지 첨부파일 + */ + public function scopeImages($query) + { + return $query->where('attachment_type', self::TYPE_IMAGE); + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + /** + * 서명 첨부파일 여부 + */ + public function isSignature(): bool + { + return $this->attachment_type === self::TYPE_SIGNATURE; + } + + /** + * 이미지 첨부파일 여부 + */ + public function isImage(): bool + { + return $this->attachment_type === self::TYPE_IMAGE; + } +} diff --git a/app/Models/Documents/DocumentData.php b/app/Models/Documents/DocumentData.php new file mode 100644 index 0000000..a9418dc --- /dev/null +++ b/app/Models/Documents/DocumentData.php @@ -0,0 +1,105 @@ + 'integer', + 'column_id' => 'integer', + 'row_index' => 'integer', + ]; + + protected $attributes = [ + 'row_index' => 0, + ]; + + // ========================================================================= + // Relationships + // ========================================================================= + + /** + * 문서 + */ + public function document(): BelongsTo + { + return $this->belongsTo(Document::class); + } + + // ========================================================================= + // Scopes + // ========================================================================= + + /** + * 특정 섹션의 데이터 + */ + public function scopeForSection($query, int $sectionId) + { + return $query->where('section_id', $sectionId); + } + + /** + * 특정 필드 키의 데이터 + */ + public function scopeForField($query, string $fieldKey) + { + return $query->where('field_key', $fieldKey); + } + + /** + * 특정 행의 데이터 + */ + public function scopeForRow($query, int $rowIndex) + { + return $query->where('row_index', $rowIndex); + } + + // ========================================================================= + // Helper Methods + // ========================================================================= + + /** + * 값을 특정 타입으로 캐스팅 + */ + public function getTypedValue(string $type = 'string'): mixed + { + return match ($type) { + 'integer', 'int' => (int) $this->field_value, + 'float', 'double' => (float) $this->field_value, + 'boolean', 'bool' => filter_var($this->field_value, FILTER_VALIDATE_BOOLEAN), + 'array', 'json' => json_decode($this->field_value, true), + default => $this->field_value, + }; + } +}