현금영수증 수기 발행 완료

This commit is contained in:
focp212@naver.com
2025-10-24 10:28:34 +09:00
parent c2041f918f
commit 9348656db0
12 changed files with 117 additions and 97 deletions

View File

@@ -54,7 +54,7 @@ export const CashReceiptHandWrittenIssuanceStep1 = ({
className="error"
type="text"
value={ businessNumber }
disabled={ true }
readOnly={ true }
/>
</div>
</div>

View File

@@ -1,37 +1,40 @@
import { ChangeEvent } from 'react';
import { useSetOnBack } from '@/widgets/sub-layout/use-sub-layout';
import { ProcessStep } from '../model/types';
import { NumericFormat } from 'react-number-format';
export interface CashReceiptHandWrittenIssuanceStep2Props {
setProcessStep: (processStep: ProcessStep) => void;
issueAmount: number;
supplyAmount: number;
vatAmount: number;
taxFreeAmount: number;
serviceCharge: number;
setIssueAmount: (issueAmount: number) => void;
setSupplyAmount: (supplyAmount: number) => void;
setVatAmount: (vatAmount: number) => void;
setTaxFreeAmount: (taxFreeAmount: number) => void;
setServiceCharge: (serviceCharge: number) => void;
onClickToVatCalculate: () => void;
};
export const CashReceiptHandWrittenIssuanceStep2 = ({
setProcessStep,
issueAmount,
supplyAmount,
vatAmount,
taxFreeAmount,
serviceCharge,
setIssueAmount,
setSupplyAmount,
setVatAmount,
setTaxFreeAmount,
setServiceCharge
setServiceCharge,
onClickToVatCalculate
}: CashReceiptHandWrittenIssuanceStep2Props) => {
useSetOnBack(() => {
setProcessStep(ProcessStep.One);
});
const onClickToVatCalculate = () => {
};
return (
<>
<h2 className="issue-title"> </h2>
@@ -41,15 +44,16 @@ export const CashReceiptHandWrittenIssuanceStep2 = ({
<div className="issue-label"></div>
<div className="issue-field">
<div className="seg-buttons">
<input
type="text"
value="555,555,555"
readOnly={ true }
/>
<NumericFormat
value={ issueAmount }
allowNegative={ false }
displayType="input"
onChange={ (e: ChangeEvent<HTMLInputElement>) => setIssueAmount(parseInt(e.target.value)) }
></NumericFormat>
<button
className="btn-40 btn-white"
type="button"
onClick={ () => onClickToVatCalculate() }
onClick={ onClickToVatCalculate }
>VAT자동계산</button>
</div>
</div>
@@ -58,49 +62,45 @@ export const CashReceiptHandWrittenIssuanceStep2 = ({
<div className="issue-row">
<div className="issue-label"></div>
<div className="issue-field">
<input
className="error"
type="text"
placeholder=""
<NumericFormat
value={ supplyAmount }
allowNegative={ false }
displayType="input"
onChange={ (e: ChangeEvent<HTMLInputElement>) => setSupplyAmount(parseInt(e.target.value)) }
/>
></NumericFormat>
</div>
</div>
<div className="issue-row">
<div className="issue-label">VAT</div>
<div className="issue-field">
<input
className="error"
type="text"
placeholder=""
<NumericFormat
value={ vatAmount }
allowNegative={ false }
displayType="input"
onChange={ (e: ChangeEvent<HTMLInputElement>) => setVatAmount(parseInt(e.target.value)) }
/>
></NumericFormat>
</div>
</div>
<div className="issue-row">
<div className="issue-label"></div>
<div className="issue-field">
<input
className="error"
type="text"
placeholder=""
<NumericFormat
value={ taxFreeAmount }
allowNegative={ false }
displayType="input"
onChange={ (e: ChangeEvent<HTMLInputElement>) => setTaxFreeAmount(parseInt(e.target.value)) }
/>
></NumericFormat>
</div>
</div>
<div className="issue-row">
<div className="issue-label"></div>
<div className="issue-field">
<input
className="error"
type="text"
placeholder=""
<NumericFormat
value={ serviceCharge }
allowNegative={ false }
displayType="input"
onChange={ (e: ChangeEvent<HTMLInputElement>) => setServiceCharge(parseInt(e.target.value)) }
/>
></NumericFormat>
</div>
</div>
<div className="issue-note error">

View File

@@ -1,29 +0,0 @@
import axios from 'axios';
import { API_URL_USER } from '@/shared/api/api-url-user';
import { resultify } from '@/shared/lib/resultify';
import { NiceAxiosError } from '@/shared/@types/error';
import {
BusinessPropertyByMidParams,
BusinessPropertyByMidResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const businessPropertyByMid = (params: BusinessPropertyByMidParams) => {
return resultify(
axios.post<BusinessPropertyByMidResponse>(API_URL_USER.businessPropertyByMid(), params),
);
};
export const useBusinessPropertyByMidMutation = (options?: UseMutationOptions<BusinessPropertyByMidResponse, NiceAxiosError, BusinessPropertyByMidParams>) => {
const mutation = useMutation<BusinessPropertyByMidResponse, NiceAxiosError, BusinessPropertyByMidParams>({
...options,
mutationFn: (params: BusinessPropertyByMidParams) => businessPropertyByMid(params),
});
return {
...mutation,
};
};

View File

@@ -0,0 +1,29 @@
import axios from 'axios';
import { API_URL_USER } from '@/shared/api/api-url-user';
import { resultify } from '@/shared/lib/resultify';
import { NiceAxiosError } from '@/shared/@types/error';
import {
BusinessPropertyParams,
BusinessPropertyResponse
} from '../model/types';
import {
useMutation,
UseMutationOptions
} from '@tanstack/react-query';
export const businessProperty = (params: BusinessPropertyParams) => {
return resultify(
axios.post<BusinessPropertyResponse>(API_URL_USER.businessProperty(), params),
);
};
export const useBusinessPropertyMutation = (options?: UseMutationOptions<BusinessPropertyResponse, NiceAxiosError, BusinessPropertyParams>) => {
const mutation = useMutation<BusinessPropertyResponse, NiceAxiosError, BusinessPropertyParams>({
...options,
mutationFn: (params: BusinessPropertyParams) => businessProperty(params),
});
return {
...mutation,
};
};

View File

@@ -261,13 +261,13 @@ export interface ShortcutUserResponse {
usingDefault: boolean;
};
export interface BusinessPropertyByMidParams {
export interface BusinessPropertyParams {
mid: string;
};
export interface BusinessPropertyByMidResponse {
export interface BusinessPropertyResponse {
companyNumber: string;
businessScaleTypeName: string;
};
export interface BusinessInfo extends BusinessPropertyByMidResponse {
export interface BusinessInfo extends BusinessPropertyResponse {
};

View File

@@ -14,6 +14,6 @@ export const VatReturnTargetTypeBtnGroup = [
];
export const VatReturnTaxBtnGroups = [
{name: '과세', value: VatReturnPayTax.Tax },
{name: '면세', value: VatReturnPayTax.NoTax },
{name: '과세', value: VatReturnPayTax.TAX },
{name: '면세', value: VatReturnPayTax.NOTAX },
];

View File

@@ -14,8 +14,8 @@ export enum VatReturnReceiptType {
BILL = 'BILL '
};
export enum VatReturnPayTax {
Tax = 'Tax',
NoTax = 'NoTax'
TAX = '0',
NOTAX = '1'
};
export enum VatReturnTargetType {
ALL = 'ALL',
@@ -91,8 +91,6 @@ export interface VatReturnReferenceRequestParams {
startMonth: string;
endMonth: string;
taxType: string,
requestorName: string;
contactNumber: string;
email: string;
};
export interface VatReturnReferenceRequestResponse {

View File

@@ -22,7 +22,7 @@ export const ReferenceWrap = () => {
const [mid, setMid] = useState<string>(userMid);
const [startDate, setStartDate] = useState<string>(moment().format('YYYYMMDD'));
const [endDate, setEndDate] = useState<string>(moment().format('YYYYMMDD'));
const [payTax, setPayTax] = useState<VatReturnPayTax>(VatReturnPayTax.Tax);
const [payTax, setPayTax] = useState<VatReturnPayTax>(VatReturnPayTax.TAX);
const [email, setEmail] = useState<string>(userEmail);
const [errorMsg, setErrorMsg] = useState<string>('');
@@ -34,9 +34,7 @@ export const ReferenceWrap = () => {
startMonth: startDate,
endMonth: endDate,
taxType: payTax,
requestorName: '',
contactNumber: '',
email: email
email: email || userEmail
}
vatReturnReferenceRequest(params).then((rs: VatReturnReferenceRequestResponse) => {
setSuccessPageOn(true);

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { PATHS } from '@/shared/constants/paths';
import { useNavigate } from '@/shared/lib/hooks/use-navigate';
import { CashReceiptHandWrittenIssuanceStep1 } from '@/entities/transaction/ui/cash-receipt-hand-written-issuance-step1';
@@ -8,19 +8,23 @@ import { HeaderType} from '@/entities/common/model/types';
import { useSetFooterMode, useSetHeaderTitle, useSetHeaderType } from '@/widgets/sub-layout/use-sub-layout';
import { useCashReceiptManualIssueMutation } from '@/entities/transaction/api/use-cash-receipt-manual-issue-mutation';
import { showAlert } from '@/widgets/show-alert';
import { useStore } from '@/shared/model/store';
import { snackBar } from '@/shared/lib';
export const CashReceitHandWrittenIssuancePage = () => {
const { navigate } = useNavigate();
let businessInfo = useStore.getState().UserStore.businessInfo;
let userMid = useStore.getState().UserStore.mid;
// 1 or 2
const [processStep, setProcessStep] = useState<ProcessStep>(ProcessStep.One);
const [businessNumber, setBusinessNumber] = useState<string>('');
const [businessNumber, setBusinessNumber] = useState<string>(businessInfo.companyNumber);
const [purposeType, setPurposeType] = useState<CashReceiptPurposeType>(CashReceiptPurposeType.INCOME_DEDUCTION);
const [productName, setProductName] = useState<string>('');
const [buyerName, setBuyerName] = useState<string>('');
const [issueNumber, setIssueNumber] = useState<string>('');
const [email, setEmail] = useState<string>('');
const [phoneNumber, setPhoneNumber] = useState<string>('');
const [issueAmount, setIssueAmount] = useState<number>(0);
const [supplyAmount, setSupplyAmount] = useState<number>(0);
const [vatAmount, setVatAmount] = useState<number>(0);
const [taxFreeAmount, setTaxFreeAmount] = useState<number>(0);
@@ -44,6 +48,7 @@ export const CashReceitHandWrittenIssuancePage = () => {
const callManualIssue = () => {
let params = {
mid: userMid,
businessNumber: businessNumber,
purpose: purposeType,
productName: productName,
@@ -58,41 +63,57 @@ export const CashReceitHandWrittenIssuancePage = () => {
};
cashReceiptManualIssue(params).then((rs) => {
console.log(rs);
alert('완료');
navigate(PATHS.transaction.cashReceipt.list);
snackBar('수기 신청이 완료되었습니다.', function(){
navigate(PATHS.transaction.cashReceipt.list);
}, 3000);
});
};
const onClickToVatCalculate = () => {
let amount = parseInt((issueAmount / 11 * 10).toFixed(0));
let vat = issueAmount - amount;
setSupplyAmount(amount);
setVatAmount(vat);
};
const onClickToChangeTab = () => {
if(processStep === ProcessStep.One){
if(!businessNumber){
showAlert('사업자 번호는 필수 입력 항목 입니다.');
return;
}
else if(!productName){
showAlert('상품명은 필수 입력 항목 입니다.');
return;
}
else if(!buyerName){
showAlert('구매자는 필수 입력 항목 입니다.');
return;
}
else if(!issueNumber){
showAlert('발행번호는 필수 입력 항목 입니다.');
return;
}
else if(email && !checkEmail(email)){
showAlert('이메일 형식이 맞지 않습니다.');
return;
}
else if(!phoneNumber){
showAlert('전화번호는 필수 입력 항목 입니다.');
return;
}
setProcessStep(ProcessStep.Two);
}
else if(processStep === ProcessStep.Two){
if(supplyAmount <= 0){
showAlert('공급가액은 1원 이상이어야 합니다.');
return;
}
callManualIssue();
// 완료시?
//alert('완료');
// navigate(PATHS.transaction.cashReceipt.list);
}
};
return (
<>
<main>
@@ -136,14 +157,17 @@ export const CashReceitHandWrittenIssuancePage = () => {
{ (processStep === ProcessStep.Two) &&
<CashReceiptHandWrittenIssuanceStep2
setProcessStep={ setProcessStep }
issueAmount={ issueAmount }
supplyAmount={ supplyAmount }
vatAmount={ vatAmount }
taxFreeAmount={ taxFreeAmount }
serviceCharge={ serviceCharge }
setIssueAmount={ setIssueAmount }
setSupplyAmount={ setSupplyAmount }
setVatAmount={ setVatAmount }
setTaxFreeAmount={ setTaxFreeAmount }
setServiceCharge={ setServiceCharge }
onClickToVatCalculate={ onClickToVatCalculate }
></CashReceiptHandWrittenIssuanceStep2>
}
</div>

View File

@@ -60,7 +60,7 @@ export const API_URL_USER = {
},
// mid 로 사업자 정보 조회
businessPropertyByMid: () => {
return `${API_BASE_URL}/api/v1/amsw/business-property/by-mid`;
businessProperty: () => {
return `${API_BASE_URL}/api/v1/${API_URL_KEY}/business-property`;
},
}

View File

@@ -1,3 +1,5 @@
import { ChangeEvent } from "react";
export interface FilterSelectProps {
title: string;
selectValue: string;
@@ -18,7 +20,6 @@ export const FilterSelect = ({
<option
key={ `key-filter-select-${i}` }
value={ selectOptions[i]?.value }
selected={ (selectValue === selectOptions[i]?.value)? true: false }
>{ selectOptions[i]?.name }</option>
);
}
@@ -33,7 +34,7 @@ export const FilterSelect = ({
<select
className="flex-1"
value={ selectValue }
onChange={ (e: any) => selectSetter(e.target.value)}
onChange={ (e: ChangeEvent<HTMLSelectElement>) => selectSetter(e.target.value)}
>
{ getSelectOptions() }
</select>

View File

@@ -23,10 +23,10 @@ import { useStore } from '@/shared/model/store';
import { getLocalStorage, setLocalStorage } from '@/shared/lib';
import { StorageKeys } from '@/shared/constants/local-storage';
import { HomeGroupsParams, HomeGroupsResponse } from '@/entities/home/model/types';
import { BusinessPropertyByMidParams, BusinessPropertyByMidResponse, LoginResponse, ShortcutUserParams, ShortcutUserResponse, UserFindAuthMethodParams, UserFindAuthMethodResponse } from '@/entities/user/model/types';
import { BusinessPropertyParams, BusinessPropertyResponse, LoginResponse, ShortcutUserParams, ShortcutUserResponse, UserFindAuthMethodParams, UserFindAuthMethodResponse } from '@/entities/user/model/types';
import { useShortcutUserMutation } from '@/entities/user/api/use-shortcut-user-mutation';
import { useShortcutDefaultMutation } from '@/entities/user/api/use-shortcut-detault-mutation';
import { useBusinessPropertyByMidMutation } from '@/entities/user/api/use-business-property-by-mid-mutation';
import { useBusinessPropertyMutation } from '@/entities/user/api/use-business-property-mutation';
import { useUserFindAuthMethodMutation } from '@/entities/user/api/use-user-find-authmethod-mutation';
import { useCodesSelectMutation } from '@/entities/common/api/use-codes-select-mutation';
@@ -75,7 +75,7 @@ export const SubLayout = () => {
const { mutateAsync: codesSelect} = useCodesSelectMutation();
const { mutateAsync: shortcutUser } = useShortcutUserMutation();
const { mutateAsync: shortcutDefault } = useShortcutDefaultMutation();
const { mutateAsync: businessPropertyByMid } = useBusinessPropertyByMidMutation();
const { mutateAsync: businessProperty } = useBusinessPropertyMutation();
const { mutateAsync: findAuthMethod } = useUserFindAuthMethodMutation();
const wrapperClassName = 'wrapper';
@@ -106,13 +106,13 @@ export const SubLayout = () => {
});
};
const callBusinessPropertyByMid = () => {
const callBusinessProperty = () => {
if(!!mid){
let params: BusinessPropertyByMidParams = {
let params: BusinessPropertyParams = {
mid: mid
};
businessPropertyByMid(params).then((rs: BusinessPropertyByMidResponse) => {
console.log(rs);
businessProperty(params).then((rs: BusinessPropertyResponse) => {
useStore.getState().UserStore.setBusinessInfo(rs);
});
}
@@ -178,7 +178,6 @@ export const SubLayout = () => {
let emails = rs.emails.map((value: any, index: any) => {
return value.content;
});
console.log('emails -->', emails)
useStore.getState().UserStore.setUserEmails(emails);
let options: Array<Record<string, string>> = emails.map((value: any, index: any) => {
return {
@@ -300,7 +299,7 @@ export const SubLayout = () => {
}, []);
useEffect(() => {
if(!!mid){
// callBusinessPropertyByMid();
callBusinessProperty();
callFindAuthMethod();
}
}, [mid]);