This commit is contained in:
hyojun.choi 2025-02-20 16:33:05 +09:00
commit 066cd03dc1
10 changed files with 174 additions and 61 deletions

View File

@ -296,7 +296,7 @@ export default function EstimateCopyPop({ planNo, setEstimateCopyPopupOpen }) {
type="button" type="button"
className="btn-origin navy mr5" className="btn-origin navy mr5"
onClick={() => { onClick={() => {
handleEstimateCopy(sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId) handleEstimateCopy(sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId, setEstimateCopyPopupOpen)
}} }}
> >
{getMessage('estimate.detail.estimateCopyPopup.copyBtn')} {getMessage('estimate.detail.estimateCopyPopup.copyBtn')}

View File

@ -221,6 +221,7 @@ export default function CanvasMenu(props) {
await reloadCanvasStatus(objectNo, pid) await reloadCanvasStatus(objectNo, pid)
break break
case 5: case 5:
setIsGlobalLoading(true)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
if (res.status === 200) { if (res.status === 200) {
const estimateDetail = res.data const estimateDetail = res.data
@ -230,6 +231,9 @@ export default function CanvasMenu(props) {
setFloorPlanObjectNo({ floorPlanObjectNo: objectNo }) setFloorPlanObjectNo({ floorPlanObjectNo: objectNo })
setIsGlobalLoading(false) setIsGlobalLoading(false)
router.push(`/floor-plan/estimate/${menu.index}?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) router.push(`/floor-plan/estimate/${menu.index}?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
if (pathname === '/floor-plan/estimate/5') {
setIsGlobalLoading(false)
}
} else { } else {
setIsGlobalLoading(false) setIsGlobalLoading(false)
swalFire({ text: getMessage('estimate.menu.move.valid1') }) swalFire({ text: getMessage('estimate.menu.move.valid1') })
@ -239,7 +243,6 @@ export default function CanvasMenu(props) {
break break
case 6: case 6:
setIsGlobalLoading(true) setIsGlobalLoading(true)
// (Simulator.jsx) setIsGlobalLoading(false)
promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => { promiseGet({ url: `/api/estimate/${objectNo}/${selectedPlan.planNo}/detail` }).then((res) => {
if (res.status === 200) { if (res.status === 200) {
const estimateDetail = res.data const estimateDetail = res.data
@ -247,7 +250,9 @@ export default function CanvasMenu(props) {
setMenuNumber(menu.index) setMenuNumber(menu.index)
setCurrentMenu(menu.title) setCurrentMenu(menu.title)
router.push(`/floor-plan/simulator/${menu.index}?pid=${selectedPlan.planNo}&objectNo=${objectNo}`) router.push(`/floor-plan/simulator/${menu.index}?pid=${selectedPlan.planNo}&objectNo=${objectNo}`)
setIsGlobalLoading(false) if (pathname === '/floor-plan/simulator/6') {
setIsGlobalLoading(false)
}
} else { } else {
setIsGlobalLoading(false) setIsGlobalLoading(false)
swalFire({ text: getMessage('simulator.menu.move.valid1') }) swalFire({ text: getMessage('simulator.menu.move.valid1') })

View File

@ -7,10 +7,14 @@ import { LINE_TYPE } from '@/common/common'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
export default function PlacementSurfaceLineProperty(props) { export default function PlacementSurfaceLineProperty(props) {
const { id, pos = { x: 50, y: 230 }, roof } = props const { id, pos = { x: 50, y: 230 }, roof, setIsHidden } = props
const { closePopup } = usePopup() const { closePopup } = usePopup()
// const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting(id) // const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting(id)
const { roofLinesInit, handleSetRidge, handleSetEaves, handleSetGable, handleRollback, handleFix } = useRoofLinePropertySetting(id, roof) const { roofLinesInit, handleSetRidge, handleSetEaves, handleSetGable, handleRollback, handleFix } = useRoofLinePropertySetting({
id,
roof,
setIsHidden,
})
const { getMessage } = useMessage() const { getMessage } = useMessage()
const { swalFire } = useSwal() const { swalFire } = useSwal()
@ -29,6 +33,10 @@ export default function PlacementSurfaceLineProperty(props) {
} }
closePopup(id) closePopup(id)
if (setIsHidden) {
setIsHidden(false)
}
} }
return ( return (

View File

@ -83,6 +83,10 @@ export const useEstimateController = (planNo, flag) => {
res.data.pkgAsp = roundedNumber.toString() res.data.pkgAsp = roundedNumber.toString()
} }
setEstimateContextState(res.data) setEstimateContextState(res.data)
} else {
swalFire({ text: getMessage('stuff.detail.header.notExistObjectNo'), type: 'alert', icon: 'error' })
setIsLoading(true)
setIsGlobalLoading(false)
} }
} }
}) })
@ -90,8 +94,7 @@ export const useEstimateController = (planNo, flag) => {
setIsGlobalLoading(false) setIsGlobalLoading(false)
} catch (error) { } catch (error) {
console.error('견적서 상세조회 Error: ', error) console.error('견적서 상세조회 Error: ', error)
swalFire({ text: getMessage('stuff.detail.header.notExistObjectNo'), type: 'alert', icon: 'error' })
swalFire({ text: getMessage('estimate.menu.move.valid1') })
setIsLoading(true) setIsLoading(true)
setIsGlobalLoading(false) setIsGlobalLoading(false)
} }
@ -415,7 +418,7 @@ export const useEstimateController = (planNo, flag) => {
* (견적서 번호(estimateData.docNo) 생성된 이후 버튼 활성화 ) * (견적서 번호(estimateData.docNo) 생성된 이후 버튼 활성화 )
* T01관리자 계정 1차판매점에게만 제공 * T01관리자 계정 1차판매점에게만 제공
*/ */
const handleEstimateCopy = async (sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId) => { const handleEstimateCopy = async (sendPlanNo, copyReceiveUser, saleStoreId, otherSaleStoreId, setEstimateCopyPopupOpen) => {
//todo: 추후 YJSS가 다시 나타날 경우 아래 swalFire 제거 필요 //todo: 추후 YJSS가 다시 나타날 경우 아래 swalFire 제거 필요
if (estimateData.estimateType === 'YJSS') { if (estimateData.estimateType === 'YJSS') {
return swalFire({ text: getMessage('estimate.detail.save.requiredEstimateType'), type: 'alert', icon: 'warning' }) return swalFire({ text: getMessage('estimate.detail.save.requiredEstimateType'), type: 'alert', icon: 'warning' })
@ -455,6 +458,7 @@ export const useEstimateController = (planNo, flag) => {
text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'), text: getMessage('estimate.detail.estimateCopyPopup.copy.alertMessage'),
type: 'alert', type: 'alert',
confirmFn: () => { confirmFn: () => {
setEstimateCopyPopupOpen(false) //팝업닫고
router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false }) router.push(`/management/stuff/detail?objectNo=${newObjectNo.toString()}`, { scroll: false })
}, },
}) })

View File

@ -501,6 +501,67 @@ export function useCanvasSetting() {
} }
} }
/**
* 기본설정(PlacementShapeSetting) 복사 저장
*/
const basicSettingCopySave = async (params) => {
try {
const patternData = {
objectNo: correntObjectNo,
planNo: Number(params.planNo),
roofSizeSet: Number(params.roofSizeSet),
roofAngleSet: params.roofAngleSet,
roofMaterialsAddList: params.roofsData.map((item) => ({
planNo: Number(item.planNo),
roofApply: item.roofApply,
roofSeq: item.roofSeq,
roofMatlCd: item.roofMatlCd,
roofWidth: item.roofWidth,
roofHeight: item.roofHeight,
roofHajebichi: item.roofHajebichi,
roofGap: item.roofGap,
roofLayout: item.roofLayout,
roofPitch: item.roofPitch,
roofAngle: item.roofAngle,
})),
}
await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }).then((res) => {
swalFire({ text: getMessage(res.returnMessage) })
})
/* CanvasSetting Recoil 설정 - roofSizeSet을 문자열로 변환 */
setCanvasSetting({
...basicSetting,
roofSizeSet: String(params.roofSizeSet),
})
/* 배치면초기설정 조회 */
fetchBasicSettings(Number(params.planNo), null)
/* 메뉴 설정 */
if (['2', '3'].includes(params?.roofSizeSet)) {
setMenuNumber(3)
setType('surface')
setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING)
} else {
setMenuNumber(2)
setType('outline')
setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE)
}
/* 모듈 선택 데이터 초기화 */
resetModuleSelectionData()
moduleSelectedDataTrigger({ common: {}, module: {}, roofConstructions: [] })
const isModuleExist = canvas.getObjects().some((obj) => obj.name === POLYGON_TYPE.MODULE)
if (!isModuleExist) {
resetSelectedModules()
}
} catch (error) {
swalFire({ text: error.message, icon: 'error' })
}
}
/** /**
* CanvasSetting 조회 초기화 * CanvasSetting 조회 초기화
*/ */
@ -915,6 +976,7 @@ export function useCanvasSetting() {
setAdsorptionRange, setAdsorptionRange,
fetchSettings, fetchSettings,
fetchBasicSettings, fetchBasicSettings,
basicSettingCopySave,
frontSettings, frontSettings,
globalFont, globalFont,
setGlobalFont, setGlobalFont,

View File

@ -15,7 +15,8 @@ const LINE_COLOR = {
ACTIVE: '#EA10AC', ACTIVE: '#EA10AC',
} }
export function useRoofLinePropertySetting(id, roof) { export function useRoofLinePropertySetting(props) {
const { id, roof, setIsHidden } = props
const canvas = useRecoilValue(canvasState) const canvas = useRecoilValue(canvasState)
const currentObject = useRecoilValue(currentObjectState) const currentObject = useRecoilValue(currentObjectState)
const history = useRef([]) const history = useRef([])
@ -25,10 +26,20 @@ export function useRoofLinePropertySetting(id, roof) {
useEffect(() => { useEffect(() => {
if (currentObject && currentObject.name === 'roofLine') { if (currentObject && currentObject.name === 'roofLine') {
roof.lines.forEach((line) => {
const lineType = line.attributes?.type
if (!lineType) {
line.set({
stroke: '#000000',
strokeWidth: 4,
})
}
})
currentObject.set({ currentObject.set({
stroke: LINE_COLOR.ACTIVE, stroke: LINE_COLOR.ACTIVE,
strokeWidth: 4, strokeWidth: 4,
}) })
canvas.renderAll()
} }
}, [currentObject]) }, [currentObject])
@ -97,11 +108,8 @@ export function useRoofLinePropertySetting(id, roof) {
} }
const lastLine = history.current.pop() const lastLine = history.current.pop()
// delete lastLine.attributes delete lastLine.attributes
lastLine.attributes = {
...lastLine.attributes,
type: null,
}
lastLine.set({ lastLine.set({
stroke: LINE_COLOR.DEFAULT, stroke: LINE_COLOR.DEFAULT,
strokeWidth: 4, strokeWidth: 4,
@ -132,6 +140,7 @@ export function useRoofLinePropertySetting(id, roof) {
canvas.renderAll() canvas.renderAll()
closePopup(id) closePopup(id)
if (setIsHidden) setIsHidden(false)
} }
const nextLineFocus = (selectedLine) => { const nextLineFocus = (selectedLine) => {

View File

@ -19,6 +19,8 @@ import { QLine } from '@/components/fabric/QLine'
import { useRoofFn } from '@/hooks/common/useRoofFn' import { useRoofFn } from '@/hooks/common/useRoofFn'
import { outerLinePointsState } from '@/store/outerLineAtom' import { outerLinePointsState } from '@/store/outerLineAtom'
import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom' import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom'
import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placementShape/PlacementSurfaceLineProperty'
import { v4 as uuidv4 } from 'uuid'
export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const { getMessage } = useMessage() const { getMessage } = useMessage()
@ -34,7 +36,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
const { swalFire } = useSwal() const { swalFire } = useSwal()
const { addCanvasMouseEventListener, initEvent } = useEvent() const { addCanvasMouseEventListener, initEvent } = useEvent()
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext) // const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
const { closePopup } = usePopup() const { addPopup, closePopup } = usePopup()
const { setSurfaceShapePattern } = useRoofFn() const { setSurfaceShapePattern } = useRoofFn()
const applySurfaceShape = (surfaceRefs, selectedType, id) => { const applySurfaceShape = (surfaceRefs, selectedType, id) => {
@ -113,9 +115,13 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
let imageRotate = 0 let imageRotate = 0
if (xInversion && !yInversion) { if (xInversion && !yInversion) {
if (rotate % 180 === 0 || rotate < 0) { if (rotate % 180 === 0 || rotate < 0) {
imageRotate = Math.abs((rotate - 180) % 360) imageRotate = Math.abs(rotate % 360)
} else { } else {
imageRotate = Math.abs((rotate + 180) % 4) if (rotate < 0) {
imageRotate = Math.abs((rotate - 180) % 360)
} else {
imageRotate = Math.abs((rotate + 180) % 360)
}
} }
} else if (xInversion && yInversion) { } else if (xInversion && yInversion) {
imageRotate = Math.abs((rotate + 360) % 360) imageRotate = Math.abs((rotate + 360) % 360)
@ -132,7 +138,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
} }
} }
} else { } else {
imageRotate = (rotate + 360) % 360 imageRotate = (rotate + 4) % 4
} }
obj.set({ angle: imageRotate }) obj.set({ angle: imageRotate })
obj.setCoords() //좌표 변경 적용 obj.setCoords() //좌표 변경 적용
@ -188,10 +194,12 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
setSurfaceShapePattern(batchSurface, roofDisplay.column) setSurfaceShapePattern(batchSurface, roofDisplay.column)
drawDirectionArrow(batchSurface) drawDirectionArrow(batchSurface)
if (setIsHidden) setIsHidden(false) // if (setIsHidden) setIsHidden(false)
// closePopup(id) // closePopup(id)
initEvent() initEvent()
const popupId = uuidv4()
addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
}) })
} else { } else {
if (setIsHidden) setIsHidden(false) if (setIsHidden) setIsHidden(false)

View File

@ -5,7 +5,7 @@ import { usePathname, useRouter } from 'next/navigation'
import { useRecoilState, useResetRecoilState } from 'recoil' import { useRecoilState, useResetRecoilState } from 'recoil'
import { canvasState, currentCanvasPlanState, plansState } from '@/store/canvasAtom' import { canvasState, currentCanvasPlanState, plansState, canvasSettingState } from '@/store/canvasAtom'
import { useAxios } from '@/hooks/useAxios' import { useAxios } from '@/hooks/useAxios'
import { useMessage } from '@/hooks/useMessage' import { useMessage } from '@/hooks/useMessage'
import { useSwal } from '@/hooks/useSwal' import { useSwal } from '@/hooks/useSwal'
@ -39,7 +39,8 @@ export function usePlan(params = {}) {
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState) const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState) const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
const { fetchBasicSettings } = useCanvasSetting() const { fetchBasicSettings, basicSettingCopySave } = useCanvasSetting()
const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState)
/** /**
* 마우스 포인터의 가이드라인 제거 * 마우스 포인터의 가이드라인 제거
@ -184,41 +185,55 @@ export function usePlan(params = {}) {
objectNo: objectNo, objectNo: objectNo,
copyFlg: '0', copyFlg: '0',
} }
await promisePost({ url: '/api/object/add-plan', data: planData }) try {
.then((res) => { const res = await promisePost({ url: '/api/object/add-plan', data: planData })
let newPlan = { let newPlan = {
id: res.data.canvasId, id: res.data.canvasId,
objectNo: objectNo, objectNo: objectNo,
planNo: res.data.planNo, planNo: res.data.planNo,
userId: userId, userId: userId,
canvasStatus: '', canvasStatus: '',
isCurrent: true, isCurrent: true,
bgImageName: null, bgImageName: null,
mapPositionAddress: null, mapPositionAddress: null,
} }
if (isInitPlan) { if (isInitPlan) {
/* 초기 플랜 생성인 경우 플랜 목록 초기화 */ /* 초기 플랜 생성인 경우 플랜 목록 초기화 */
setCurrentCanvasPlan(newPlan) setCurrentCanvasPlan(newPlan)
setPlans([newPlan]) setPlans([newPlan])
} else {
if (isCopy) {
/* 복제 플랜 생성인 경우 현재 캔버스 데이터를 복제 */
newPlan.canvasStatus = currentCanvasData()
newPlan.bgImageName = currentCanvasPlan?.bgImageName ?? null
newPlan.mapPositionAddress = currentCanvasPlan?.mapPositionAddress ?? null
}
setCurrentCanvasPlan(newPlan)
setPlans((plans) => [...plans.map((plan) => ({ ...plan, isCurrent: false })), newPlan])
swalFire({ text: getMessage('plan.message.save') })
}
/* 플랜 추가 시 배치면초기설정 정보 조회 */ /* 플랜 추가 시 배치면초기설정 정보 조회 */
fetchBasicSettings(newPlan.planNo, null) fetchBasicSettings(newPlan.planNo, null)
}) } else {
.catch((error) => { if (isCopy) {
swalFire({ text: error.response.data.message, icon: 'error' }) /* 복제 플랜 생성인 경우 현재 캔버스 데이터를 복제 */
}) newPlan.canvasStatus = currentCanvasData()
newPlan.bgImageName = currentCanvasPlan?.bgImageName ?? null
newPlan.mapPositionAddress = currentCanvasPlan?.mapPositionAddress ?? null
/* 복제 시 배치면 초기설정 복사 */
basicSettingCopySave({
...canvasSetting,
planNo: newPlan.planNo,
selectedRoofMaterial: {
...canvasSetting.selectedRoofMaterial,
planNo: newPlan.planNo,
},
roofsData: canvasSetting.roofsData.map((roof) => ({
...roof,
planNo: newPlan.planNo,
})),
})
}
setCurrentCanvasPlan(newPlan)
setPlans((plans) => [...plans.map((plan) => ({ ...plan, isCurrent: false })), newPlan])
swalFire({ text: getMessage('plan.message.save') })
}
} catch (error) {
swalFire({ text: error.response.data.message, icon: 'error' })
}
} }
/** /**
@ -275,7 +290,6 @@ export function usePlan(params = {}) {
const objectNo = floorPlanState.objectNo const objectNo = floorPlanState.objectNo
//견적서 or 발전시뮬 //견적서 or 발전시뮬
if (pathname !== '/floor-plan') { if (pathname !== '/floor-plan') {
await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` }) await promiseGet({ url: `/api/estimate/${objectNo}/${planNo}/detail` })
.then((res) => { .then((res) => {
@ -304,9 +318,6 @@ export function usePlan(params = {}) {
// 클릭한 플랜 탭으로 이동 // 클릭한 플랜 탭으로 이동
setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId)) setCurrentCanvasPlan(plans.find((plan) => plan.id === newCurrentId))
setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId }))) setPlans((plans) => plans.map((plan) => ({ ...plan, isCurrent: plan.id === newCurrentId })))
/* 플랜 이동 시 배치면초기설정 정보 조회 (견적서 메뉴 제외) */
fetchBasicSettings(planNo, null)
} else { } else {
swalFire({ text: getMessage('estimate.menu.move.valid1') }) swalFire({ text: getMessage('estimate.menu.move.valid1') })
} }
@ -494,7 +505,13 @@ export function usePlan(params = {}) {
* @param {string} planNo - 플랜번호 * @param {string} planNo - 플랜번호
*/ */
const deleteBasicSettings = async (objectNo, planNo) => { const deleteBasicSettings = async (objectNo, planNo) => {
await promiseDel({ url: `/api/canvas-management/canvas-basic-settings/delete-basic-settings/${objectNo}/${planNo}` }) try {
await promiseDel({ url: `/api/canvas-management/canvas-basic-settings/delete-basic-settings/${objectNo}/${planNo}` })
} catch (error) {
/* 오류를 무시하고 계속 진행 */
console.log('Basic settings delete failed or not found:', error)
// swalFire({ text: error.message, icon: 'error' })
}
} }
return { return {

View File

@ -663,9 +663,9 @@
"stuff.planReqPopup.title": "設計依頼のインポート", "stuff.planReqPopup.title": "設計依頼のインポート",
"stuff.temp.subTitle": "商品情報", "stuff.temp.subTitle": "商品情報",
"stuff.temp.subTitle2": "作図", "stuff.temp.subTitle2": "作図",
"stuff.detail.header.notExistObjectNo": "存在しないものです。", "stuff.detail.header.notExistObjectNo": "存在しないもの番号です.",
"stuff.detail.header.successCopy": "物件番号がコピーされました", "stuff.detail.header.successCopy": "物件番号がコピーされました.",
"stuff.detail.header.failCopy": "物件番号のコピーに失敗しました", "stuff.detail.header.failCopy": "物件番号のコピーに失敗しました.",
"stuff.detail.header.objectNo": "物件番号", "stuff.detail.header.objectNo": "物件番号",
"stuff.detail.header.specificationConfirmDate": "仕様確認日", "stuff.detail.header.specificationConfirmDate": "仕様確認日",
"stuff.detail.header.lastEditDatetime": "更新日時", "stuff.detail.header.lastEditDatetime": "更新日時",

View File

@ -663,7 +663,7 @@
"stuff.planReqPopup.title": "설계의뢰 불러오기", "stuff.planReqPopup.title": "설계의뢰 불러오기",
"stuff.temp.subTitle": "물건정보", "stuff.temp.subTitle": "물건정보",
"stuff.temp.subTitle2": "도면작성", "stuff.temp.subTitle2": "도면작성",
"stuff.detail.header.notExistObjectNo": "존재하지 않는 물건입니다.", "stuff.detail.header.notExistObjectNo": "존재하지 않는 물건번호 입니다.",
"stuff.detail.header.successCopy": "물건번호가 복사되었습니다.", "stuff.detail.header.successCopy": "물건번호가 복사되었습니다.",
"stuff.detail.header.failCopy": "물건번호 복사에 실패했습니다.", "stuff.detail.header.failCopy": "물건번호 복사에 실패했습니다.",
"stuff.detail.header.objectNo": "물건번호", "stuff.detail.header.objectNo": "물건번호",