수기발행 / 현금영수증 용도변경

This commit is contained in:
focp212@naver.com
2025-09-11 16:38:49 +09:00
parent 1b4af7a82f
commit 48a3bd1ed4
15 changed files with 424 additions and 96 deletions

View File

@@ -0,0 +1,29 @@
import axios from 'axios';
import { API_URL } from '@/shared/api/urls';
import { resultify } from '@/shared/lib/resultify';
import { CBDCAxiosError } from '@/shared/@types/error';
import {
CashReceiptManualIssueParams,
CashReceiptManualIssueResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const cashReceiptManualIssue = (params: CashReceiptManualIssueParams) => {
return resultify(
axios.post<CashReceiptManualIssueResponse>(API_URL.cashReceiptManualIssue(), params),
);
};
export const useCashReceiptManualIssueMutation = (options?: UseMutationOptions<CashReceiptManualIssueResponse, CBDCAxiosError, CashReceiptManualIssueParams>) => {
const mutation = useMutation<CashReceiptManualIssueResponse, CBDCAxiosError, CashReceiptManualIssueParams>({
...options,
mutationFn: (params: CashReceiptManualIssueParams) => cashReceiptManualIssue(params),
});
return {
...mutation,
};
};

View File

@@ -0,0 +1,29 @@
import axios from 'axios';
import { API_URL } from '@/shared/api/urls';
import { resultify } from '@/shared/lib/resultify';
import { CBDCAxiosError } from '@/shared/@types/error';
import {
CashReceiptPurposeUpdateParams,
CashReceiptPurposeUpdateResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const cashReceiptPurposeUpdate = (params: CashReceiptPurposeUpdateParams) => {
return resultify(
axios.post<CashReceiptPurposeUpdateResponse>(API_URL.cashReceiptPurposeUpdate(), params),
);
};
export const useCashReceiptPurposeUpdateMutation = (options?: UseMutationOptions<CashReceiptPurposeUpdateResponse, CBDCAxiosError, CashReceiptPurposeUpdateParams>) => {
const mutation = useMutation<CashReceiptPurposeUpdateResponse, CBDCAxiosError, CashReceiptPurposeUpdateParams>({
...options,
mutationFn: (params: CashReceiptPurposeUpdateParams) => cashReceiptPurposeUpdate(params),
});
return {
...mutation,
};
};

View File

@@ -1,4 +1,4 @@
import { DefaulResponsePagination, DefaultRequestPagination } from '@/entities/common/model/types';
import { DefaulResponsePagination, DefaultRequestPagination, SuccessResult } from '@/entities/common/model/types';
export enum CancelTabKeys {
All = 'All',
@@ -33,6 +33,11 @@ export enum ProcessStep {
One = 'One',
Two = 'Two',
};
export enum CashReceiptPurpose {
ALL = 'ALL',
INCOME_DEDUCTION = 'INCOME_DEDUCTION',
EXPENSE_PROOF = 'EXPENSE_PROOF',
};
export interface SortOptionsBoxProps {
sortBy: SortByKeys;
onCliCkToSort: (sortBy: SortByKeys) => void;
@@ -301,7 +306,7 @@ export interface IssueInfo {
approvalNumber?: number;
issueNumber?: number;
issueDateTime?: string;
purpose?: string;
purpose?: CashReceiptPurpose;
paymentMethod?: string;
productName?: string;
transmissionStatus?: string;
@@ -361,11 +366,11 @@ export interface BillingDetailResponse extends BillingInfo {
}
export interface DetailInfoProps extends DetailResponse {
transactionCategory: TransactionCategory;
transactionCategory?: TransactionCategory;
show?: boolean;
tid?: string;
serviceCode?: string;
issueNumber?: number;
purpose?: CashReceiptPurpose;
onClickToShowInfo?: (info: InfoWrapKeys) => void;
}
export interface DetailArrowProps {
@@ -415,4 +420,34 @@ export interface AllTransactionCancelInfoResponse {
export interface FilterProps {
filterOn: boolean;
setFilterOn: (filterOn: boolean) => void;
}
};
export interface CashReceiptPurposeUpdateParams {
issueNumber: number;
newPurpose: string;
};
export interface CashReceiptPurposeUpdateResponse {
issueNumber: number;
beforePurposeType: CashReceiptPurpose;
afterPurposeType: CashReceiptPurpose;
updateDateTime: string;
};
export interface CashReceiptManualIssueParams {
businessNumber: string,
purpose: CashReceiptPurpose
productName: string,
buyerName: string,
issueNumber: string,
email: string,
phoneNumber: string,
supplyAmount: number,
vatAmount: number,
taxFreeAmount: number,
serviceCharge: number
};
export interface CashReceiptManualIssueResponse {
approvalNumber: number,
totalAmount: number,
issueDateTime: string,
issueResult: SuccessResult
};

View File

@@ -1,11 +1,42 @@
import { IMAGE_ROOT } from '@/shared/constants/common';
import { motion } from 'framer-motion';
export const BottomSheetChangesCashReceitUses = () => {
export interface BottomSheetCashReceitPurposeUpdateProps {
setBottomSheetOn: (bottomSheetOn: boolean) => void;
bottomSheetOn: boolean;
callPurposeUpdate: () => void;
};
export const BottomSheetCashReceitPurposeUpdate = ({
setBottomSheetOn,
bottomSheetOn,
callPurposeUpdate
}: BottomSheetCashReceitPurposeUpdateProps) => {
const onClickToClose = () => {
setBottomSheetOn(false);
};
const onCliickToPurposeUpdate = () => {
callPurposeUpdate();
};
const variants = {
hidden: { y: '100%' },
visible: { y: '0%' },
};
return (
<>
<div className="bg-dim"></div>
<div className="bottomsheet">
{ (bottomSheetOn) &&
<div className="bg-dim"></div>
}
<motion.div
className="bottomsheet"
initial="hidden"
animate={ (bottomSheetOn)? 'visible': 'hidden' }
variants={ variants }
transition={{ duration: 0.5 }}
>
<div className="bottomsheet-header">
<div className="bottomsheet-title">
<h2> </h2>
@@ -13,9 +44,10 @@ export const BottomSheetChangesCashReceitUses = () => {
className="close-btn"
type="button"
>
<img
<img
src={ IMAGE_ROOT + '/ico_close.svg' }
alt="닫기"
onClick={ () => onClickToClose() }
/>
</button>
</div>
@@ -35,9 +67,10 @@ export const BottomSheetChangesCashReceitUses = () => {
<button
className="btn-50 btn-blue flex-1"
type="button"
onClick={ () => onCliickToPurposeUpdate() }
></button>
</div>
</div>
</motion.div>
</>
);
};

View File

@@ -1,10 +1,44 @@
import { ChangeEvent } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout';
import { CashReceiptPurpose } from '../model/types';
export const CashReceiptHandWrittenIssuanceStep1 = () => {
export interface CashReceiptHandWrittenIssuanceStep1Props {
businessNumber?: string;
purpose?: CashReceiptPurpose;
productName?: string;
buyerName?: string;
issueNumber?: string;
email?: string;
phoneNumber?: string;
setBusinessNumber: (businessNumber: string) => void;
setPurpose: (purpose: CashReceiptPurpose) => void;
setProductName: (productName: string) => void;
setBuyerName: (buyerName: string) => void;
setIssueNumber: (issueNumber: string) => void;
setEmail: (email: string) => void;
setPhoneNumber: (phoneNumber: string) => void;
};
export const CashReceiptHandWrittenIssuanceStep1 = ({
businessNumber,
purpose,
productName,
buyerName,
issueNumber,
email,
phoneNumber,
setBusinessNumber,
setPurpose,
setProductName,
setBuyerName,
setIssueNumber,
setEmail,
setPhoneNumber
}: CashReceiptHandWrittenIssuanceStep1Props) => {
const { navigate } = useNavigate();
useSetOnBack(() => {
navigate(PATHS.transaction.cashReceipt.list);
});
@@ -18,7 +52,7 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
className="error"
type="text"
value="123456789"
value={ businessNumber }
disabled={ true }
/>
</div>
@@ -29,12 +63,14 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<div className="issue-field">
<div className="seg-buttons">
<button
className="btn-40 btn-blue"
className={ `btn-40 ${(purpose === CashReceiptPurpose.INCOME_DEDUCTION)? 'btn-blue': 'btn-white'}` }
type="button"
onClick={ () => setPurpose(CashReceiptPurpose.INCOME_DEDUCTION) }
></button>
<button
className="btn-40 btn-white"
className={ `btn-40 ${(purpose === CashReceiptPurpose.EXPENSE_PROOF)? 'btn-blue': 'btn-white'}` }
type="button"
onClick={ () => setPurpose(CashReceiptPurpose.EXPENSE_PROOF) }
></button>
</div>
</div>
@@ -45,6 +81,8 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
type="text"
placeholder="상품명"
value={ productName }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setProductName(e.target.value) }
/>
</div>
</div>
@@ -54,6 +92,8 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
type="text"
placeholder="구매자명"
value={ buyerName }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setBuyerName(e.target.value) }
/>
</div>
</div>
@@ -63,6 +103,8 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
type="text"
placeholder="사업자번호 OR 휴대폰번호"
value={ issueNumber }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setIssueNumber(e.target.value) }
/>
</div>
</div>
@@ -72,6 +114,8 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
type="email"
placeholder="TEST123@nicepay.com"
value={ email }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setEmail(e.target.value) }
/>
</div>
</div>
@@ -81,6 +125,8 @@ export const CashReceiptHandWrittenIssuanceStep1 = () => {
<input
type="tel"
placeholder="01012345678"
value={ phoneNumber }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setPhoneNumber(e.target.value) }
/>
</div>
</div>

View File

@@ -1,15 +1,37 @@
import { ChangeEvent } from 'react';
import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout';
import { ProcessStep } from '../model/types';
export interface CashReceiptHandWrittenIssuanceStep2Props {
setProcessStep: ((processStep: ProcessStep) => void);
setProcessStep: (processStep: ProcessStep) => void;
supplyAmount: number | string;
vatAmount: number | string;
taxFreeAmount: number | string;
serviceCharge: number | string;
setSupplyAmount: (supplyAmount: number | string) => void;
setVatAmount: (vatAmount: number | string) => void;
setTaxFreeAmount: (taxFreeAmount: number | string) => void;
setServiceCharge: (serviceCharge: number | string) => void;
};
export const CashReceiptHandWrittenIssuanceStep2 = ({
setProcessStep
setProcessStep,
supplyAmount,
vatAmount,
taxFreeAmount,
serviceCharge,
setSupplyAmount,
setVatAmount,
setTaxFreeAmount,
setServiceCharge
}: CashReceiptHandWrittenIssuanceStep2Props) => {
useSetOnBack(() => {
setProcessStep(ProcessStep.One);
});
const onClickToVatCalculate = () => {
};
return (
<>
<h2 className="issue-title"> </h2>
@@ -27,6 +49,7 @@ export const CashReceiptHandWrittenIssuanceStep2 = ({
<button
className="btn-40 btn-white"
type="button"
onClick={ () => onClickToVatCalculate() }
>VAT자동계산</button>
</div>
</div>
@@ -39,6 +62,8 @@ export const CashReceiptHandWrittenIssuanceStep2 = ({
className="error"
type="text"
placeholder=""
value={ supplyAmount }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setSupplyAmount(e.target.value) }
/>
</div>
</div>
@@ -49,26 +74,32 @@ export const CashReceiptHandWrittenIssuanceStep2 = ({
className="error"
type="text"
placeholder=""
value={ vatAmount }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setVatAmount(e.target.value) }
/>
</div>
</div>
<div className="issue-row">
<div className="issue-label"></div>
<div className="issue-label"></div>
<div className="issue-field">
<input
className="error"
type="text"
placeholder=""
value={ taxFreeAmount }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setTaxFreeAmount(e.target.value) }
/>
</div>
</div>
<div className="issue-row">
<div className="issue-label"> </div>
<div className="issue-label"></div>
<div className="issue-field">
<input
type="email"
className="error"
type="text"
placeholder=""
value={ serviceCharge }
onChange={ (e: ChangeEvent<HTMLInputElement>) => setServiceCharge(e.target.value) }
/>
</div>
</div>

View File

@@ -2,20 +2,35 @@ import moment from 'moment';
import { InfoWrapKeys, DetailInfoProps } from '../../model/types';
export const BillingInfoWrap = ({
transactionCategory,
billingInfo,
show,
onClickToShowInfo
}: DetailInfoProps) => {
const onClickToSetShowInfo = () => {
if(!!onClickToShowInfo){
onClickToShowInfo(InfoWrapKeys.Important);
const getInstallmentMonth = () => {
let rs = [];
if((!!billingInfo?.installmentMonth && parseInt(billingInfo?.installmentMonth) > 1)){
rs.push(
<li
key={ `key-installmentMonth`}
className="kv-row"
>
<span className="k"></span>
<span className="v">{ billingInfo?.installmentMonth } </span>
</li>
);
}
else{
rs.push(
<li
key={ `key-installmentMonth`}
className="kv-row"
>
<span className="k"></span>
<span className="v"></span>
</li>
);
}
return rs;
};
return (
<>
<div className="txn-section">
@@ -49,12 +64,7 @@ export const BillingInfoWrap = ({
<span className="k"></span>
<span className="v">{ billingInfo?.processResult }</span>
</li>
{ (!!billingInfo?.installmentMonth && parseInt(billingInfo?.installmentMonth) > 1) &&
<li className="kv-row">
<span className="k"></span>
<span className="v">{ billingInfo?.installmentMonth } </span>
</li>
}
{ getInstallmentMonth() }
<li className="kv-row">
<span className="k"></span>
<span className="v">{ billingInfo?.productName }</span>

View File

@@ -5,26 +5,13 @@ import moment from 'moment';
export const IssueInfoWrap = ({
transactionCategory,
issueInfo,
show,
onClickToShowInfo
purpose,
}: DetailInfoProps) => {
const onClickToSetShowInfo = () => {
if(!!onClickToShowInfo){
onClickToShowInfo(InfoWrapKeys.Issue);
}
};
return (
<>
<div className="txn-section">
<div
className="section-title with-toggle"
onClick={ () => onClickToSetShowInfo() }
>
<DetailArrow show={ show }></DetailArrow>
</div>
<div className="section-title"> </div>
<ul className="kv-list">
<li className="kv-row">
<span className="k"></span>
@@ -40,7 +27,7 @@ export const IssueInfoWrap = ({
</li>
<li className="kv-row">
<span className="k"></span>
<span className="v">{ issueInfo?.purpose }</span>
<span className="v">{ purpose }</span>
</li>
<li className="kv-row">
<span className="k"></span>