
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
File name
Commit message
Commit date
<template>
<div class="card">
<div class="card-body">
<h2 class="card-title">출장 신청</h2>
<p class="require"><img :src="require" alt=""> 필수입력</p>
<div class="tbl-wrap">
<table class="tbl data">
<tbody>
<tr>
<th>출장구분 <span class="require"><img :src="require" alt=""></span></th>
<td>
<select class="form-select sm" style="width: 110px;" v-model="bsrpInfo.bsrpSe">
<option v-for="(item, idx) of cmmnCodes.bsrpCodeList" :key="idx" :value="item.code">
{{ item.codeNm }}
</option>
</select>
</td>
<th>일수</th>
<td>
<input type="text" class="form-control sm" v-model="totalDays" readonly />
</td>
</tr>
<tr>
<th>출장지 <span class="require"><img :src="require" alt=""></span></th>
<td>
<input type="text" class="form-control sm" v-model="bsrpInfo.bsrpPlace" />
</td>
<th>출장목적 <span class="require"><img :src="require" alt=""></span></th>
<td>
<input type="text" class="form-control sm" v-model="bsrpInfo.bsrpPurps" />
</td>
</tr>
<tr>
<th>출장기간 <span class="require"><img :src="require" alt=""></span></th>
<td colspan="3">
<div class="d-flex">
<div class="d-flex gap-1 mb-1">
<input type="date" class="form-control sm" v-model="bsrpInfo.bgnde" />
<input type="text" class="form-control sm" style="width: 100px;" placeholder="시" v-model="bsrpInfo.beginHour" />
<input type="text" class="form-control sm" style="width: 100px;" placeholder="분" v-model="bsrpInfo.beginMnt" />
</div>
<div class="d-flex gap-1">
<input type="date" class="form-control sm" v-model="bsrpInfo.endde" />
<input type="text" class="form-control sm" style="width: 100px;" placeholder="시" v-model="bsrpInfo.endHour" />
<input type="text" class="form-control sm" style="width: 100px;" placeholder="분" v-model="bsrpInfo.endMnt" />
</div>
</div>
</td>
</tr>
<tr>
<th>
동행자
</th>
<td>
<button type="button" title="추가" @click="isOpenNmprModal = true">
<PlusCircleFilled />
</button>
<HrPopup v-if="isOpenNmprModal" :lists="bsrpInfo.bsrpNmprList" @onSelected="fnAddNmpr" @close="isOpenNmprModal = false" />
<div class="approval-container">
<div v-for="(item, idx) of bsrpInfo.bsrpNmprList" :key="idx" class="d-flex addapproval">
<div class="d-flex align-items-center border-x">
<p>{{ item.triperNm }} {{ item.clsfNm }}</p>
<button type="button" @click="fnDelNmpr(idx)" @mousedown.stop>
<CloseOutlined />
</button>
</div>
</div>
</div>
</td>
<th>
승인자 <span class="require"><img :src="require" alt=""></span>
</th>
<td>
<button type="button" title="추가" @click="isOpenSanctnModal = true">
<PlusCircleFilled />
</button>
<HrPopup v-if="isOpenSanctnModal" :lists="bsrpCnsul.sanctnList" @onSelected="fnAddSanctn" @close="isOpenSanctnModal = false" />
<div class="approval-container">
<SanctnList v-model:lists="bsrpCnsul.sanctnList" @delSanctn="fnDelSanctn" />
</div>
</td>
</tr>
<tr>
<th>품의내용 <span class="require"><img :src="require" alt=""></span></th>
<td colspan="3" style="height: calc(100% - 550px);">
<EditorComponent v-model:contents="bsrpCnsul.cn" />
</td>
</tr>
<tr>
<th>
법인카드
<button type="button" title="추가" @click="isOpenCardModal = true">
<PlusCircleFilled />
</button>
</th>
<td>
<CorpCardPopup v-if="isOpenCardModal" :bsrpInfo="bsrpInfo" :lists="cprCardList" @close="isOpenCardModal = false" @onSelected="fnAddCard" />
<div class="approval-container">
<div v-for="(card, idx) in cprCardList" :key="idx" class="d-flex gap-2 addapproval mb-2">
<form class="d-flex align-items-center border-x">
<p>{{ card.cardNm }}</p>
<button type="button" @click="fnDelCard(idx)" class="delete-button">
<CloseOutlined />
</button>
</form>
</div>
</div>
</td>
<th>
법인차량
<button type="button" title="추가" @click="isOpenVhcleModal = true">
<PlusCircleFilled />
</button>
</th>
<td>
<CorpCarPopup v-if="isOpenVhcleModal" :bsrpInfo="bsrpInfo" :lists="cprVhcleList" @close="isOpenVhcleModal = false" @onSelected="fnAddVhcle" />
<div class="approval-container">
<div v-for="(vhcle, idx) in cprVhcleList" :key="idx" class="d-flex gap-2 addapproval mb-2">
<p>{{ vhcle.vhcleNm }}</p>
<select class="form-select" v-model="vhcle.drverId">
<option value="" disabled hidden>운전자 선택</option>
<option :value="userInfo.userId">{{ userInfo.userNm }}</option>
<option v-for="(item, idx) of bsrpInfo.bsrpNmprList" :key="idx" :value="item.userId">
{{ item.userNm }}
</option>
</select>
<button type="button" @click="fnDelVhcle(idx)" class="delete-button">
<CloseOutlined />
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="buttons">
<button type="button" class="btn sm sm primary" @click="fnSave">신청</button>
<button v-if="$isEmpty(pageId)" type="button" class="btn sm sm secondary" @click="fnMoveTo('list')">목록</button>
<button v-else type="button" class="btn sm sm secondary" @click="fnMoveTo('view', pageId)">취소</button>
</div>
</div>
</div>
</template>
<script>
import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue';
import HrPopup from '../../../component/Popup/HrPopup.vue';
import CorpCarPopup from '../../../component/Popup/CorpCarPopup.vue';
import CorpCardPopup from '../../../component/Popup/CorpCardPopup.vue';
import SanctnList from '../../../component/Sanctn/SanctnFormList.vue';
import EditorComponent from '../../../component/editor/EditorComponent.vue';
// API
import { saveBsrpProc, bsrpProc, updateBsrpProc } from '../../../../resources/api/bsrp';
export default {
components: {
PlusCircleFilled, CloseOutlined,
HrPopup, CorpCarPopup, CorpCardPopup,
SanctnList, EditorComponent,
},
data() {
return {
require: "/client/resources/img/require.png",
pageId: null,
userInfo: this.$store.state.userInfo,
cmmnCodes: {},
isOpenNmprModal: false,
isOpenSanctnModal: false,
isOpenCardModal: false,
isOpenVhcleModal: false,
// 출장정보
bsrpInfo: {
applcntId: null, // 신청자 아이디
bsrpSe: null, // 출장구분
bsrpPlace: null, // 출장지
bsrpPurps: null, // 출장목적
bgnde: null, // 시작일
beginHour: null, // 시작시
beginMnt: null, // 시작분
endde: null, // 종료일
endHour: null, // 종료시
endMnt: null, // 종료분
bsrpNmprList: [], // 출장인원 목록
},
// 출장품의
bsrpCnsul: {
bsrpId: null, // 출장 아이디
cn: null, // 품의 내용
confmAt: null, // 승인여부
rgsde: null, // 등록일
register: null, // 등록자
updde: null, // 수정일
updusr: null, // 수정자
sanctnList: [], // 결재 목록
},
cprCardList: [], // 법인카드 목록
cprVhcleList: [], // 법인차량 목록
totalDays: 0, // 일수
};
},
computed: {},
watch: {
cmmnCodes(newVal) {
if (Object.keys(newVal).length > 0) {
this.bsrpInfo.bsrpSe = this.cmmnCodes.bsrpCodeList[0].code;
}
},
'bsrpInfo.bgnde'(newVal, oldVal) {
if (newVal !== oldVal) {
this.calcDayCnt(); // 일수 계산
}
},
'bsrpInfo.endde'(newVal, oldVal) {
if (newVal !== oldVal) {
this.calcDayCnt(); // 일수 계산
}
},
},
async created() {
this.pageId = this.$route.query.id;
this.cmmnCodes = await this.$defaultCodes();
},
mounted() {
if (!this.$isEmpty(this.pageId)) {
this.findData();
}
},
methods: {
// 상세 조회
async findData() {
try {
const response = await bsrpProc(this.pageId);
const result = response.data.data;
this.bsrpInfo = result.bsrpInfo;
this.bsrpInfo.bgnde = this.bsrpInfo.bgnde.split(' ')[0];
this.bsrpInfo.endde = this.bsrpInfo.endde.split(' ')[0];
this.bsrpCnsul = result.bsrpCnsul;
this.cprCardList = result.cprCardList;
this.cprVhcleList = result.cprVhcleList;
} catch (error) {
console.error('데이터 조회 실패:', error);
alert(error.response.data.message);
this.fnMoveTo('list');
}
},
// 일수 계산
calcDayCnt() {
if (!this.bsrpInfo.bgnde || !this.bsrpInfo.endde) {
this.totalDays = 0;
return;
}
const startDate = new Date(this.bsrpInfo.bgnde);
const endDate = new Date(this.bsrpInfo.endde);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
this.totalDays = 0;
return;
}
const timeDiff = endDate.getTime() - startDate.getTime();
const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24)) + 1;
this.totalDays = Math.max(0, dayDiff);
},
// 동행자 추가
fnAddNmpr(user) {
const data = {
triperId: user.userId, // 출장자 아이디
deptId: user.deptId, // 부서 아이디
clsf: user.clsf, // 직급
triperNm: user.userNm, // 출장자 이름
deptNm: user.deptNm, // 부서 이름
clsfNm: user.clsfNm, // 직급 이름
};
this.bsrpInfo.bsrpNmprList.push(data);
this.isOpenNmprModal = false;
},
// 동행자 삭제
fnDelNmpr(idx) {
this.bsrpInfo.bsrpNmprList.splice(idx, 1);
},
// 승인자 추가
fnAddSanctn(user) {
const data = {
sanctnId: null, // 결재 아이디
confmerId: user.userId, // 승인자 아이디
clsf: user.clsf, // 직급
sanctnOrdr: this.bsrpCnsul.sanctnList.length + 1, // 결재 순서
sanctnIem: 'cnsul', // 결재 항목
sanctnMbyId: null, // 결재 주체 아이디
sanctnSe: this.cmmnCodes.sanctnCodeList[0].code, // 결재구분
confmerNm: user.userNm, // 승인자 이름
clsfNm: user.clsfNm, // 직급 이름
};
this.bsrpCnsul.sanctnList.push(data);
this.isOpenSanctnModal = false;
},
// 승인자 삭제
fnDelSanctn(idx) {
this.bsrpInfo.sanctnList.splice(idx, 1);
this.bsrpInfo.sanctnList.forEach((item, idx) => { item.sanctnOrdr = idx + 1; });
},
// 법인카드 추가
fnAddCard(item) {
this.cprCardList.push(item);
this.isOpenCardModal = false;
},
// 법인카드 삭제
fnDelCard(idx) {
this.cprCardList.splice(idx, 1);
},
// 법인차량 추가
fnAddVhcle(item) {
item.drverId = '';
this.cprVhcleList.push(item);
this.isOpenVhcleModal = false;
},
// 법인차량 삭제
fnDelVhcle(idx) {
this.cprVhcleList.splice(idx, 1);
},
// 유효성 검사
validateForm() {
if (this.$isEmpty(this.bsrpInfo.bsrpSe)) {
return false;
}
if (this.$isEmpty(this.bsrpInfo.bsrpPlace)) {
return false;
}
if (this.$isEmpty(this.bsrpInfo.bsrpPurps)) {
return false;
}
if (this.$isEmpty(this.bsrpInfo.bgnde) || this.$isEmpty(this.bsrpInfo.beginHour) || this.$isEmpty(this.bsrpInfo.beginMnt)) {
return false;
}
if (this.$isEmpty(this.bsrpInfo.endde) || this.$isEmpty(this.bsrpInfo.endHour) || this.$isEmpty(this.bsrpInfo.endMnt)) {
return false;
}
if (this.$isEmpty(this.bsrpInfo.bsrpNmprList) || this.bsrpInfo.bsrpNmprList.length === 0) {
return false;
}
if (this.$isEmpty(this.bsrpCnsul.cn)) {
return false;
}
return true;
},
// 신청
async fnSave() {
try {
if (!this.validateForm()) {
return;
}
let data = {
bsrpCnsulInsertDTO: this.bsrpCnsul,
cardDtlsList: this.cprCardList,
vhcleDtlsList: this.cprVhcleList,
}
if (this.$isEmpty(this.pageId)) {
data.bsrpInfoInsertDTO = this.bsrpInfo;
} else {
data.bsrpInfoUpdateDTO = this.bsrpInfo;
}
const response = this.$isEmpty(this.pageId) ? await saveBsrpProc(data) : await updateBsrpProc(data);
const message = this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다.";
alert(message);
this.fnMoveTo('view', response.data.data.pageId);
} catch (error) {
console.error('저장 실패:', error);
const message = error.response?.data?.message || "저장에 실패했습니다.";
alert(message);
}
},
fnMoveTo(type, id) {
const routes = {
'list': { name: 'ChuljangStatue' },
'view': { name: 'ChuljangDetailAll', query: { id } },
'edit': { name: 'ChuljangInsert', query: this.$isEmpty(id) ? {} : { id } },
};
if (routes[type]) {
if (!this.$isEmpty(this.pageId) && type === 'list') {
this.$router.push({ name: 'ChuljangDetailAll', query: { id: this.pageId } });
return;
}
this.$router.push(routes[type]);
} else {
alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
this.$router.push(routes['list']);
}
},
},
};
</script>