Files
oa/src/pages/app/documentManagementInsert.vue

722 lines
22 KiB
Vue

<script setup lang="ts">
import {saveTempSlip} from "/@src/service/slipApi.ts";
import {type iPbAtt, Person, SapPerson} from "/@src/utils/types.ts";
import {VTabsItem} from "/src/components/app-vuero/VCustomTabs.vue";
import {getContractDetail} from "/@src/service/contractApi.ts";
import {deletePrcsFile} from "/@src/service/priceApi.ts";
onBeforeMount(async ()=>{
const result = await getContractDetail(history.state.key)
// 계약종료는 contract api 결재선 추가해야 됨
getDetailBeforeList(result)
})
const notyf = useNotyf()
const affiliationCode = ref()
const taxCode = ref('')
const currencyCode = ref('KRW')
const taxInvoiceCode = ref()
const accountSubjectCode = ref()
const params = reactive({
//샘플 데이터 형식으로 맞춤
// https://git.hmsn.ink/kospo/svcm/api/-/blob/main/api/slip/%EC%9E%84%EC%8B%9C%EC%A0%84%ED%91%9C%EC%A0%80%EC%9E%A5.http?ref_type=heads
cateCd: '',
contNo: '',
bldat: '',
budat: '',
regSdt: '',
regSdt2: '',
//waers => currencyCode ref
bktxt: '', //계약명
lifnr: '', //계약상대자
//wrbtr => formattedNumber ref
//mwskz => taxCode ref
//gsber => affiliationCode ref
//bupla => affiliationCode ref
zterm: '',
banks: '',
bankl:'',
bankn:'',
hkont: '',
wrbtrS: '', //공급가액 체크 필요 todo
kostl: '',
projk: '',
trtGubun: '',
txBillSeq: '',
slipAtts: [],
//apprList:[], ==> apprLine 변수로 변경 // 결재선 데이터 키: 값(배열 들어감)
compNm: '',
signDt: '',
contAmt: 0,
contStat: '',
regsabun: '',
place: '',
regDt: '',
reason: '',
page: 1,
row: 10,
flexColumn: [
//이름은 안넘어오고 있음
{ key: 'lineclsf', label: '구분', value: {} },
{ key: 'bname', label: '사원번호', value: {} },
{ key: 'btext', label: '이름', value: {} },
{ key: 'abscd', label: '근태관리', value: {} },
{ key: 'actions', label: '동작' },
],
modalColumn: [],
})
const pbAtts = ref<iPbAtt[]>(params.slipAtts)
const formattedNumber = ref(0)
const apprLine = ref<SapPerson[]>([])
const getDetailBeforeList = (item) => {
console.log("item before",item)
params.contNo = item.contNo
params.place = item.compNm// !!!!!!업체명 bupla
params.bktxt = item.title //!!!!!!계약명
params.lifnr = item.bizNo //!!!!!계약상대자
}
function onInput(event) {
let onlyNumber = event.target.value.replace(/[^0-9]/g, '')
formattedNumber.value = onlyNumber ? Number(onlyNumber).toLocaleString() : ''
}
const selectCostCode = ref('코스트센터') //todo
const selectCostCodeOptions = [
{ text: '코스트센터', key: 'kostl' },
{ text: 'WBS', key: 'projk'}
]
const router = useRouter()
const cancel = () => {
router.push('/app/contractManagement')
}
const validation = () => {
notyf.dismissAll()
const resultGu = apprLine.value.filter((item,index)=> index != 0 && !item.lineclsf)
if(resultGu.length > 0){
console.log("resultGu",resultGu)
notyf.error("결재선 구분값을 입력해주세요")
return false
}
if(apprLine.value.length < 2){
notyf.error("결재선은 본인 외 2명이상 필수입니다.")
return false
}
const result = apprLine.value.filter((item)=> item.lineclsf === 'A' )
if(result.length > 1){
notyf.error("결재는 한 명입니다.")
return false
}
const resultLastItem = apprLine.value[apprLine.value.length-1].lineclsf
if(resultLastItem != 'A'){
notyf.error("결재선의 마지막은 결재자이어야 함")
return false
}
return true
}
const loading = ref(false)
const createChit = async () => {
let res = null
try{
loading.value = true
if (validation()) {
const createParams = {
contNo: params.contNo, //"CONT-0000000005",
bldat: '20250602',//params.regSdt.replace("-",""), // "20250501",
budat: '20250610',//params.regSdt2.replace("-",""), // "20250502",
waers: currencyCode.value, //currencyCode.value, //"KRW",
bktxt: params.bktxt,//"전표 생성 테스트1",
lifnr: params.lifnr, //999-99-99999",
wrbtr: "1203", //formattedNumber.value, //!!!!!수정필요 "1203",
mwskz: taxCode.value, //"V4",
gsber: affiliationCode.value, //무슨 값인지 모름 gsber bukrs bupla 같다고 함 1000
bupla: affiliationCode.value, //1000
zterm: 'PF00', //params.zterm, //!!!!!!어떤 값인지 모름 PF00
banks: 'KR', //params.banks , //어떤 값인지 모름 KR
bankl: params.bankl, //012
bankn: params.bankn, //3510876657453
hkont: '5366010', //params.hkont,//G/L 계정 어떤 값인지 모름 5366010
wrbtrS: formattedNumber.value, //1102
kostl: "12330", //!!!!!!12330
projk: selectCostCode.value, //빈값
trtGubun: '11', //params.trtGubun,//!!!!!!어떤 값인지 모름 11
txBillSeq: '202503231', //params.txBillSeq,//!!!!!!어떤 값인지 모름 202503231
slipAtts: pbAtts.value.map(req => ({
logiFnm: req.logiFnm,
data: req.data
})),//첨부파일 데이터,
zwf0011t: {
wkftx: "결재 테스트11", //!!!!!!어떤 값인지 모름
apprs: [{
label: '결재',
value: apprLine.value.map((req) => ({
lineclsf: req.lineclsf,
bname: req.bname,
abscd: req.abscd
}))
}]
}
// waers : currencyCode.value, //"KRW",
// bktxt : params.bktxt,//"전표 생성 테스트1",
// lifnr : params.lifnr, //999-99-99999",
// wrbtr : formattedNumber.value, //수정필요 "1203",
// mwskz : taxCode.value, //"V4",
// gsber : affiliationCode.value, //무슨 값인지 모름 gsber bukrs bupla 같다고 함 1000
// bupla : affiliationCode.value, //1000
// zterm : params.zterm, //어떤 값인지 모름 PF00
// banks : params.banks , //어떤 값인지 모름 KR
// bankl : params.bankl, //012
// bankn : params.bankn, //3510876657453
// hkont : params.hkont,//G/L 계정 5366010
// wrbtrS : formattedNumber.value, //1102
// kostl : "12330", //12330
// projk : selectCostCode.value, //빈값
// trtGubun : params.trtGubun,//어떤 값인지 모름 11
// txBillSeq : params.txBillSeq,//어떤 값인지 모름 202503231
}
notyf.dismissAll()
res = await saveTempSlip(createParams)
if (res.request.status == '200') {
notyf.primary('전표가 등록 되었습니다.')
router.push({path: '/app/priceManagement'})
}
}
}catch(e){
notyf.error(e.message)
}finally {
loading.value = false
}
}
const selectUser = ref<SapPerson[]>([])
const isDuplicate = (person: SapPerson) =>{
return apprLine.value.some((p) => p.bname === person.bname)}
watch(
selectUser,
(newPersons) => {
if (Array.isArray(newPersons) && newPersons.length > 0) {
const filtered = newPersons.filter((p) => !isDuplicate(p))
apprLine.value = [...apprLine.value, ...filtered]
selectUser.value = []
}
},
{ deep: true }
)
const onFileClick = () => {
const input = <any> document.querySelector('.file-input')
input.click()
}
const onFileChange = (e: any) => {
Array.from(e.target.files).forEach((file: any) => {
const reader = new FileReader()
reader.onload = () => {
const result = <string>reader.result
const pbAtt = <iPbAtt>{ // todo 여기 첨부파일로 변경 필요
prcsNo: params.prcsNo,
logiFnm: file.name,
size: file.size,
data: result.split(',')[1],
}
notyf.dismissAll()
if(pbAtt.logiFnm.includes('.pdf')) { //todo 여러개 올릴 때 이슈 발생 확인 필요
pbAtts.value.push(pbAtt)
}else{
notyf.error("pdf 파일만 업로드 가능합니다.")
}
}
reader.readAsDataURL(file)
})
}
const onFilDelete = async (prcsNo: string, index: number, fileOrd: number) => {
notyf.dismissAll()
const confirmed = confirm('삭제하시겠습니까?')
if (!prcsNo) {
if (confirmed){
pbAtts.value.splice(index, 1)
}
} else {
if (confirmed) {
await deletePrcsFile(prcsNo, fileOrd).then((res: string) => {
notyf.success(res)
pbAtts.value.splice(index, 1)
}).catch((err) => {
notyf.error('삭제 중 오류가 발생했습니다.')
console.error(err)
})
}
}
}
const onPrcsFileDownload = async (prcsNo: string, fileOrd: number, logiFnm: string) => {
const link = document.createElement('a')
link.href = `https://svcm.hmsn.ink/api/prcs/${prcsNo}/${fileOrd}` //todo
link.setAttribute('download', logiFnm)
link.setAttribute('target', '_blank')
document.body.appendChild(link)
link.click()
}
const onPriceDelete = (index: number) => {
if(apprLine.value.length-1 !== params.flexColumn.length
|| (params.flexColumn.length == 8 && apprLine.value.length-1 == params.flexColumn.length))
{
apprLine.value.splice(index, 1)
}
}
const moveUp = (index: number) => {
if (index <= 0) return
let temp = apprLine.value[index]
apprLine.value[index] = apprLine.value[index - 1]
apprLine.value[index - 1] = temp
}
const moveDown = (index: number) => {
if (index >= apprLine.value.length - 1) return
let temp = apprLine.value[index]
apprLine.value[index] = apprLine.value[index + 1]
apprLine.value[index + 1] = temp
}
</script>
<template>
<div class="page-content is-navbar-lg">
<div class="datatable-wrapper">
<div class="table-container">
<table class="table datatable-table is-fullwidth">
<colgroup>
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr>
<td>소속</td>
<td colspan="2">
<VField>
<VCodeSelect
v-model="affiliationCode"
cd_grp=11 >
</VCodeSelect>
</VField>
</td>
<td colspan="7" />
</tr>
<tr>
<td>계약명</td>
<td colspan="9">
<VField>
<VControl>
<input
v-model="params.bktxt"
class="input custom-text-filter"
placeholder="계약명"
>
</VControl>
</VField>
</td>
</tr>
<tr>
<td>계약상대자</td>
<td colspan="2">
<VField>
<VControl>
<input
v-model="params.lifnr"
class="input custom-text-filter"
placeholder="사업자번호"
>
</VControl>
</VField>
</td>
<td colspan="2">
<VField>
<VControl>
<input
v-model="params.place"
class="input custom-text-filter"
placeholder="업체명"
>
</VControl>
</VField>
</td>
<td>
<VButton color="danger">
구매처 확인
</VButton>
</td>
<td>
<VButton color="success">
정상
</VButton>
</td>
<td colspan="2" />
</tr>
<tr>
<td>공급가액</td>
<td colspan="2">
<VField>
<VControl>
<input
:value="formattedNumber"
placeholder="금액"
@input="onInput"
class="input custom-text-filter"
>
</VControl>
</VField>
</td>
<td style="color: black">(부가세 별도)</td>
<td style="background-color: var(--primary); text-align: center">
<span>세금코드</span>
</td>
<td colspan="2">
<VField>
<VCodeSelect v-model="taxCode" cd_grp="12">
<template #code="{ item }">
{{ item.cd }}
</template>
</VCodeSelect>
</VField>
</td>
<td style="background-color: var(--primary); text-align: center">통화</td>
<td colspan="2">
<VField>
<VCodeSelect v-model="currencyCode" cd_grp="13">
<template #code="{ item }">
{{ item.cd }}
</template>
</VCodeSelect>
</VField>
</td>
</tr>
<tr>
<td>증빙일</td>
<td colspan="2">
<div>
<VDatePicker
v-model="params.regSdt"
color="green"
trim-weeks
>
<template #default="{ inputValue, inputEvents }">
<VField>
<VControl icon="lucide:calendar">
<input
class="input v-input"
type="text"
:value="inputValue"
placeholder="증빙일"
v-on="inputEvents"
>
</VControl>
</VField>
</template>
</VDatePicker>
</div>
</td>
<td />
<td style="background-color: var(--primary); text-align: center">
<span>전기일</span>
</td>
<td colspan="2">
<div>
<VDatePicker
v-model="params.regSdt2"
color="green"
trim-weeks
>
<template #default="{ inputValue, inputEvents }">
<VField>
<VControl icon="lucide:calendar">
<input
class="input v-input"
type="text"
:value="inputValue"
placeholder="전기일"
v-on="inputEvents"
>
</VControl>
</VField>
</template>
</VDatePicker>
</div>
</td>
</tr>
<tr>
<td>계좌관리</td>
<td>
<VField>
<VControl>
<input
v-model="params.bankl"
class="input custom-text-filter"
placeholder="계약번호"
>
</VControl>
</VField>
</td>
<td colspan="2">
<VField>
<VControl>
<input
v-model="params.bankn"
class="input custom-text-filter"
placeholder="계좌번호"
>
</VControl>
</VField>
</td>
<td>
<VButton color="info">
계좌조회
</VButton>
</td>
<td>
<VButton color="success">
정상
</VButton>
</td>
<td></td>
<td style="background-color: var(--primary); text-align: center">
<span>세금계산서</span>
</td>
<td colspan="2">
<VField>
<VCodeSelect
v-model="taxInvoiceCode"
cd_grp=12 >
</VCodeSelect>
</VField>
</td>
</tr>
<tr>
<td>예산관리</td>
<td>
<VButton
disabled
color="primary">
G/L계정
</VButton>
</td>
<td colspan="2">
<VField>
<VCodeSelect
v-model="accountSubjectCode"
cd_grp=12 >
</VCodeSelect>
</VField>
</td>
<td>
<VField>
<VSelect v-model="selectCostCode">
<option v-for="opt in selectCostCodeOptions" :key="opt.key">
{{ opt.text }}
</option>
</VSelect>
</VField>
</td>
<td colspan="2">
<VField>
<VControl>
<input
v-model="params.bktxt"
class="input custom-text-filter"
placeholder="코드"
>
</VControl>
</VField>
</td>
<td colspan="4"></td>
</tr>
<tr>
<td>첨부파일</td>
<td colspan="1">
<VField>
<div class="form-label">
<div class="file has-name is-fullwidth">
<input
class="file-input hide"
type="file"
multiple
@change="(e) => onFileChange(e)"
>
<VLabel>
<VButton icon="fas fa-plus" color="info" class="file-trigger" @click="onFileClick" >
<span>파일업로드</span>
</VButton>
</VLabel>
</div>
</div>
<VControl>
<div
v-for="(f, i) in pbAtts"
:key="f.logiFnm"
class="content estimate-file-wrapper"
>
<div class="estimate-file-name">
{{ f.logiFnm }}{{" ("}}{{Math.ceil(f.size / 1024)}}kb{{")"}}
</div>
<div>
<i class="fa fa-trash estimate-file-delete" @click="onFilDelete(f.bizNo, i, f.fileOrd)" />
</div>
</div>
</VControl>
</VField>
<div class="column is-12">
<VField>
<VControl>
<div
v-for="f in params.slipAtts"
:key="f.logiFnm"
class="content estimate-file-wrapper"
>
<div class="estimate-file-name">
{{ f.logiFnm }}{{" ("}}{{Math.ceil(f.size / 1024)}}kb{{")"}}
</div>
<span>
<i class="fa fa-download estimate-file-download" @click="onPrcsFileDownload(f.prcsNo, f.fileOrd, f.logiFnm)" />
<i class="fa fa-trash estimate-file-delete" @click="onFilDelete(f.prcsNo, i, f.fileOrd)" />
</span>
</div>
</VControl>
</VField>
</div>
</td>
<td colspan="5">
<div>('준공보고서' 또는 '검수보고서' )</div>
</td>
</tr>
</tbody>
</table>
<div class="bottom-button">
<VButton @click="createChit"> </VButton>
<VButton @click="cancel"> </VButton>
</div>
<div class="column is-12">
<VUserSap v-model="selectUser" placeholder="사번이나 이름으로 검색해주세요"/>
</div>
<div>
<ComVFlexTable
:data="apprLine"
:columns="params.flexColumn"
:compact="true"
>
<template #body-cell="{row, column, index, value}">
<span v-if="column.key=='lineclsf'" class="column">
<VField class="pr-1">
<VCodeSelect
cd_grp=9
v-model="row.lineclsf"
/>
</VField>
</span>
<span v-else-if="column.key=='wkfst'" class="column">
<VField class="pr-1">
<VCodeSelect
cd_grp=8
v-model="row.wkfst"
/>
</VField>
</span>
<span v-else-if="column.key === 'actions'" class="flex gap-1">
<VCustomButton v-if="index != 1" @click="moveUp(index)" icon="lucide:arrow-up" />
<VCustomButton @click="moveDown(index)" icon="lucide:arrow-down" />
<VCustomButton @click="onPriceDelete(index)">{{"삭제"}}</VCustomButton>
</span>
</template>
</ComVFlexTable>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.table tbody td {
color: var(--smoke-white);
}
.datatable-table {
td{
font-family: var(--font),serif;
vertical-align: middle;
padding: 4px 20px;
border-bottom: 1px solid var(--fade-grey);
}
td:nth-child(1) {
background-color: var(--primary);
text-align: center;
}
tr:nth-child(3) {
text-align: center;
}
tr td button{
width: 100%;
}
}
.button.v-button {
padding: 0px 0px;
}
.bottom-button {
text-align: center;
button {
background-color: cornflowerblue;
margin: 10px;
font-weight: bold;
border-color: var(--primary);
color: white;
padding: 0.5rem 1rem !important;
}
}
button:nth-child(2) {
background-color: #AB9A6c;
}
button:nth-child(3) {
background-color: silver;
}
.field {
margin: 0px 0px;
}
.disabled-button {
//opacity: 0.5;
background-color: #ccc;
cursor: not-allowed;
//transition: all 0.2s;
}
</style>