fix: 가맹점 등록을 막는다
백엔드가 등록을 자체 저장에서 BizPlay 외부 가맹점 등록 API 위임으로 바꿨다 (edupay-backend 5fa9d34). `POST /api/v1/mngr/voc`가 form에서 @RequestBody(JSON)가 되고 bizPlayVocApiService.insertVoc를 부르며, 예금주명·입금은행코드·입금계좌번호·업태코드가 새로 필수가 됐다. 지금 등록 폼은 그 값을 받지 않아 계약이 맞지 않는다. - 목록의 [등록] 버튼을 비활성화한다(엑셀등록과 같은 방식). 시안의 자리는 그대로 둔다. - 모달을 여는 상태·렌더를 걷어내 폼에 닿을 수 없게 한다. - saveMerchantAction도 저장 직전에서 끊는다 — Server Action은 UI를 거치지 않고도 호출되므로 버튼만 비활성화하면 막은 것이 아니다. 세션 확인과 입력 검증은 그대로 두어 다시 열 때 createMerchant 호출만 되살리면 되게 했다. 다시 열 때: createMerchant를 새 계약(JSON, MngrVocInsertReqVo)으로 바꾸고 폼에 정산 계좌 항목을 추가한 뒤, _actions.ts의 마지막 return을 try/catch + createMerchant로 되돌린다. Co-Authored-By: Claude Opus 5
@e8f99be617b885ce70b2a8c76768a25d431076af
--- app/(protected)/(basic)/system/merchants/_actions.ts
+++ app/(protected)/(basic)/system/merchants/_actions.ts
... | ... | @@ -1,21 +1,22 @@ |
| 1 | 1 |
'use server'; |
| 2 | 2 |
|
| 3 |
-import { unstable_rethrow } from 'next/navigation';
|
|
| 4 |
-import { revalidatePath } from 'next/cache';
|
|
| 5 | 3 |
import { verifySession } from '@/lib/auth/dal';
|
| 6 |
-import { BackendRequestError } from '@/lib/http/backend-fetch';
|
|
| 7 |
-import { createMerchant } from '@/lib/data/repositories/merchant-repository';
|
|
| 8 | 4 |
import {
|
| 9 | 5 |
validateMerchant, |
| 10 | 6 |
type MerchantFormState, |
| 11 | 7 |
} from '@/lib/domain/merchant-form'; |
| 12 |
-import { MERCHANTS_PATH } from '@/lib/domain/merchant-query';
|
|
| 13 | 8 |
|
| 14 |
-const SAVE_FAILED_MESSAGE = '저장하지 못했습니다. 잠시 후 다시 시도해 주세요.'; |
|
| 15 |
- |
|
| 16 |
-function failureMessage(error: unknown, fallback: string): string {
|
|
| 17 |
- return error instanceof BackendRequestError ? error.message : fallback; |
|
| 18 |
-} |
|
| 9 |
+/** |
|
| 10 |
+ * TODO(백엔드): 등록이 자체 저장에서 BizPlay 외부 가맹점 등록 API 위임으로 바뀌었다 — |
|
| 11 |
+ * `POST /api/v1/mngr/voc`가 @RequestBody(JSON)가 되고 `bizPlayVocApiService.insertVoc`를 |
|
| 12 |
+ * 부른다. 예금주명·입금은행코드·입금계좌번호·업태코드가 새로 필수인데 이 폼은 받지 않는다. |
|
| 13 |
+ * 항목이 정해질 때까지 등록을 막는다. |
|
| 14 |
+ * |
|
| 15 |
+ * 화면의 [등록] 버튼만 비활성화하면 막은 것이 아니다 — Server Action은 UI를 거치지 않고도 |
|
| 16 |
+ * 직접 호출된다. 그래서 저장 직전에서 끊는다. |
|
| 17 |
+ */ |
|
| 18 |
+const DISABLED_MESSAGE = |
|
| 19 |
+ '가맹점 등록은 현재 사용할 수 없습니다. 등록 항목이 확정되면 다시 열립니다.'; |
|
| 19 | 20 |
|
| 20 | 21 |
function readString(formData: FormData, key: string): string {
|
| 21 | 22 |
const value = formData.get(key); |
... | ... | @@ -48,15 +49,5 @@ |
| 48 | 49 |
return { status: 'error', errors: validation.errors };
|
| 49 | 50 |
} |
| 50 | 51 |
|
| 51 |
- // TODO(백엔드): 수정 API가 없다(`/api/v1/mngr/voc`는 목록·등록 둘뿐). 생기면 여기서 가른다. |
|
| 52 |
- try {
|
|
| 53 |
- await createMerchant(validation.values); |
|
| 54 |
- } catch (error) {
|
|
| 55 |
- // 이슈: 세션이 밀렸을 때 backendFetch가 던지는 redirect를 이 catch가 삼키면 안 된다. |
|
| 56 |
- unstable_rethrow(error); |
|
| 57 |
- return { status: 'error', message: failureMessage(error, SAVE_FAILED_MESSAGE) };
|
|
| 58 |
- } |
|
| 59 |
- |
|
| 60 |
- revalidatePath(MERCHANTS_PATH); |
|
| 61 |
- return { status: 'success' };
|
|
| 52 |
+ return { status: 'error', message: DISABLED_MESSAGE };
|
|
| 62 | 53 |
} |
--- app/(protected)/(basic)/system/merchants/_components/merchant-list.tsx
+++ app/(protected)/(basic)/system/merchants/_components/merchant-list.tsx
... | ... | @@ -1,7 +1,6 @@ |
| 1 | 1 |
'use client'; |
| 2 | 2 |
|
| 3 | 3 |
import { useRouter } from 'next/navigation';
|
| 4 |
-import { useState } from 'react';
|
|
| 5 | 4 |
import { FoxButton } from '@fox/core/components/fox-button';
|
| 6 | 5 |
import {
|
| 7 | 6 |
FoxListContainer, |
... | ... | @@ -29,7 +28,6 @@ |
| 29 | 28 |
type MerchantQuery, |
| 30 | 29 |
type MerchantSearchField, |
| 31 | 30 |
} from '@/lib/domain/merchant-query'; |
| 32 |
-import { MerchantFormModal } from './merchant-form-modal';
|
|
| 33 | 31 |
import { MerchantRowActions } from './merchant-row-actions';
|
| 34 | 32 |
import styles from './merchant-filter.module.scss'; |
| 35 | 33 |
|
... | ... | @@ -52,7 +50,6 @@ |
| 52 | 50 |
totalCount, |
| 53 | 51 |
}: MerchantListProps) {
|
| 54 | 52 |
const router = useRouter(); |
| 55 |
- const [isCreateOpen, setIsCreateOpen] = useState(false); |
|
| 56 | 53 |
|
| 57 | 54 |
function go(patch: Partial<MerchantQuery>) {
|
| 58 | 55 |
router.replace(buildMerchantHref(query, { ...patch, page: 1 }));
|
... | ... | @@ -216,6 +213,10 @@ |
| 216 | 213 |
label="엑셀다운로드" |
| 217 | 214 |
/> |
| 218 | 215 |
</form> |
| 216 |
+ {/* TODO(백엔드): 등록이 자체 저장에서 BizPlay 외부 가맹점 등록 API 위임으로 바뀌었다
|
|
| 217 |
+ (`POST /api/v1/mngr/voc`가 @RequestBody JSON이 되고 bizPlayVocApiService.insertVoc를 |
|
| 218 |
+ 부른다). 예금주명·입금은행코드·입금계좌번호·업태코드가 새로 필요한데 이 폼은 받지 |
|
| 219 |
+ 않는다. 항목이 정해지면 MerchantFormModal과 함께 다시 연다. */} |
|
| 219 | 220 |
<FoxButton |
| 220 | 221 |
type="primary" |
| 221 | 222 |
size="md" |
... | ... | @@ -223,7 +224,7 @@ |
| 223 | 224 |
<i className="fox-ico fox-ico-Plus" aria-hidden="true" /> |
| 224 | 225 |
} |
| 225 | 226 |
label="등록" |
| 226 |
- onAction={() => setIsCreateOpen(true)}
|
|
| 227 |
+ disabled |
|
| 227 | 228 |
/> |
| 228 | 229 |
<FoxButton |
| 229 | 230 |
type="default" |
... | ... | @@ -248,10 +249,6 @@ |
| 248 | 249 |
} |
| 249 | 250 |
}} |
| 250 | 251 |
/> |
| 251 |
- |
|
| 252 |
- {isCreateOpen && (
|
|
| 253 |
- <MerchantFormModal onClose={() => setIsCreateOpen(false)} />
|
|
| 254 |
- )} |
|
| 255 | 252 |
</> |
| 256 | 253 |
); |
| 257 | 254 |
} |
--- lib/data/repositories/merchant-repository.ts
+++ lib/data/repositories/merchant-repository.ts
... | ... | @@ -155,6 +155,13 @@ |
| 155 | 155 |
: []; |
| 156 | 156 |
} |
| 157 | 157 |
|
| 158 |
+/** |
|
| 159 |
+ * TODO(백엔드): 아래 form 전송은 옛 계약이다. 지금 `POST /api/v1/mngr/voc`는 @RequestBody(JSON) |
|
| 160 |
+ * `MngrVocInsertReqVo`를 받고 BizPlay 외부 API로 위임하며, 예금주명(bankDpstrNm)·입금은행코드 |
|
| 161 |
+ * (dpstBankCd)·입금계좌번호(dpstActNo)·업태코드(frcsBzstatCd)를 새로 요구한다. lat·lng는 요청 |
|
| 162 |
+ * VO에서 빠졌다. 등록 화면이 그 항목을 받게 되면 이 함수를 JSON으로 바꿔 다시 연결한다 |
|
| 163 |
+ * (현재 호출부 없음 — `_actions.ts` 참고). |
|
| 164 |
+ */ |
|
| 158 | 165 |
export async function createMerchant(values: MerchantValues): Promise<void> {
|
| 159 | 166 |
const accessToken = await getSessionAccessToken(); |
| 160 | 167 |
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?