Merge pull request '[DB] 컬럼 길이 가드 — addressee 합산 50자 + receiveUser 20자 클립' (#863) from dev into dev-deploy

Reviewed-on: #863
This commit is contained in:
ysCha 2026-05-18 15:15:11 +09:00
commit 609db77968
6 changed files with 42 additions and 14 deletions

View File

@ -1426,6 +1426,7 @@ export default function Estimate({}) {
<input <input
type="text" type="text"
className="input-light" className="input-light"
maxLength={50 - 1 - (estimateContextState?.objectNameOmit?.length ?? 0)}
value={estimateContextState?.objectName || ''} value={estimateContextState?.objectName || ''}
onBlur={handleBlurObjectName} onBlur={handleBlurObjectName}
onChange={handleBlurObjectName} onChange={handleBlurObjectName}

View File

@ -127,7 +127,8 @@ export default function EstimateCopyPop({ planNo, setEstimateCopyPopupOpen }) {
useEffect(() => { useEffect(() => {
if (estimateContextState?.charger) { if (estimateContextState?.charger) {
setCopyReceiveUser(session?.userNm) // [RECEIVEUSER-CLIP20 2026-05-18] T_PLAN.CHARGER nvarchar(20) 21+ session
setCopyReceiveUser(session?.userNm?.slice(0, 20))
} }
}, [estimateContextState?.charger]) }, [estimateContextState?.charger])
@ -311,7 +312,8 @@ export default function EstimateCopyPop({ planNo, setEstimateCopyPopupOpen }) {
type="text" type="text"
className="input-light" className="input-light"
required required
defaultValue={session?.userNm} maxLength={20}
defaultValue={session?.userNm?.slice(0, 20)}
onChange={(e) => { onChange={(e) => {
setCopyReceiveUser(e.target.value) setCopyReceiveUser(e.target.value)
}} }}

View File

@ -64,7 +64,7 @@ export default function StuffDetail() {
const formInitValue = { const formInitValue = {
// T...() S...() // T...() S...()
planReqNo: '', //No planReqNo: '', //No
receiveUser: session?.userNm, // receiveUser: session?.userNm?.slice(0, 20), // // [RECEIVEUSER-CLIP20 2026-05-18] DDL nvarchar(20) 21+ session
objectStatusId: '0', //(:0 : 1) objectStatusId: '0', //(:0 : 1)
objectName: '', // objectName: '', //
objectNameOmit: '', // objectNameOmit: '', //
@ -637,7 +637,8 @@ export default function StuffDetail() {
//No. //No.
form.setValue('planReqNo', managementState.planReqNo) form.setValue('planReqNo', managementState.planReqNo)
// //
form.setValue('receiveUser', managementState.receiveUser) // [RECEIVEUSER-CLIP20 2026-05-18] DDL nvarchar(20) 21+
form.setValue('receiveUser', managementState.receiveUser?.slice(0, 20))
//objectStatusId //objectStatusId
setSelectObjectStatusId(managementState.objectStatusId) setSelectObjectStatusId(managementState.objectStatusId)
@ -1123,6 +1124,9 @@ export default function StuffDetail() {
const _receiveUser = watch('receiveUser') const _receiveUser = watch('receiveUser')
//objectName: '', // //objectName: '', //
const _objectName = watch('objectName') const _objectName = watch('objectName')
// [NVARCHAR50-GUARD 2026-05-18] ADDRESSEE_COMPANY_NAME(nvarchar(50)) = objectName + ' ' + objectNameOmit maxLength
const _objectNameOmit = watch('objectNameOmit')
const objectNameMaxLength = Math.max(0, 50 - 1 - (_objectNameOmit?.length ?? 0))
// saleStoreId: '', //1 ID // saleStoreId: '', //1 ID
const _saleStoreId = watch('saleStoreId') const _saleStoreId = watch('saleStoreId')
// 2 // 2
@ -1359,6 +1363,11 @@ export default function StuffDetail() {
return swalFire({ text: getMessage('stuff.detail.save.valierror3', [errors]), type: 'alert', icon: 'warning' }) return swalFire({ text: getMessage('stuff.detail.save.valierror3', [errors]), type: 'alert', icon: 'warning' })
} }
// [NVARCHAR50-GUARD 2026-05-18] ADDRESSEE_COMPANY_NAME = objectName + ' ' + objectNameOmit (nvarchar(50))
if (formData.objectName && (formData.objectName + ' ' + (formData.objectNameOmit ?? '')).length > 50) {
return swalFire({ text: getMessage('stuff.detail.save.objectNameTooLong'), type: 'alert', icon: 'warning' })
}
const apiUrl = '/api/object/save-object' const apiUrl = '/api/object/save-object'
const params = { const params = {
@ -1595,6 +1604,11 @@ export default function StuffDetail() {
} }
} }
// [NVARCHAR50-GUARD 2026-05-18] ADDRESSEE_COMPANY_NAME = objectName + ' ' + objectNameOmit (nvarchar(50))
if (params.objectName && (params.objectName + ' ' + (params.objectNameOmit ?? '')).length > 50) {
return swalFire({ text: getMessage('stuff.detail.save.objectNameTooLong'), type: 'alert', icon: 'warning' })
}
const apiUrl = '/api/object/save-object' const apiUrl = '/api/object/save-object'
if (objectNo) { if (objectNo) {
setIsGlobalLoading(true) setIsGlobalLoading(true)
@ -1866,7 +1880,7 @@ export default function StuffDetail() {
</th> </th>
<td> <td>
<div className="input-wrap" style={{ width: '500px' }}> <div className="input-wrap" style={{ width: '500px' }}>
<input type="text" className="input-light" {...form.register('receiveUser')} /> <input type="text" className="input-light" maxLength={20} {...form.register('receiveUser')} />
</div> </div>
</td> </td>
</tr> </tr>
@ -1895,7 +1909,7 @@ export default function StuffDetail() {
})} })}
{/* 라디오끝 */} {/* 라디오끝 */}
<div className="input-wrap mr5" style={{ width: '545px' }}> <div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" maxLength={50} {...form.register('objectName')} /> <input type="text" className="input-light" maxLength={objectNameMaxLength} {...form.register('objectName')} />
</div> </div>
<div className="select-wrap" style={{ width: '120px' }}> <div className="select-wrap" style={{ width: '120px' }}>
<Select <Select
@ -1923,7 +1937,7 @@ export default function StuffDetail() {
<th>{getMessage('stuff.detail.objectNameKana')}</th> <th>{getMessage('stuff.detail.objectNameKana')}</th>
<td> <td>
<div className="input-wrap" style={{ width: '789px' }}> <div className="input-wrap" style={{ width: '789px' }}>
<input type="text" className="input-light" {...form.register('objectNameKana')} /> <input type="text" className="input-light" maxLength={50} {...form.register('objectNameKana')} />
</div> </div>
</td> </td>
</tr> </tr>
@ -2136,7 +2150,7 @@ export default function StuffDetail() {
)} )}
</div> </div>
<div className="input-wrap mr5" style={{ width: '580px' }}> <div className="input-wrap mr5" style={{ width: '580px' }}>
<input type="text" className="input-light" value={form.watch('address') || ''} {...form.register('address')} /> <input type="text" className="input-light" maxLength={255} value={form.watch('address') || ''} {...form.register('address')} />
</div> </div>
</div> </div>
</td> </td>
@ -2323,7 +2337,7 @@ export default function StuffDetail() {
<th>{getMessage('stuff.detail.remarks')}</th> <th>{getMessage('stuff.detail.remarks')}</th>
<td> <td>
<div className="input-wrap"> <div className="input-wrap">
<input type="text" className="input-light" {...form.register('remarks')} /> <input type="text" className="input-light" maxLength={500} {...form.register('remarks')} />
</div> </div>
</td> </td>
</tr> </tr>
@ -2458,7 +2472,7 @@ export default function StuffDetail() {
</th> </th>
<td> <td>
<div className="input-wrap" style={{ width: '500px' }}> <div className="input-wrap" style={{ width: '500px' }}>
<input type="text" className="input-light" {...form.register('receiveUser')} value={form.watch('receiveUser') || ''} /> <input type="text" className="input-light" maxLength={20} {...form.register('receiveUser')} value={form.watch('receiveUser') || ''} />
</div> </div>
</td> </td>
</tr> </tr>
@ -2487,7 +2501,7 @@ export default function StuffDetail() {
})} })}
{/* 상세라디오끝 */} {/* 상세라디오끝 */}
<div className="input-wrap mr5" style={{ width: '545px' }}> <div className="input-wrap mr5" style={{ width: '545px' }}>
<input type="text" className="input-light" maxLength={50} {...form.register('objectName')} /> <input type="text" className="input-light" maxLength={objectNameMaxLength} {...form.register('objectName')} />
</div> </div>
<div className="select-wrap" style={{ width: '120px' }}> <div className="select-wrap" style={{ width: '120px' }}>
<Select <Select
@ -2515,7 +2529,7 @@ export default function StuffDetail() {
<th>{getMessage('stuff.detail.objectNameKana')}</th> <th>{getMessage('stuff.detail.objectNameKana')}</th>
<td> <td>
<div className="input-wrap" style={{ width: '789px' }}> <div className="input-wrap" style={{ width: '789px' }}>
<input type="text" className="input-light" {...form.register('objectNameKana')} /> <input type="text" className="input-light" maxLength={50} {...form.register('objectNameKana')} />
</div> </div>
</td> </td>
</tr> </tr>
@ -2737,7 +2751,7 @@ export default function StuffDetail() {
)} )}
</div> </div>
<div className="input-wrap mr5" style={{ width: '580px' }}> <div className="input-wrap mr5" style={{ width: '580px' }}>
<input type="text" className="input-light" value={form.watch('address') || ''} {...form.register('address')} /> <input type="text" className="input-light" maxLength={255} value={form.watch('address') || ''} {...form.register('address')} />
</div> </div>
</div> </div>
</td> </td>
@ -2938,7 +2952,7 @@ export default function StuffDetail() {
<th>{getMessage('stuff.detail.remarks')}</th> <th>{getMessage('stuff.detail.remarks')}</th>
<td> <td>
<div className="input-wrap"> <div className="input-wrap">
<input type="text" className="input-light" {...form.register('remarks')} value={form.watch('remarks') || ''} /> <input type="text" className="input-light" maxLength={500} {...form.register('remarks')} value={form.watch('remarks') || ''} />
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -207,6 +207,13 @@ export const useEstimateController = (planNo, flag) => {
return swalFire({ text: getMessage('estimate.detail.save.requiredObjectName'), type: 'alert', icon: 'warning' }) return swalFire({ text: getMessage('estimate.detail.save.requiredObjectName'), type: 'alert', icon: 'warning' })
} }
// [NVARCHAR50-GUARD 2026-05-18] ADDRESSEE_COMPANY_NAME = objectName + ' ' + objectNameOmit (nvarchar(50)) 합산 가드
if ((estimateData.objectName + ' ' + (estimateData.objectNameOmit ?? '')).length > 50) {
flag = false
setIsGlobalLoading(false)
return swalFire({ text: getMessage('estimate.detail.save.objectNameTooLong'), type: 'alert', icon: 'warning' })
}
if (isNaN(Date.parse(estimateData.estimateDate))) { if (isNaN(Date.parse(estimateData.estimateDate))) {
flag = false flag = false
setIsGlobalLoading(false) setIsGlobalLoading(false)

View File

@ -801,6 +801,7 @@
"stuff.detail.save.valierror2": "設置高さ0より大きい値を入力してください", "stuff.detail.save.valierror2": "設置高さ0より大きい値を入力してください",
"stuff.detail.save.valierror3": "{0} 必須入力項目です。", "stuff.detail.save.valierror3": "{0} 必須入力項目です。",
"stuff.detail.save.valierror4": "二次販売店名は必須オプションです。", "stuff.detail.save.valierror4": "二次販売店名は必須オプションです。",
"stuff.detail.save.objectNameTooLong": "物件名敬称含むは50文字以内で入力してください。",
"stuff.detail.move.confirmMsg": "商品情報画面に移動します。 [保存]していない図面情報は削除されます。商品情報画面に移動しますか?", "stuff.detail.move.confirmMsg": "商品情報画面に移動します。 [保存]していない図面情報は削除されます。商品情報画面に移動しますか?",
"stuff.planReqPopup.popTitle": "設計依頼検索", "stuff.planReqPopup.popTitle": "設計依頼検索",
"stuff.planReqPopup.btn1": "検索", "stuff.planReqPopup.btn1": "検索",
@ -1051,6 +1052,7 @@
"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": "案件名は必須です。",
"estimate.detail.save.objectNameTooLong": "案件名敬称含むは50文字以内で入力してください。",
"estimate.detail.save.requiredPkgAsp": "住宅PKG単価は0より大きい値を入力してください。", "estimate.detail.save.requiredPkgAsp": "住宅PKG単価は0より大きい値を入力してください。",
"estimate.detail.save.requiredEstimateDate": "見積日は必須です。", "estimate.detail.save.requiredEstimateDate": "見積日は必須です。",
"estimate.detail.save.requiredItemId": "製品を選択してください。", "estimate.detail.save.requiredItemId": "製品を選択してください。",

View File

@ -801,6 +801,7 @@
"stuff.detail.save.valierror2": "설치높이는 0보다 큰 값을 입력하세요", "stuff.detail.save.valierror2": "설치높이는 0보다 큰 값을 입력하세요",
"stuff.detail.save.valierror3": "{0} 필수 입력 항목입니다.", "stuff.detail.save.valierror3": "{0} 필수 입력 항목입니다.",
"stuff.detail.save.valierror4": "2차 판매점명은 필수 선택사항입니다.", "stuff.detail.save.valierror4": "2차 판매점명은 필수 선택사항입니다.",
"stuff.detail.save.objectNameTooLong": "물건명과 경칭을 합쳐 50자 이내로 입력해주세요.",
"stuff.detail.move.confirmMsg": "물건정보 화면으로 이동합니다. [저장]하지 않은 도면정보는 삭제됩니다. 물건정보 화면으로 이동하시겠습니까?", "stuff.detail.move.confirmMsg": "물건정보 화면으로 이동합니다. [저장]하지 않은 도면정보는 삭제됩니다. 물건정보 화면으로 이동하시겠습니까?",
"stuff.planReqPopup.popTitle": "설계 의뢰 검색", "stuff.planReqPopup.popTitle": "설계 의뢰 검색",
"stuff.planReqPopup.btn1": "검색", "stuff.planReqPopup.btn1": "검색",
@ -1051,6 +1052,7 @@
"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": "안건명은 필수값 입니다.",
"estimate.detail.save.objectNameTooLong": "안건명과 경칭을 합쳐 50자 이내로 입력해주세요.",
"estimate.detail.save.requiredPkgAsp": "주택pkg 단가는 0보다 큰 값을 입력하세요.", "estimate.detail.save.requiredPkgAsp": "주택pkg 단가는 0보다 큰 값을 입력하세요.",
"estimate.detail.save.requiredEstimateDate": "견적일은 필수값 입니다.", "estimate.detail.save.requiredEstimateDate": "견적일은 필수값 입니다.",
"estimate.detail.save.requiredItemId": "제품을 선택해주세요.", "estimate.detail.save.requiredItemId": "제품을 선택해주세요.",