feat: [approval] 기안함/완료함/대기함에 재상신 구분 열 추가

- resubmit_count 필드로 재상신 횟수 추적
- 반려 후 재상신 시 카운트 증가
- 보라색 뱃지로 재상신/재상신(N차) 표시
This commit is contained in:
김보곤
2026-03-05 13:06:30 +09:00
parent 61e77346de
commit 5fd69830ca
5 changed files with 34 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ class Approval extends Model
'completed_at' => 'datetime',
'drafter_read_at' => 'datetime',
'current_step' => 'integer',
'resubmit_count' => 'integer',
'is_urgent' => 'boolean',
];
@@ -41,6 +42,7 @@ class Approval extends Model
'completed_at',
'drafter_read_at',
'current_step',
'resubmit_count',
'attachments',
'recall_reason',
'parent_doc_id',
@@ -52,6 +54,7 @@ class Approval extends Model
protected $attributes = [
'status' => 'draft',
'current_step' => 0,
'resubmit_count' => 0,
'is_urgent' => false,
];

View File

@@ -243,8 +243,9 @@ public function submit(int $id): Approval
throw new \InvalidArgumentException('결재선을 설정해주세요.');
}
// 반려 후 재상신이면 모든 step 초기화
if ($approval->status === Approval::STATUS_REJECTED) {
// 반려 후 재상신이면 모든 step 초기화 + 재상신 카운트 증가
$isResubmit = $approval->status === Approval::STATUS_REJECTED;
if ($isResubmit) {
$approval->steps()->update([
'status' => ApprovalStep::STATUS_PENDING,
'comment' => null,
@@ -256,6 +257,7 @@ public function submit(int $id): Approval
'status' => Approval::STATUS_PENDING,
'drafted_at' => now(),
'current_step' => 1,
'resubmit_count' => $isResubmit ? $approval->resubmit_count + 1 : $approval->resubmit_count,
'updated_by' => auth()->id(),
]);

View File

@@ -82,6 +82,13 @@ function renderTable(items, pagination) {
return map[status] || status;
};
const resubmitBadge = (item) => {
const count = item.resubmit_count || 0;
if (count === 0) return '<span class="text-xs text-gray-400">-</span>';
const label = count === 1 ? '재상신' : `재상신(${count}차)`;
return `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-700">${label}</span>`;
};
const confirmBadge = (item) => {
const isMyDraft = item.drafter_id === currentUserId;
if (!isMyDraft) return '<span class="text-xs text-gray-400">-</span>';
@@ -99,6 +106,7 @@ function renderTable(items, pagination) {
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">제목</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">기안자</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">상태</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">구분</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">확인</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">완료일</th>
</tr>
@@ -115,6 +123,7 @@ function renderTable(items, pagination) {
<td class="px-4 py-3 text-sm text-gray-800 font-medium">${isUnread ? '<span class="font-bold">' + (item.title || '-') + '</span>' : (item.title || '-')}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.drafter?.name || '-'}</td>
<td class="px-4 py-3 text-center">${statusBadge(item.status)}</td>
<td class="px-4 py-3 text-center">${resubmitBadge(item)}</td>
<td class="px-4 py-3 text-center">${confirmBadge(item)}</td>
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${completedAt}</td>
</tr>`;

View File

@@ -439,6 +439,13 @@ function renderTable(items, pagination) {
return map[status] || status;
};
const resubmitBadge = (item) => {
const count = item.resubmit_count || 0;
if (count === 0) return '<span class="text-xs text-gray-400">-</span>';
const label = count === 1 ? '재상신' : `재상신(${count}차)`;
return `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-700">${label}</span>`;
};
let html = `<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
@@ -447,6 +454,7 @@ function renderTable(items, pagination) {
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">제목</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">양식</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">상태</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">구분</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">긴급</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">작성일</th>
</tr>
@@ -465,6 +473,7 @@ function renderTable(items, pagination) {
<td class="px-4 py-3 text-sm text-gray-800 font-medium">${item.title || '-'}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.form?.name || '-'}</td>
<td class="px-4 py-3 text-center">${statusBadge(item.status)}</td>
<td class="px-4 py-3 text-center">${resubmitBadge(item)}</td>
<td class="px-4 py-3 text-center">${urgent}</td>
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${createdAt}</td>
</tr>`;

View File

@@ -71,12 +71,20 @@ function renderTable(items, pagination) {
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">제목</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">기안자</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">양식</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">구분</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">긴급</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">기안일</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">`;
const resubmitBadge = (item) => {
const count = item.resubmit_count || 0;
if (count === 0) return '<span class="text-xs text-gray-400">-</span>';
const label = count === 1 ? '재상신' : `재상신(${count}차)`;
return `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-700">${label}</span>`;
};
items.forEach(item => {
const draftedAt = item.drafted_at ? new Date(item.drafted_at).toLocaleDateString('ko-KR') : '-';
const urgent = item.is_urgent ? '<span class="text-red-500 font-bold text-xs">긴급</span>' : '';
@@ -86,6 +94,7 @@ function renderTable(items, pagination) {
<td class="px-4 py-3 text-sm text-gray-800 font-medium">${item.title || '-'}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.drafter?.name || '-'}</td>
<td class="px-4 py-3 text-sm text-gray-600">${item.form?.name || '-'}</td>
<td class="px-4 py-3 text-center">${resubmitBadge(item)}</td>
<td class="px-4 py-3 text-center">${urgent}</td>
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${draftedAt}</td>
</tr>`;