feat: [결재] 양식 마이그레이션 12종 + 반려이력/재상신
- 재직/경력/위촉증명서, 사직서, 사용인감계, 위임장 - 이사회의사록, 견적서, 공문서, 연차사용촉진 1차/2차 - 지출결의서 body_template 고도화 - rejection_history, resubmit_count, drafter_read_at 컬럼 - Document-Approval 브릿지 연동 (linkable) - 수신함 날짜 범위 필터 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ public function rules(): array
|
||||
'sort_dir' => 'nullable|string|in:asc,desc',
|
||||
'per_page' => 'nullable|integer|min:1',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,8 @@ class Approval extends Model
|
||||
'completed_at',
|
||||
'current_step',
|
||||
'attachments',
|
||||
'linkable_type',
|
||||
'linkable_id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
@@ -135,6 +138,14 @@ public function referenceSteps(): HasMany
|
||||
->orderBy('step_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* 연결 대상 (Document 등)
|
||||
*/
|
||||
public function linkable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Documents\Document;
|
||||
use App\Models\Tenants\Approval;
|
||||
use App\Models\Tenants\ApprovalForm;
|
||||
use App\Models\Tenants\ApprovalLine;
|
||||
@@ -446,6 +447,14 @@ public function inbox(array $params): LengthAwarePaginator
|
||||
}
|
||||
}
|
||||
|
||||
// 날짜 범위 필터
|
||||
if (! empty($params['start_date'])) {
|
||||
$query->whereDate('created_at', '>=', $params['start_date']);
|
||||
}
|
||||
if (! empty($params['end_date'])) {
|
||||
$query->whereDate('created_at', '<=', $params['end_date']);
|
||||
}
|
||||
|
||||
// 정렬
|
||||
$sortBy = $params['sort_by'] ?? 'created_at';
|
||||
$sortDir = $params['sort_dir'] ?? 'desc';
|
||||
@@ -559,7 +568,7 @@ public function show(int $id): Approval
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
return Approval::query()
|
||||
$approval = Approval::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with([
|
||||
'form:id,name,code,category,template',
|
||||
@@ -571,6 +580,19 @@ public function show(int $id): Approval
|
||||
'steps.approver.tenantProfile.department:id,name',
|
||||
])
|
||||
->findOrFail($id);
|
||||
|
||||
// Document 브릿지: 연결된 문서 데이터 로딩
|
||||
if ($approval->linkable_type === Document::class) {
|
||||
$approval->load([
|
||||
'linkable.template',
|
||||
'linkable.template.approvalLines',
|
||||
'linkable.data',
|
||||
'linkable.approvals.user:id,name',
|
||||
'linkable.attachments',
|
||||
]);
|
||||
}
|
||||
|
||||
return $approval;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -834,6 +856,9 @@ public function approve(int $id, ?string $comment = null): Approval
|
||||
$approval->updated_by = $userId;
|
||||
$approval->save();
|
||||
|
||||
// Document 브릿지 동기화
|
||||
$this->syncToLinkedDocument($approval);
|
||||
|
||||
return $approval->fresh([
|
||||
'form:id,name,code,category',
|
||||
'drafter:id,name',
|
||||
@@ -887,6 +912,9 @@ public function reject(int $id, string $comment): Approval
|
||||
$approval->updated_by = $userId;
|
||||
$approval->save();
|
||||
|
||||
// Document 브릿지 동기화
|
||||
$this->syncToLinkedDocument($approval);
|
||||
|
||||
return $approval->fresh([
|
||||
'form:id,name,code,category',
|
||||
'drafter:id,name',
|
||||
@@ -926,6 +954,9 @@ public function cancel(int $id): Approval
|
||||
$approval->updated_by = $userId;
|
||||
$approval->save();
|
||||
|
||||
// Document 브릿지 동기화 (steps 삭제 전에 실행)
|
||||
$this->syncToLinkedDocument($approval);
|
||||
|
||||
// 결재 단계들 삭제
|
||||
$approval->steps()->delete();
|
||||
|
||||
@@ -936,6 +967,57 @@ public function cancel(int $id): Approval
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Approval → Document 브릿지 동기화
|
||||
* 결재 승인/반려/회수 시 연결된 Document의 상태와 결재란을 동기화
|
||||
*/
|
||||
private function syncToLinkedDocument(Approval $approval): void
|
||||
{
|
||||
if ($approval->linkable_type !== Document::class) {
|
||||
return;
|
||||
}
|
||||
|
||||
$document = Document::find($approval->linkable_id);
|
||||
if (! $document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// approval_steps → document_approvals 동기화 (승인자 이름/시각 반영)
|
||||
foreach ($approval->steps as $step) {
|
||||
if ($step->status === ApprovalStep::STATUS_PENDING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$docApproval = $document->approvals()
|
||||
->where('step', $step->step_order)
|
||||
->first();
|
||||
|
||||
if ($docApproval) {
|
||||
$docApproval->update([
|
||||
'status' => strtoupper($step->status),
|
||||
'acted_at' => $step->acted_at,
|
||||
'comment' => $step->comment,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Document 전체 상태 동기화
|
||||
$documentStatus = match ($approval->status) {
|
||||
Approval::STATUS_APPROVED => Document::STATUS_APPROVED,
|
||||
Approval::STATUS_REJECTED => Document::STATUS_REJECTED,
|
||||
Approval::STATUS_CANCELLED => Document::STATUS_CANCELLED,
|
||||
default => Document::STATUS_PENDING,
|
||||
};
|
||||
|
||||
$document->update([
|
||||
'status' => $documentStatus,
|
||||
'completed_at' => in_array($approval->status, [
|
||||
Approval::STATUS_APPROVED,
|
||||
Approval::STATUS_REJECTED,
|
||||
]) ? now() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 참조 열람 처리
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 결재(approvals) 테이블에 연결 대상(linkable) 컬럼 추가
|
||||
* - Document 시스템과 Approval 시스템을 브릿지하기 위한 다형성 관계
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('approvals', function (Blueprint $table) {
|
||||
$table->string('linkable_type')->nullable()->after('attachments')->comment('연결 대상 모델 (예: App\\Models\\Documents\\Document)');
|
||||
$table->unsignedBigInteger('linkable_id')->nullable()->after('linkable_type')->comment('연결 대상 ID');
|
||||
$table->index(['linkable_type', 'linkable_id'], 'idx_linkable');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('approvals', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_linkable');
|
||||
$table->dropColumn(['linkable_type', 'linkable_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$tenants = DB::table('tenants')->whereNull('deleted_at')->pluck('id');
|
||||
|
||||
foreach ($tenants as $tenantId) {
|
||||
$exists = DB::table('approval_forms')
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', 'resignation')
|
||||
->exists();
|
||||
|
||||
if (! $exists) {
|
||||
DB::table('approval_forms')->insert([
|
||||
'tenant_id' => $tenantId,
|
||||
'name' => '사직서',
|
||||
'code' => 'resignation',
|
||||
'category' => 'certificate',
|
||||
'template' => '[]',
|
||||
'body_template' => '',
|
||||
'is_active' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('approval_forms')->where('code', 'resignation')->delete();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user