feat: [approval] 기안함/완료함/대기함에 재상신 구분 열 추가
- resubmit_count 필드로 재상신 횟수 추적 - 반려 후 재상신 시 카운트 증가 - 보라색 뱃지로 재상신/재상신(N차) 표시
This commit is contained in:
@@ -22,6 +22,7 @@ class Approval extends Model
|
|||||||
'completed_at' => 'datetime',
|
'completed_at' => 'datetime',
|
||||||
'drafter_read_at' => 'datetime',
|
'drafter_read_at' => 'datetime',
|
||||||
'current_step' => 'integer',
|
'current_step' => 'integer',
|
||||||
|
'resubmit_count' => 'integer',
|
||||||
'is_urgent' => 'boolean',
|
'is_urgent' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ class Approval extends Model
|
|||||||
'completed_at',
|
'completed_at',
|
||||||
'drafter_read_at',
|
'drafter_read_at',
|
||||||
'current_step',
|
'current_step',
|
||||||
|
'resubmit_count',
|
||||||
'attachments',
|
'attachments',
|
||||||
'recall_reason',
|
'recall_reason',
|
||||||
'parent_doc_id',
|
'parent_doc_id',
|
||||||
@@ -52,6 +54,7 @@ class Approval extends Model
|
|||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'status' => 'draft',
|
'status' => 'draft',
|
||||||
'current_step' => 0,
|
'current_step' => 0,
|
||||||
|
'resubmit_count' => 0,
|
||||||
'is_urgent' => false,
|
'is_urgent' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -243,8 +243,9 @@ public function submit(int $id): Approval
|
|||||||
throw new \InvalidArgumentException('결재선을 설정해주세요.');
|
throw new \InvalidArgumentException('결재선을 설정해주세요.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 반려 후 재상신이면 모든 step 초기화
|
// 반려 후 재상신이면 모든 step 초기화 + 재상신 카운트 증가
|
||||||
if ($approval->status === Approval::STATUS_REJECTED) {
|
$isResubmit = $approval->status === Approval::STATUS_REJECTED;
|
||||||
|
if ($isResubmit) {
|
||||||
$approval->steps()->update([
|
$approval->steps()->update([
|
||||||
'status' => ApprovalStep::STATUS_PENDING,
|
'status' => ApprovalStep::STATUS_PENDING,
|
||||||
'comment' => null,
|
'comment' => null,
|
||||||
@@ -256,6 +257,7 @@ public function submit(int $id): Approval
|
|||||||
'status' => Approval::STATUS_PENDING,
|
'status' => Approval::STATUS_PENDING,
|
||||||
'drafted_at' => now(),
|
'drafted_at' => now(),
|
||||||
'current_step' => 1,
|
'current_step' => 1,
|
||||||
|
'resubmit_count' => $isResubmit ? $approval->resubmit_count + 1 : $approval->resubmit_count,
|
||||||
'updated_by' => auth()->id(),
|
'updated_by' => auth()->id(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ function renderTable(items, pagination) {
|
|||||||
return map[status] || status;
|
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 confirmBadge = (item) => {
|
||||||
const isMyDraft = item.drafter_id === currentUserId;
|
const isMyDraft = item.drafter_id === currentUserId;
|
||||||
if (!isMyDraft) return '<span class="text-xs text-gray-400">-</span>';
|
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-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-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>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">완료일</th>
|
||||||
</tr>
|
</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-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-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">${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-center">${confirmBadge(item)}</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${completedAt}</td>
|
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${completedAt}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
@@ -439,6 +439,13 @@ function renderTable(items, pagination) {
|
|||||||
return map[status] || status;
|
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">
|
let html = `<div class="overflow-x-auto">
|
||||||
<table class="min-w-full divide-y divide-gray-200">
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
<thead class="bg-gray-50">
|
<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-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-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>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">작성일</th>
|
||||||
</tr>
|
</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-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-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">${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-center">${urgent}</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${createdAt}</td>
|
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${createdAt}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
@@ -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-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-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>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">기안일</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">`;
|
<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 => {
|
items.forEach(item => {
|
||||||
const draftedAt = item.drafted_at ? new Date(item.drafted_at).toLocaleDateString('ko-KR') : '-';
|
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>' : '';
|
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-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.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-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-center">${urgent}</td>
|
||||||
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${draftedAt}</td>
|
<td class="px-4 py-3 text-sm text-gray-500 whitespace-nowrap">${draftedAt}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user