Merge branch 'dev' of https://git.jetbrains.space/nalpari/q-cast-iii/qcast-front into dev
# Conflicts: # src/hooks/module/useTrestle.js
This commit is contained in:
commit
1666de3238
@ -34,11 +34,11 @@ export default function ImgLoad() {
|
|||||||
|
|
||||||
const handleModal = () => {
|
const handleModal = () => {
|
||||||
setFloorPlanState({ ...floorPlanState, refFileModalOpen: false, toggleRotate: false })
|
setFloorPlanState({ ...floorPlanState, refFileModalOpen: false, toggleRotate: false })
|
||||||
setCurrentCanvasPlan({
|
// setCurrentCanvasPlan({
|
||||||
...currentCanvasPlan,
|
// ...currentCanvasPlan,
|
||||||
bgImageName: refImage?.name ?? null,
|
// bgImageName: refImage?.name ?? null,
|
||||||
mapPositionAddress,
|
// mapPositionAddress,
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { selectedModuleState } from '@/store/selectedModuleOptions'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
import { stepUpListDataState } from '@/store/circuitTrestleAtom'
|
import { stepUpListDataState } from '@/store/circuitTrestleAtom'
|
||||||
|
import { useEstimate } from '@/hooks/useEstimate'
|
||||||
|
|
||||||
const ALLOCATION_TYPE = {
|
const ALLOCATION_TYPE = {
|
||||||
AUTO: 'auto',
|
AUTO: 'auto',
|
||||||
@ -31,6 +32,7 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
const { apply } = useTrestle()
|
const { apply } = useTrestle()
|
||||||
const { swalFire } = useSwal()
|
const { swalFire } = useSwal()
|
||||||
|
const { saveEstimate } = useEstimate()
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
|
|
||||||
const [makers, setMakers] = useRecoilState(makersState)
|
const [makers, setMakers] = useRecoilState(makersState)
|
||||||
@ -336,6 +338,9 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await apply()
|
const result = await apply()
|
||||||
|
if (result) {
|
||||||
|
await saveEstimate(result)
|
||||||
|
}
|
||||||
removeNotAllocationModules()
|
removeNotAllocationModules()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -131,12 +131,16 @@ export function useRefFiles() {
|
|||||||
// setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: currentCanvasPlan.id, mapPositionAddress: queryRef.current.value }))
|
// setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: currentCanvasPlan.id, mapPositionAddress: queryRef.current.value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배경 이미지 로드를 위한 세팅
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentBgImage) {
|
if (!currentBgImage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage)
|
console.log('🚀 ~ useEffect ~ currentBgImage:', currentBgImage)
|
||||||
handleBackImageLoadToCanvas(`plan-images/${currentCanvasPlan.id}.png`)
|
// handleBackImageLoadToCanvas(`plan-images/${currentCanvasPlan.id}.png`)
|
||||||
|
handleBackImageLoadToCanvas(currentBgImage)
|
||||||
setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: refImage?.name ?? null, mapPositionAddress: queryRef.current.value }))
|
setCurrentCanvasPlan((prev) => ({ ...prev, bgImageName: refImage?.name ?? null, mapPositionAddress: queryRef.current.value }))
|
||||||
}, [currentBgImage])
|
}, [currentBgImage])
|
||||||
|
|
||||||
@ -147,15 +151,15 @@ export function useRefFiles() {
|
|||||||
const handleUploadImageRefFile = async (file) => {
|
const handleUploadImageRefFile = async (file) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
formData.append('fileName', currentCanvasPlan.id)
|
// formData.append('fileName', currentCanvasPlan.id)
|
||||||
|
|
||||||
// const res = await post({ url: `${process.env.NEXT_PUBLIC_API_SERVER_PATH}/api/image-upload`, data: formData })
|
// const res = await post({ url: `${process.env.NEXT_PUBLIC_API_SERVER_PATH}/api/image-upload`, data: formData })
|
||||||
const res = await post({ url: `http://localhost:3000/api/image-upload`, data: formData })
|
const res = await post({ url: `${process.env.NEXT_PUBLIC_HOST_URL}/image/upload`, data: formData })
|
||||||
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
|
console.log('🚀 ~ handleUploadImageRefFile ~ res:', res)
|
||||||
const image = await readImage(res.fileNm)
|
// const image = await readImage(res.filePath)
|
||||||
console.log('🚀 ~ handleUploadImageRefFile ~ file:', image)
|
// console.log('🚀 ~ handleUploadImageRefFile ~ file:', image)
|
||||||
|
|
||||||
setCurrentBgImage(image)
|
setCurrentBgImage(`${process.env.NEXT_PUBLIC_HOST_URL}${res.filePath}`)
|
||||||
setRefImage(file)
|
setRefImage(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -258,6 +258,8 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
|
|
||||||
//설치 범위 지정 클릭 이벤트
|
//설치 범위 지정 클릭 이벤트
|
||||||
const toggleSelection = (setupSurface) => {
|
const toggleSelection = (setupSurface) => {
|
||||||
|
console.log('setupSurface', setupSurface)
|
||||||
|
|
||||||
const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId)
|
const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId)
|
||||||
//최초 선택일때
|
//최초 선택일때
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
@ -442,12 +444,12 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
|
|
||||||
let intvHor =
|
let intvHor =
|
||||||
flowDirection === 'south' || flowDirection === 'north'
|
flowDirection === 'south' || flowDirection === 'north'
|
||||||
? moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor
|
? moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor / 10
|
||||||
: moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer
|
: moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer / 10
|
||||||
let intvVer =
|
let intvVer =
|
||||||
flowDirection === 'south' || flowDirection === 'north'
|
flowDirection === 'south' || flowDirection === 'north'
|
||||||
? moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer
|
? moduleSetupSurfaces[i].trestleDetail.moduleIntvlVer / 10
|
||||||
: moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor
|
: moduleSetupSurfaces[i].trestleDetail.moduleIntvlHor / 10
|
||||||
|
|
||||||
const trestleLeft = moduleSetupSurfaces[i].left
|
const trestleLeft = moduleSetupSurfaces[i].left
|
||||||
const trestleTop = moduleSetupSurfaces[i].top
|
const trestleTop = moduleSetupSurfaces[i].top
|
||||||
@ -667,8 +669,6 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('placementRef', placementRef)
|
|
||||||
|
|
||||||
const isChidori = placementRef.isChidori.current === 'true' ? true : false
|
const isChidori = placementRef.isChidori.current === 'true' ? true : false
|
||||||
const setupLocation = placementRef.setupLocation.current
|
const setupLocation = placementRef.setupLocation.current
|
||||||
const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false
|
const isMaxSetup = placementRef.isMaxSetup.current === 'true' ? true : false
|
||||||
@ -1262,16 +1262,14 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
|
|
||||||
const flowDirection = moduleSetupSurface.flowDirection
|
const flowDirection = moduleSetupSurface.flowDirection
|
||||||
|
|
||||||
console.log('moduleSetupSurface', moduleSetupSurface)
|
|
||||||
|
|
||||||
let intvHor =
|
let intvHor =
|
||||||
flowDirection === 'south' || flowDirection === 'north'
|
flowDirection === 'south' || flowDirection === 'north'
|
||||||
? moduleSetupSurface.trestleDetail.moduleIntvlHor
|
? moduleSetupSurface.trestleDetail.moduleIntvlHor / 10
|
||||||
: moduleSetupSurface.trestleDetail.moduleIntvlVer
|
: moduleSetupSurface.trestleDetail.moduleIntvlVer / 10
|
||||||
let intvVer =
|
let intvVer =
|
||||||
flowDirection === 'south' || flowDirection === 'north'
|
flowDirection === 'south' || flowDirection === 'north'
|
||||||
? moduleSetupSurface.trestleDetail.moduleIntvlVer
|
? moduleSetupSurface.trestleDetail.moduleIntvlVer / 10
|
||||||
: moduleSetupSurface.trestleDetail.moduleIntvlHor
|
: moduleSetupSurface.trestleDetail.moduleIntvlHor / 10
|
||||||
|
|
||||||
//처마면 배치
|
//처마면 배치
|
||||||
if (setupLocation === 'eaves') {
|
if (setupLocation === 'eaves') {
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { canvasState, currentAngleTypeSelector } from '@/store/canvasAtom'
|
import { canvasState, currentAngleTypeSelector } from '@/store/canvasAtom'
|
||||||
import { POLYGON_TYPE } from '@/common/common'
|
import { POLYGON_TYPE } from '@/common/common'
|
||||||
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
|
import { moduleSelectionDataState } from '@/store/selectedModuleOptions'
|
||||||
import { getDegreeByChon, getTrestleLength } from '@/util/canvas-util'
|
import { getDegreeByChon, getTrestleLength } from '@/util/canvas-util'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useMasterController } from '@/hooks/common/useMasterController'
|
import { useMasterController } from '@/hooks/common/useMasterController'
|
||||||
import { estimateParamAtom } from '@/store/estimateAtom'
|
|
||||||
|
|
||||||
// 회로 및 가대설정
|
// 회로 및 가대설정
|
||||||
export const useTrestle = () => {
|
export const useTrestle = () => {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const moduleSelectionData = useRecoilValue(moduleSelectionDataState) //다음으로 넘어가는 최종 데이터
|
const moduleSelectionData = useRecoilValue(moduleSelectionDataState) //다음으로 넘어가는 최종 데이터
|
||||||
const { getQuotationItem } = useMasterController()
|
const { getQuotationItem } = useMasterController()
|
||||||
const [estimateParam, setEstimateParam] = useRecoilState(estimateParamAtom)
|
|
||||||
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
||||||
|
|
||||||
const apply = () => {
|
const apply = () => {
|
||||||
@ -583,8 +581,7 @@ export const useTrestle = () => {
|
|||||||
|
|
||||||
return setEstimateData()
|
return setEstimateData()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
return null
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,7 +613,7 @@ export const useTrestle = () => {
|
|||||||
//견적서 itemList 조회
|
//견적서 itemList 조회
|
||||||
const res = await getQuotationItem(params)
|
const res = await getQuotationItem(params)
|
||||||
if (!res.data) {
|
if (!res.data) {
|
||||||
return false
|
return null
|
||||||
}
|
}
|
||||||
const itemList = res.data
|
const itemList = res.data
|
||||||
//northArrangement 북면 설치 여부
|
//northArrangement 북면 설치 여부
|
||||||
@ -676,10 +673,10 @@ export const useTrestle = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setEstimateParam({ ...estimateParam, itemList, northArrangement, roofSurfaceList, circuitItemList })
|
const estimateParam = { itemList, northArrangement, roofSurfaceList, circuitItemList }
|
||||||
|
|
||||||
// 정상적으로 완료 되면 true 반환
|
// 정상적으로 완료 되면 true 반환
|
||||||
return true
|
return estimateParam
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNorthArrangement = () => {
|
const getNorthArrangement = () => {
|
||||||
|
|||||||
@ -637,7 +637,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|||||||
y: pointer.y + length3 / 2,
|
y: pointer.y + length3 / 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
x: pointer.x - length1 / 2 + length4 * Math.cos(angle),
|
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)),
|
||||||
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
|
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
59
src/hooks/useEstimate.js
Normal file
59
src/hooks/useEstimate.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useContext } from 'react'
|
||||||
|
|
||||||
|
import { useRecoilValue } from 'recoil'
|
||||||
|
|
||||||
|
import { useAxios } from '@/hooks/useAxios'
|
||||||
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
|
import { GlobalDataContext } from '@/app/GlobalDataProvider'
|
||||||
|
import { currentCanvasPlanState } from '@/store/canvasAtom'
|
||||||
|
import { loginUserStore } from '@/store/commonAtom'
|
||||||
|
|
||||||
|
export function useEstimate() {
|
||||||
|
const { managementStateLoaded } = useContext(GlobalDataContext)
|
||||||
|
|
||||||
|
const loginUserState = useRecoilValue(loginUserStore)
|
||||||
|
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
||||||
|
|
||||||
|
const { swalFire } = useSwal()
|
||||||
|
const { promisePost } = useAxios()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 도면 견적서 저장
|
||||||
|
*/
|
||||||
|
const saveEstimate = async (estimateParam) => {
|
||||||
|
const userId = loginUserState.userId
|
||||||
|
const saleStoreId = managementStateLoaded.saleStoreId
|
||||||
|
const objectNo = currentCanvasPlan.objectNo
|
||||||
|
const planNo = currentCanvasPlan.planNo
|
||||||
|
const slope = estimateParam.roofSurfaceList[0].slope
|
||||||
|
const angle = estimateParam.roofSurfaceList[0].angle
|
||||||
|
const surfaceType = managementStateLoaded.surfaceType
|
||||||
|
const setupHeight = managementStateLoaded.installHeight
|
||||||
|
const standardWindSpeedId = managementStateLoaded.standardWindSpeedId
|
||||||
|
const snowfall = managementStateLoaded.verticalSnowCover
|
||||||
|
const drawingFlg = '1'
|
||||||
|
|
||||||
|
const saveEstimateData = {
|
||||||
|
...estimateParam,
|
||||||
|
userId: userId,
|
||||||
|
saleStoreId: saleStoreId,
|
||||||
|
objectNo: objectNo,
|
||||||
|
planNo: planNo,
|
||||||
|
slope: slope,
|
||||||
|
angle: angle,
|
||||||
|
surfaceType: surfaceType,
|
||||||
|
setupHeight: setupHeight,
|
||||||
|
standardWindSpeedId: standardWindSpeedId,
|
||||||
|
snowfall: snowfall,
|
||||||
|
drawingFlg: drawingFlg,
|
||||||
|
}
|
||||||
|
|
||||||
|
await promisePost({ url: '/api/estimate/save-estimate', data: saveEstimateData }).catch((error) => {
|
||||||
|
swalFire({ text: error.message, icon: 'error' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
saveEstimate,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -394,6 +394,7 @@ export function usePlan(params = {}) {
|
|||||||
canvas,
|
canvas,
|
||||||
plans,
|
plans,
|
||||||
currentCanvasPlan,
|
currentCanvasPlan,
|
||||||
|
setCurrentCanvasPlan,
|
||||||
selectedPlan,
|
selectedPlan,
|
||||||
saveCanvas,
|
saveCanvas,
|
||||||
handleCurrentPlan,
|
handleCurrentPlan,
|
||||||
|
|||||||
@ -72,7 +72,8 @@ const writeImage = async (fileName, file) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const readImage = async (fileName) => {
|
const readImage = async (fileName) => {
|
||||||
const file = await fs.readFile(`${FILE_PATH}/${fileName}`)
|
// const file = await fs.readFile(`${FILE_PATH}/${fileName}`)
|
||||||
|
const file = await fs.readFile(`${process.env.NEXT_PUBLIC_HOST_URL}${fileName}`)
|
||||||
// .then((res) => {
|
// .then((res) => {
|
||||||
// console.log('readImage-then', res)
|
// console.log('readImage-then', res)
|
||||||
// })
|
// })
|
||||||
|
|||||||
@ -877,6 +877,7 @@
|
|||||||
"estimate.detail.roofCns": "屋根材・仕様施工",
|
"estimate.detail.roofCns": "屋根材・仕様施工",
|
||||||
"estimate.detail.remarks": "備考",
|
"estimate.detail.remarks": "備考",
|
||||||
"estimate.detail.fileFlg": "後日資料提出",
|
"estimate.detail.fileFlg": "後日資料提出",
|
||||||
|
"estimate.detail.dragFileGuide": "(※北面設置の場合、ファイル添付が必須です.)",
|
||||||
"estimate.detail.header.fileList1": "ファイル添付",
|
"estimate.detail.header.fileList1": "ファイル添付",
|
||||||
"estimate.detail.fileList.btn": "ファイル選択",
|
"estimate.detail.fileList.btn": "ファイル選択",
|
||||||
"estimate.detail.fileList.extCheck": "画像ファイルのみ添付可能です。",
|
"estimate.detail.fileList.extCheck": "画像ファイルのみ添付可能です。",
|
||||||
@ -942,6 +943,7 @@
|
|||||||
"estimate.detail.save.alertMsg": "保存されました。見積書で製品を変更すると、図面や回路には反映されません。",
|
"estimate.detail.save.alertMsg": "保存されました。見積書で製品を変更すると、図面や回路には反映されません。",
|
||||||
"estimate.detail.copy.alertMsg": "コピーされました。",
|
"estimate.detail.copy.alertMsg": "コピーされました。",
|
||||||
"estimate.detail.save.requiredFileUpload": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください。",
|
"estimate.detail.save.requiredFileUpload": "ファイル添付が必須のアイテムがあります。ファイルを添付するか、後日添付をチェックしてください。",
|
||||||
|
"estimate.detail.save.requiredNorthArrangementFileUpload": "北面にモジュールを配置した場合、北面配置許可書を必ず添付する必要があります.",
|
||||||
"estimate.detail.save.requiredItem": "製品は1つ以上登録する必要があります。",
|
"estimate.detail.save.requiredItem": "製品は1つ以上登録する必要があります。",
|
||||||
"estimate.detail.save.requiredCharger": "担当者は必須です。",
|
"estimate.detail.save.requiredCharger": "担当者は必須です。",
|
||||||
"estimate.detail.save.requiredObjectName": "案件名は必須です。",
|
"estimate.detail.save.requiredObjectName": "案件名は必須です。",
|
||||||
|
|||||||
@ -877,6 +877,7 @@
|
|||||||
"estimate.detail.roofCns": "지붕재・사양시공",
|
"estimate.detail.roofCns": "지붕재・사양시공",
|
||||||
"estimate.detail.remarks": "비고",
|
"estimate.detail.remarks": "비고",
|
||||||
"estimate.detail.fileFlg": "후일자료제출",
|
"estimate.detail.fileFlg": "후일자료제출",
|
||||||
|
"estimate.detail.dragFileGuide": "(※ 북면설치인 경우, 파일 첨부가 필수입니다.)",
|
||||||
"estimate.detail.header.fileList1": "파일첨부",
|
"estimate.detail.header.fileList1": "파일첨부",
|
||||||
"estimate.detail.fileList.btn": "파일선택",
|
"estimate.detail.fileList.btn": "파일선택",
|
||||||
"estimate.detail.fileList.extCheck": "이미지 파일만 첨부 가능합니다.",
|
"estimate.detail.fileList.extCheck": "이미지 파일만 첨부 가능합니다.",
|
||||||
@ -942,6 +943,7 @@
|
|||||||
"estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우 도면 및 회로에 반영되지 않습니다.",
|
"estimate.detail.save.alertMsg": "저장되었습니다. 견적서에서 제품을 변경할 경우 도면 및 회로에 반영되지 않습니다.",
|
||||||
"estimate.detail.copy.alertMsg": "복사되었습니다.",
|
"estimate.detail.copy.alertMsg": "복사되었습니다.",
|
||||||
"estimate.detail.save.requiredFileUpload": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
|
"estimate.detail.save.requiredFileUpload": "파일첨부가 필수인 아이템이 있습니다. 파일을 첨부하거나 후일첨부를 체크해주십시오.",
|
||||||
|
"estimate.detail.save.requiredNorthArrangementFileUpload": "북면에 모듈을 배치한 경우, 북면배치허가서를 반드시 첨부해야 합니다.",
|
||||||
"estimate.detail.save.requiredItem": "제품은 1개이상 등록해야 됩니다.",
|
"estimate.detail.save.requiredItem": "제품은 1개이상 등록해야 됩니다.",
|
||||||
"estimate.detail.save.requiredCharger": "담당자는 필수값 입니다.",
|
"estimate.detail.save.requiredCharger": "담당자는 필수값 입니다.",
|
||||||
"estimate.detail.save.requiredObjectName": "안건명은 필수값 입니다.",
|
"estimate.detail.save.requiredObjectName": "안건명은 필수값 입니다.",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user