
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<div class="form-card">
<h1>출장복명서</h1>
<SanctnViewList v-if="bsrpRport.sanctnList.length > 0" :sanctns="bsrpRport.sanctnList" />
<div class="tbl-wrap row g-3 needs-validation detail">
<table class="tbl">
<tbody>
<tr>
<th>복명내용</th>
<td>
<ViewerComponent :content="bsrpRport.cn" />
</td>
</tr>
<tr>
<th>여비계산</th>
<td>
<template v-if="bsrpRport.bsrpTrvctList.length > 0">
<div v-for="(item, idx) of bsrpRport.bsrpTrvctList" :key="idx">
<p>
<span>{{ item.seNm + '결재' }} ({{ getPaymentEntityText(item.se, item.setleMbyId) }})</span>
<span>{{ item.tyNm }}</span>
<span>{{ formatAmount(item.amount) }}</span>
<button type="button" v-if="item.ordr" @click="handleDownload(item.ordr)">{{ getFileNm(item.ordr) }}</button>
<span v-else>영수증 없음</span>
</p>
</div>
</template>
</td>
</tr>
<tr>
<th>복명 신청일</th>
<td>{{ bsrpRport.rgsde }}</td>
</tr>
<tr v-if="reportSanctnStatus === 'rejected'">
<th>복명 반려사유</th>
<td>{{ getRejectionReason(bsrpRport.sanctnList) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="buttons">
<template v-if="isReportApprover">
<button type="button" class="btn sm primary" @click="handleApproval('A')">승인</button>
<button type="button" class="btn sm btn-red" @click="showReportPopup = true">반려</button>
</template>
<template v-else-if="isApplicant">
<template v-if="reportSanctnStatus === 'waiting'">
<button type="button" class="btn sm btn-red" @click="handleDeleteReport">복명서 취소</button>
<button type="button" class="btn sm secondary" @click="handleNavigation('bsrpRportInsert', pageId)">복명서 수정</button>
</template>
<template v-if="reportSanctnStatus === 'rejected'">
<button type="button" class="btn sm secondary" @click="handleNavigation('bsrpRportReapply', pageId)">복명서 재신청</button>
</template>
</template>
<template v-if="isCancelable">
<button type="button" class="btn sm secondary" @click="handleCancel">취소</button>
</template>
<button type="button" class="btn sm tertiary" @click="handleNavigation('list')">목록</button>
</div>
<ReturnPopup v-if="showReportPopup" @close="showReportPopup = false" @confirm="handleRejection" />
</template>
<script>
import ReturnPopup from '../Popup/ReturnPopup.vue';
import SanctnViewList from '../Sanctn/SanctnViewList.vue';
import ViewerComponent from '../editor/ViewerComponent.vue';
// API
import { findBsrpRportProc, deleteBsrpRportProc } from '../../../resources/api/bsrpRport';
import { updateConfmAtProc } from '../../../resources/api/sanctns';
import { fileDownloadProc } from '../../../resources/api/file';
export default {
name: 'BsrpRportView',
components: {
ReturnPopup,
SanctnViewList,
ViewerComponent,
},
props: {
pageId: {
type: String,
default: null,
},
pageMode: {
type: String,
default: null,
},
cards: {
type: Array,
default: () => [],
},
users: {
type: Array,
default: () => [],
},
},
data() {
return {
showReportPopup: false,
bsrpInfo: {
applcntId: null,
},
bsrpRport: {
cn: null,
rgsde: null,
sanctnList: [],
bsrpTrvctList: [],
fileList: [],
},
returnResn: null,
};
},
computed: {
// 결재 상태
reportSanctnStatus() {
const sanctnList = this.bsrpRport.sanctnList;
if (sanctnList.length === 0) return 'none';
// 하나라도 반려된 경우 > 반려
if (sanctnList.some(item => item.confmAt === 'R')) {
return 'rejected';
}
// 전부 승인된 경우 > 승인
if (sanctnList.every(item => item.confmAt === 'A')) {
return 'approved';
}
// 그 외 > 대기
return 'waiting';
},
// 작성자 여부
isApplicant() {
return this.bsrpRport.register === this.$store.state.userInfo.userId;
},
// 결재자 여부
isReportApprover() {
const sanctnList = this.bsrpRport.sanctnList;
const mySanctn = sanctnList.find(
item => item.confmerId == this.$store.state.userInfo.userId
);
return mySanctn && mySanctn.confmAt === 'W' && this.pageMode === 'sanctns';
},
// 취소 가능 여부
isCancelable() {
const sanctnList = this.bsrpRport.sanctnList;
const mySanctn = sanctnList.find(
item => item.confmerId == this.$store.state.userInfo.userId
);
return ((mySanctn && this.pageMode === 'sanctns') || this.isApplicant) && this.reportSanctnStatus === 'approved';
},
},
mounted() {
this.fetchData(); // 복명 정보 조회
},
methods: {
// 복명 정보 조회
async fetchData() {
try {
const response = await findBsrpRportProc(this.pageId);
const result = response.data.data;
this.bsrpRport = result;
} catch (error) {
const message = error.response?.data?.message || '데이터 조회에 실패했습니다.';
alert(message);
}
},
// 결재
async handleApproval(value) {
try {
const sanctnList = this.bsrpRport.sanctnList;
const sanctn = sanctnList.find(item => item.confmerId == this.$store.state.userInfo.userId);
if (!sanctn) {
alert('결재 권한이 없습니다.');
return;
}
const data = {
sanctnOrdr: sanctn.sanctnOrdr,
sanctnIem: sanctn.sanctnIem,
sanctnMbyId: this.pageId,
confmAt: value,
returnResn: this.returnResn,
};
await updateConfmAtProc(sanctn.sanctnId, data);
const message = value === 'A' ? '승인했습니다.' : '반려했습니다.';
alert(message);
this.fetchData();
} catch (error) {
const message = error.response?.data?.message || '승인 처리에 실패했습니다.';
alert(message);
}
},
// 복명 취소 (완전 삭제)
async handleDeleteReport() {
const isCheck = confirm("복명서를 취소하시겠습니까?");
if (!isCheck) return;
try {
await deleteBsrpRportProc(this.pageId);
alert("복명서가 취소되었습니다.");
this.fetchData();
} catch (error) {
const message = error.response?.data?.message || '복명서 취소에 실패했습니다.';
alert(message);
}
},
// 복명 취소 (결재 승인 취소)
async handleCancel() {
const isCheck = confirm("출장 복명을 취소하시겠습니까?");
if (!isCheck) return;
try {
await cancelVcatnProc(this.pageId);
alert("출장 복명이 취소되었습니다.");
this.handleNavigation('view', this.pageId);
} catch (error) {
this.handleError(error);
}
},
// 첨부 파일 다운로드 핸들러
async handleDownload(fileOrdr) {
const selectedFile = this.bsrpRport.fileList.find(file => file.ordr == fileOrdr);
if (!selectedFile) return;
let url = null;
let link = null;
try {
const response = await fileDownloadProc(selectedFile);
let filename = 'downloadFile.bin';
const filenameRegex = /file[Nn]ame[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(response.headers['content-disposition']);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
url = window.URL.createObjectURL(new Blob([response.data]));
link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
} catch (error) {
alert('파일 다운로드 중 오류가 발생했습니다.');
} finally {
setTimeout(() => {
if (url) {
window.URL.revokeObjectURL(url);
}
if (link && link.parentNode) {
document.body.removeChild(link);
}
}, 100);
}
},
// 반려 결재 핸들러
async handleRejection(reason) {
this.returnResn = reason;
await this.handleApproval('R');
this.showReportPopup = false;
},
// 페이지 이동 핸들러
handleNavigation(type, id) {
const routeMap = {
'list': { name: this.pageMode === 'sanctns' ? 'PendingApprovalListPage' : 'BsrpListPage' },
'view': { name: 'BsrpViewPage', query: { id } },
'bsrpInsert': { name: 'BsrpInsertPage', query: this.$isEmpty(id) ? {} : { id } },
'bsrpReapply': { name: 'BsrpInsertPage', query: { id, type: 'reapply' } },
'bsrpRportInsert': { name: 'BsrpRportInsertPage', query: this.$isEmpty(id) ? {} : { id } },
'bsrpRportReapply': { name: 'BsrpRportInsertPage', query: { id, type: 'reapply' } },
};
const route = routeMap[type];
if (route) {
this.$router.push(route);
} else {
alert("올바르지 않은 경로입니다.");
this.$router.push(routeMap['list']);
}
},
// 반려 사유 검색 유틸리티
getRejectionReason(sanctnList) {
const rejectedItem = sanctnList.find(item => item.confmAt === 'R');
return rejectedItem?.returnResn;
},
// 여비 주체명 검색 유틸리티
getPaymentEntityText(se, id) {
if (se === 'PERSONAL') {
const user = this.users.find(user => user.userId === id);
return user?.userNm;
} else {
const card = this.cards.find(card => card.cardId === id);
return card?.cardNm;
}
},
// 금액 표기 변경 유틸리티
formatAmount(amount) {
return new Intl.NumberFormat('ko-KR').format(amount) + '원';
},
// 파일명 검색 유틸리티
getFileNm(fileOrdr) {
const file = this.bsrpRport.fileList.find(file => file.ordr == fileOrdr);
return file?.fileNm;
},
},
};
</script>