Merge remote-tracking branch 'origin/main'

This commit is contained in:
Yesol Choi
2025-05-28 09:27:38 +09:00
3 changed files with 223 additions and 73 deletions

View File

@@ -7,8 +7,13 @@ import {useRouter} from "vue-router";
const registerFormOpen = ref(false) const registerFormOpen = ref(false)
const loading = ref(false) const loading = ref(false)
const isSelectOpen = ref(false)
const notyf = useNotyf() const notyf = useNotyf()
const router = useRouter() const router = useRouter()
const selectedCode = ref()
const priceSearchCheckBoxStatus = ref(false)
const isLoading = ref(false)
const params = reactive({ const params = reactive({
cateCd: '', cateCd: '',
contNo: '', contNo: '',
@@ -29,6 +34,8 @@ const params = reactive({
contAtts: [], //첨부파일 데이터 contAtts: [], //첨부파일 데이터
}) })
const completedPriceDataParams = reactive({ const completedPriceDataParams = reactive({
prcsNo:'', prcsNo:'',
cateCd:'', cateCd:'',
@@ -37,8 +44,8 @@ const completedPriceDataParams = reactive({
compNm:'', compNm:'',
title:'', title:'',
content:'', content:'',
regSdat:'', contSdat:'',
regEdat:'', contEdat:'',
regSabun:'', regSabun:'',
regNm:'', regNm:'',
regDt:'', regDt:'',
@@ -62,15 +69,16 @@ params.modalColumn = [
{ key: 'regNm', label: '선택' }, { key: 'regNm', label: '선택' },
] ]
const selectedCode = ref() params.contAtts = [
const priceSearchCheckBoxStatus = ref(false) { prcsNo : '', bizNo : '' ,fileOrd : 0 ,logiFnm : '' ,physFnm : '',size : 0 ,data : '' ,path : '' },
]
const data = reactive({ const data = reactive({
contractData: [], contractData: [],
completedPriceSearchData: [], completedPriceSearchData: [],
}) })
const isLoading = ref(false)
watch(registerFormOpen, async (isOpen) => { watch(registerFormOpen, async (isOpen) => {
if (isOpen) { if (isOpen) {
isLoading.value = true isLoading.value = true
@@ -105,8 +113,8 @@ function getDateDiff(start, end) {
} }
const contractPeriod = computed(() => { const contractPeriod = computed(() => {
const start = completedPriceDataParams.regSdat const start = completedPriceDataParams.contSdat
const end = completedPriceDataParams.regEdat const end = completedPriceDataParams.contEdat
const startStr = formatMonthDate(start) const startStr = formatMonthDate(start)
const endStr = formatMonthDate(end) const endStr = formatMonthDate(end)
const diff = getDateDiff(start, end) const diff = getDateDiff(start, end)
@@ -134,8 +142,8 @@ function handlePriceRowClick(row) {
completedPriceDataParams.cateNm = row.cateNm || '' completedPriceDataParams.cateNm = row.cateNm || ''
completedPriceDataParams.title = row.title || '' completedPriceDataParams.title = row.title || ''
completedPriceDataParams.content = row.content || '' completedPriceDataParams.content = row.content || ''
completedPriceDataParams.regSdat = row.regSdat || '' completedPriceDataParams.contSdat = row.regSdat || ''
completedPriceDataParams.regEdat = row.regEdat || '' completedPriceDataParams.contEdat = row.regEdat || ''
completedPriceDataParams.regSabun = row.regSabun || '' completedPriceDataParams.regSabun = row.regSabun || ''
completedPriceDataParams.regNm = row.regNm || '' completedPriceDataParams.regNm = row.regNm || ''
completedPriceDataParams.regDt = row.regDt || '' completedPriceDataParams.regDt = row.regDt || ''
@@ -160,8 +168,9 @@ function handlePriceRowClick(row) {
} }
registerFormOpen.value = false registerFormOpen.value = false
completedPriceDataParams.estimates = row.estimates
console.log(row) console.log(row)
console.log(completedPriceDataParams.contAmt) console.log(completedPriceDataParams.estimates)
} }
const showFileInputs = ref(false) const showFileInputs = ref(false)
@@ -179,19 +188,27 @@ const saveContOne = async () => {
let res = null let res = null
try{ try{
loading.value = true loading.value = true
// if (!validation()) {
// return; const amt = Number(String(completedPriceDataParams.contAmt).replace(/[^0-9]/g, ''))
// } if (amt >= 10000000) {
const injiFile = pbAtts.value[4]
if (!injiFile || !injiFile.logiFnm) {
notyf.error('계약금액이 1,000만원 이상일 경우 인지세 납부확인서 첨부가 필수입니다.')
loading.value = false
return
}
}
const paramsCont ={ const paramsCont ={
prcsNo: completedPriceDataParams.prcsNo, prcsNo: completedPriceDataParams.prcsNo,
bizNo: completedPriceDataParams.bizNo, bizNo: completedPriceDataParams.bizNo,
cateCd: priceSearchCheckBoxStatus.value ? selectedCode.value : completedPriceDataParams.cateCd, cateCd: priceSearchCheckBoxStatus.value ? selectedCode.value : completedPriceDataParams.cateCd,
compNm: completedPriceDataParams.compNm, compNm: completedPriceDataParams.compNm,
title: completedPriceDataParams.title, title: completedPriceDataParams.title,
regSdat: formatMonthDate(completedPriceDataParams.regSdat), contSdat: formatMonthDate(completedPriceDataParams.contSdat),
regEdat: formatMonthDate(completedPriceDataParams.regEdat), contEdat: formatMonthDate(completedPriceDataParams.contEdat),
contAmt: completedPriceDataParams.contAmt, contAmt: completedPriceDataParams.contAmt,
signDt: formatMonthDate(completedPriceDataParams.regSdat), signDt: formatMonthDate(completedPriceDataParams.contSdat),
reason: completedPriceDataParams.reason, reason: completedPriceDataParams.reason,
excYn: priceSearchCheckBoxStatus.value, // 가격조사 예외여부 확인필요 excYn: priceSearchCheckBoxStatus.value, // 가격조사 예외여부 확인필요
contAtts: params.contAtts, contAtts: params.contAtts,
@@ -201,7 +218,7 @@ const saveContOne = async () => {
if(res.request.status == '200'){ if(res.request.status == '200'){
notyf.primary('등록 되었습니다.') notyf.primary('등록 되었습니다.')
// router.push({path: '/app/contractManagement'}) router.push({path: '/app/contractManagement'})
} }
}catch(e){ }catch(e){
notyf.error(e.message) notyf.error(e.message)
@@ -227,7 +244,7 @@ const onFileChange = (e, idx) => {
// 인덱스별로 파일 저장 // 인덱스별로 파일 저장
pbAtts.value[idx] = pbAtt pbAtts.value[idx] = pbAtt
console.log(pbAtts) console.log(pbAtts)
console.log(pbAtts.value[0].logiFnm) console.log(pbAtts.value[idx].logiFnm)
} }
reader.readAsDataURL(file) reader.readAsDataURL(file)
} }
@@ -237,8 +254,8 @@ function resetForm() {
completedPriceDataParams.bizNo = '' completedPriceDataParams.bizNo = ''
completedPriceDataParams.compNm = '' completedPriceDataParams.compNm = ''
completedPriceDataParams.title = '' completedPriceDataParams.title = ''
completedPriceDataParams.regSdat = '' completedPriceDataParams.contSdat = ''
completedPriceDataParams.regEdat = '' completedPriceDataParams.contEdat = ''
completedPriceDataParams.contAmt = '' completedPriceDataParams.contAmt = ''
completedPriceDataParams.reason = '' completedPriceDataParams.reason = ''
selectedCode.value = '' selectedCode.value = ''
@@ -254,6 +271,33 @@ watch(
function routerMove() { function routerMove() {
router.push({path: '/app/contractManagement'}) router.push({path: '/app/contractManagement'})
} }
const showReasonError = computed(() =>
priceSearchCheckBoxStatus.value &&
(!completedPriceDataParams.reason || !completedPriceDataParams.reason.trim())
)
const estimateBizNoOptions = computed(() =>
completedPriceDataParams.estimates.map(est => ({
value: est.bizNo,
text: est.bizNo
}))
)
function onBizNoChange() {
const selected = completedPriceDataParams.estimates.find(est => est.bizNo === completedPriceDataParams.bizNo)
if (selected) {
completedPriceDataParams.compNm = selected.compNm || ''
completedPriceDataParams.contAmt = selected.amt || 0
completedPriceDataParams.bizNo = selected.bizNo || ''
} else {
completedPriceDataParams.compNm = ''
completedPriceDataParams.contAmt = 0
completedPriceDataParams.bizNo = ''
}
}
</script> </script>
<template> <template>
@@ -330,6 +374,7 @@ function routerMove() {
v-model="completedPriceDataParams.reason" v-model="completedPriceDataParams.reason"
class="input custom-text-filter" class="input custom-text-filter"
placeholder="가격조사 안했을 시 예외 사유 입력(필수)" placeholder="가격조사 안했을 시 예외 사유 입력(필수)"
:class="{ 'danger-placeholder': showReasonError }"
> >
</VControl> </VControl>
</VField> </VField>
@@ -356,12 +401,21 @@ function routerMove() {
<td> <td>
<VField> <VField>
<VControl> <VControl>
<input <VSelect
v-model="completedPriceDataParams.bizNo" v-model="completedPriceDataParams.bizNo"
class="input custom-text-filter" class="input custom-text-filter"
placeholder="사업자번호" @change="onBizNoChange"
:disabled="!priceSearchCheckBoxStatus" @focus="isSelectOpen = true"
> >
<option value="" disabled>사업자번호 선택</option>
<option
v-for="option in estimateBizNoOptions"
:key="option.value"
:value="option.value"
>
{{ option.text }}
</option>
</VSelect>
</VControl> </VControl>
</VField> </VField>
</td> </td>
@@ -404,7 +458,7 @@ function routerMove() {
<VField> <VField>
<VControl> <VControl>
<input <input
:value="formatMonthDate(completedPriceDataParams.regSdat)" :value="formatMonthDate(completedPriceDataParams.contSdat)"
class="input custom-text-filter" class="input custom-text-filter"
placeholder="계약체결일" placeholder="계약체결일"
:disabled="!priceSearchCheckBoxStatus" :disabled="!priceSearchCheckBoxStatus"
@@ -452,7 +506,7 @@ function routerMove() {
<div> <div>
<div> <div>
<VDatePicker <VDatePicker
v-model="completedPriceDataParams.regSdat" v-model="completedPriceDataParams.contSdat"
color="green" color="green"
trim-weeks trim-weeks
> >
@@ -477,7 +531,7 @@ function routerMove() {
<div class=""> <div class="">
<div> <div>
<VDatePicker <VDatePicker
v-model="completedPriceDataParams.regEdat" v-model="completedPriceDataParams.contEdat"
color="green" color="green"
trim-weeks trim-weeks
> >
@@ -511,35 +565,41 @@ function routerMove() {
</td> </td>
<td colspan="5"> <td colspan="5">
<!-- 첨부파일 입력영역: 등록 버튼 클릭 토글 --> <!-- 첨부파일 입력영역: 등록 버튼 클릭 토글 -->
<div v-if="showFileInputs" class="file-upload-list" style="margin-top:10px;">
<div
v-for="(input, idx) in fileInputs"
:key="idx"
style="display: flex; align-items: center; margin-bottom: 8px;"
>
<!-- 파일선택 -->
<label class="file-label" style="margin-right: 10px;">
<input
type="file"
class="file-input"
@change="(e) => onFileChange(e, idx)"
/>
<span class="file-cta">파일선택</span>
</label>
<span style="flex:1; margin-right: 10px;">
{{ pbAtts[idx] == null ? '' : pbAtts[idx].logiFnm }}
</span>
<span style="flex:2; color: #666;">{{ input.description }}</span>
</div>
</div>
</td> </td>
</tr> </tr>
<tr>
<td></td>
<td colspan="7">
<div v-if="showFileInputs" class="file-upload-list" style="margin-top:10px;">
<div
v-for="(input, idx) in fileInputs"
:key="idx"
style="display: flex; align-items: center; margin-bottom: 8px;"
>
<!-- 파일선택 -->
<label class="file-label" style="margin-right: 10px;">
<input
type="file"
class="file-input"
@change="(e) => onFileChange(e, idx)"
/>
<span class="file-cta">파일선택</span>
</label>
<span style="flex:1; margin-right: 10px;">
{{ pbAtts[idx] == null ? '' : pbAtts[idx].logiFnm }}
</span>
<span style="flex:2; color: #666;">{{ input.description }}</span>
</div>
</div>
</td>
</tr>
</tbody> </tbody>
</table> </table>
<div class="bottom-button"> </div>
<VButton @click="saveContOne"> </VButton> <div class="bottom-button">
<VButton @click="routerMove"> </VButton> <VButton @click="saveContOne"> </VButton>
</div> <VButton @click="routerMove"> </VButton>
</div> </div>
</div> </div>
</div> </div>
@@ -549,6 +609,7 @@ function routerMove() {
.table tbody td { .table tbody td {
color: var(--smoke-white); color: var(--smoke-white);
} }
.datatable-table { .datatable-table {
td{ td{
font-family: var(--font),serif; font-family: var(--font),serif;
@@ -573,6 +634,21 @@ function routerMove() {
tr:nth-child(5) > td:nth-child(4) { tr:nth-child(5) > td:nth-child(4) {
color: black; color: black;
} }
tr:nth-child(7) > td {
padding: 0px 12px;
}
}
/* 일반 상태 */
.input::placeholder {
color: #b0b0b0;
}
/* 에러 상태 (빨간색) */
.danger-placeholder::placeholder {
color: #ff3860; /* Bulma 기준 빨간색 */
opacity: 1; /* 크롬 등에서 흐려지는 것 방지 */
} }
.bottom-button { .bottom-button {

View File

@@ -1,21 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
// import { getIntegratedApproval } from '/src/service/integratedApprovalApi' import {conveterNo, getIntegratedApproval, updateIntegratedStatus} from '/src/service/integratedPayment'
import { getIntegratedApproval, updateIntegratedApproval } from '/src/service/integratedPayment'
import type { VFlexTableWrapperSortFunction, VFlexTableWrapperFilterFunction } from '/src/components/app-vuero/ComVFlexTableWrapper.vue' import type { VFlexTableWrapperSortFunction, VFlexTableWrapperFilterFunction } from '/src/components/app-vuero/ComVFlexTableWrapper.vue'
import { users } from '/src/data/layouts/card-grid-v1' import { users } from '/src/data/layouts/card-grid-v1'
import PriceDetail from "/@src/pages/app/priceDetail.vue"; import PriceDetail from "/@src/pages/app/priceDetail.vue";
onBeforeMount(async () => { onBeforeMount(async () => {
await getIntegratedPaymentList() await getIntegratedPaymentList()
}) })
const isModalOpen = ref(false) const isModalOpen = ref(false)
const selectedRow = ref<any>(null) const selectedRow = ref<any>(null)
const prcsNo = ref('PRCS-20250521020') const prcsNo = ref<string>('')
const router = useRouter()
const masks = ref({ const masks = ref({
modelValue: 'YYYY-MM-DD', modelValue: 'YYYY-MM-DD',
}) })
const notyf = useNotyf()
const params = reactive({ const params = reactive({
title: '', title: '',
@@ -40,6 +41,7 @@ const params = reactive({
process: { label: '구분', cellClass: 'paymentColumn5', searchable: true, sortable: true }, process: { label: '구분', cellClass: 'paymentColumn5', searchable: true, sortable: true },
}, },
paymentParams: [], paymentParams: [],
rowData: [],
}) })
function formatRegDt(value) { function formatRegDt(value) {
@@ -51,7 +53,7 @@ async function getIntegratedPaymentList() {
title: '', title: '',
page: '1', page: '1',
row: '10', row: '10',
sabun: '17131303', sabun: '17131303', // 김진형 17131303, 손원창 17131304
} }
const result = await getIntegratedApproval(paymentParams) const result = await getIntegratedApproval(paymentParams)
params.paymentParams = result.content.map(item => ({ params.paymentParams = result.content.map(item => ({
@@ -60,18 +62,30 @@ async function getIntegratedPaymentList() {
})) }))
} }
async function updateIntegratedPaymentList() { async function updateIntegratedPaymentApprovalFunc() {
const paymentUpdateParams = { const paymentUpdateParams = {
apprNo: selectedRow.value.apprNo, apprNo: params.rowData.apprNo,
apprOrd: selectedRow.value.apprOrd, apprOrd: params.rowData.apprOrd,
sabun: params.rowData.sabun,
apprStatCd: '0200', // 결재 상태변경 (결재승인 0200 결재회수 0300 결재반려 0400) apprStatCd: '0200', // 결재 상태변경 (결재승인 0200 결재회수 0300 결재반려 0400)
reason: '', reason: '',
sabun: selectedRow.value.sabun,
} }
const result = await updateIntegratedApproval(paymentUpdateParams) const result = await updateIntegratedStatus(paymentUpdateParams)
console.log(result) notyf.primary("결재승인완료")
notyf.error("견적서가 없습니다.") window.location.reload()
router.push('/app/paymentManagement') }
async function updateIntegratedPaymentRejectionFunc() {
const paymentUpdateParams = {
apprNo: params.rowData.apprNo,
apprOrd: params.rowData.apprOrd,
sabun: params.rowData.sabun,
apprStatCd: '0400', // 결재 상태변경 (결재승인 0200 결재회수 0300 결재반려 0400)
reason: '',
}
const result = await updateIntegratedStatus(paymentUpdateParams)
notyf.primary("결재반려완료")
window.location.reload()
} }
const gubunMap = { const gubunMap = {
@@ -125,12 +139,42 @@ const userFilter: VFlexTableWrapperFilterFunction<User> = ({ searchTerm, row })
) )
} }
// 로우 클릭 핸들러 const onRowClick = async (row) => {
const onRowClick = (row: any) => { params.rowData = row
try {
const params = {
apprNo: row.apprNo,
sabun: row.sabun
}
console.log(params)
// API 호출
const result = await conveterNo(params)
// 모달 무조건 열기
isModalOpen.value = true
// prcsNo 설정 (응답 구조에 맞게 수정)
if (result?.prcsNo) {
prcsNo.value = result.prcsNo
} else {
notyf.error('가격조사번호를 찾을 수 없습니다.')
}
} catch (e) {
isModalOpen.value = true // 에러 시에도 모달 열기
notyf.error(e.message)
}
}
const onRowClick2 = (row: any) => {
selectedRow.value = row selectedRow.value = row
isModalOpen.value = true isModalOpen.value = true
console.log(selectedRow) prcsNo.value = row.prcsNo
// prcsNo.value = 'PRCS-20250527049'
console.log(row.apprNo)
} }
</script> </script>
<template> <template>
@@ -259,8 +303,8 @@ const onRowClick = (row: any) => {
<PriceDetail :prcsNo="prcsNo" /> <PriceDetail :prcsNo="prcsNo" />
</template> </template>
<template #action> <template #action>
<VButton type="submit" color="info" raised>반려</VButton> <VButton type="submit" color="info" raised @click="updateIntegratedPaymentRejectionFunc">반려</VButton>
<VButton type="submit" color="primary" raised @click="updateIntegratedPaymentList">승인</VButton> <VButton type="submit" color="primary" raised @click="updateIntegratedPaymentApprovalFunc">승인</VButton>
</template> </template>
</VModal> </VModal>
<!-- <VFlexTableWrapper :columns="params.flexWrapperColumn" :data="params.paymentParams">--> <!-- <VFlexTableWrapper :columns="params.flexWrapperColumn" :data="params.paymentParams">-->

View File

@@ -14,7 +14,6 @@ export async function getIntegratedApproval(params = {}) {
params, params,
headers: { sabun: params.sabun }, headers: { sabun: params.sabun },
}) })
console.log(result)
if (result.status === 200) { if (result.status === 200) {
return result.data return result.data
} }
@@ -42,7 +41,7 @@ console.log(result)
* @property {string} params.reason - 사유 (반려일시 상태코드 0400) * @property {string} params.reason - 사유 (반려일시 상태코드 0400)
* @returns * @returns
*/ */
export async function updateIntegratedApproval(params = {}) { export async function updateIntegratedStatus(params = {}) {
try { try {
const result = await axios.put(`/api/appr`, params, { const result = await axios.put(`/api/appr`, params, {
headers: { sabun: params.sabun }, headers: { sabun: params.sabun },
@@ -63,3 +62,34 @@ export async function updateIntegratedApproval(params = {}) {
} }
} }
/**
* 결재번호를 가격조사 번호로 변경
* @param {object} params
* @property {string} params.apprNo - 결재번호
* @property {string} params.apprOrd - 결재순서
* @property {string} params.sabun - 사번
* @property {string} params.apprStatCd - 결재상태코드
* @property {string} params.reason - 사유 (반려일시 상태코드 0400)
* @returns
*/
export async function conveterNo(params = {}) {
try {
const result = await axios.get(`/api/appr/conveter/${params.apprNo}`,{
headers: { sabun: params.sabun },
})
if (result.status === 200) {
return result.data
}
}
catch (e) {
if (e.response) {
if (e.response.status >= 500) {
throw new Error('서버 오류가 발생했습니다.')
} else if (e.response.status >= 400) {
throw new Error('잘못된 요청입니다.')
}
}
throw new Error(e.message)
}
}