feat(WEB): 회계 DevFill 개선 - 유형 검증 제거, 매입 결재선/참조 자동설정
- 입금/출금: depositType/withdrawalType 'unset' 검증 제거 (미설정 상태로 등록 가능) - 매입(지출결의서): - 문서번호 자동 생성 (DEV-YYYYMMDD-HHMM-XXX) - 결재선: localStorage에서 현재 사용자 이름 매칭, 없으면 랜덤 선택 - 참조: 경리/회계/재무 부서 직원 중 랜덤 1명 선택
This commit is contained in:
@@ -71,10 +71,6 @@ export default function DepositDetailClientV2({
|
|||||||
toast.error('거래처를 선택해주세요.');
|
toast.error('거래처를 선택해주세요.');
|
||||||
return { success: false, error: '거래처를 선택해주세요.' };
|
return { success: false, error: '거래처를 선택해주세요.' };
|
||||||
}
|
}
|
||||||
if (submitData.depositType === 'unset') {
|
|
||||||
toast.error('입금 유형을 선택해주세요.');
|
|
||||||
return { success: false, error: '입금 유형을 선택해주세요.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
mode === 'create'
|
mode === 'create'
|
||||||
|
|||||||
@@ -70,10 +70,6 @@ export default function WithdrawalDetailClientV2({
|
|||||||
toast.error('거래처를 선택해주세요.');
|
toast.error('거래처를 선택해주세요.');
|
||||||
return { success: false, error: '거래처를 선택해주세요.' };
|
return { success: false, error: '거래처를 선택해주세요.' };
|
||||||
}
|
}
|
||||||
if (submitData.withdrawalType === 'unset') {
|
|
||||||
toast.error('출금 유형을 선택해주세요.');
|
|
||||||
return { success: false, error: '출금 유형을 선택해주세요.' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
mode === 'create'
|
mode === 'create'
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import {
|
|||||||
updateApproval,
|
updateApproval,
|
||||||
updateAndSubmitApproval,
|
updateAndSubmitApproval,
|
||||||
deleteApproval,
|
deleteApproval,
|
||||||
|
getEmployees,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { BasicInfoSection } from './BasicInfoSection';
|
import { BasicInfoSection } from './BasicInfoSection';
|
||||||
import { ApprovalLineSection } from './ApprovalLineSection';
|
import { ApprovalLineSection } from './ApprovalLineSection';
|
||||||
@@ -82,6 +84,7 @@ export function DocumentCreate() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const { currentUser } = useAuth();
|
||||||
|
|
||||||
// 수정 모드 / 복제 모드 상태
|
// 수정 모드 / 복제 모드 상태
|
||||||
const documentId = searchParams.get('id');
|
const documentId = searchParams.get('id');
|
||||||
@@ -121,21 +124,57 @@ export function DocumentCreate() {
|
|||||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||||
|
|
||||||
// ===== DevFill: 자동 입력 기능 =====
|
// ===== DevFill: 자동 입력 기능 =====
|
||||||
useDevFill('purchaseApproval', useCallback(() => {
|
useDevFill('purchaseApproval', useCallback(async () => {
|
||||||
if (!isEditMode && !isCopyMode) {
|
if (!isEditMode && !isCopyMode) {
|
||||||
const mockData = generatePurchaseApprovalData();
|
const mockData = generatePurchaseApprovalData();
|
||||||
|
|
||||||
|
// 직원 목록 가져오기
|
||||||
|
const employees = await getEmployees();
|
||||||
|
|
||||||
|
// localStorage에서 실제 로그인 사용자 이름 가져오기 (우측 상단 표시와 동일한 소스)
|
||||||
|
const userDataStr = localStorage.getItem("user");
|
||||||
|
const currentUserName = userDataStr ? JSON.parse(userDataStr).name : currentUser?.name;
|
||||||
|
|
||||||
|
// 현재 사용자 이름으로 결재선에 추가할 직원 찾기
|
||||||
|
const approver = currentUserName
|
||||||
|
? employees.find(e => e.name === currentUserName)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// 경리/회계/재무 부서 직원 중 랜덤 1명 참조 추가
|
||||||
|
const accountingDepts = ['경리', '회계', '재무'];
|
||||||
|
const accountingStaff = employees.filter(e =>
|
||||||
|
accountingDepts.some(dept => e.department?.includes(dept))
|
||||||
|
);
|
||||||
|
const randomReference = accountingStaff.length > 0
|
||||||
|
? accountingStaff[Math.floor(Math.random() * accountingStaff.length)]
|
||||||
|
: null;
|
||||||
|
|
||||||
setBasicInfo(prev => ({
|
setBasicInfo(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...mockData.basicInfo,
|
...mockData.basicInfo,
|
||||||
draftDate: prev.draftDate || mockData.basicInfo.draftDate,
|
draftDate: prev.draftDate || mockData.basicInfo.draftDate,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 결재선: 현재 사용자가 직원 목록에 있으면 설정, 없으면 랜덤 1명
|
||||||
|
if (approver) {
|
||||||
|
setApprovalLine([approver]);
|
||||||
|
} else if (employees.length > 0) {
|
||||||
|
const randomApprover = employees[Math.floor(Math.random() * employees.length)];
|
||||||
|
setApprovalLine([randomApprover]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 참조: 경리/회계/재무 직원이 있으면 설정
|
||||||
|
if (randomReference) {
|
||||||
|
setReferences([randomReference]);
|
||||||
|
}
|
||||||
|
|
||||||
setProposalData(prev => ({
|
setProposalData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
...mockData.proposalData,
|
...mockData.proposalData,
|
||||||
}));
|
}));
|
||||||
toast.success('지출결의서 데이터가 자동 입력되었습니다.');
|
toast.success('지출결의서 데이터가 자동 입력되었습니다.');
|
||||||
}
|
}
|
||||||
}, [isEditMode, isCopyMode]));
|
}, [isEditMode, isCopyMode, currentUser?.name]));
|
||||||
|
|
||||||
// 수정 모드: 문서 로드
|
// 수정 모드: 문서 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 회계 샘플 데이터 생성기 (입금, 출금, 매입)
|
* 회계 샘플 데이터 생성기 (입금, 출금, 매입, 카드)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -187,6 +187,24 @@ const PROPOSAL_REASONS = [
|
|||||||
'시설 유지보수',
|
'시설 유지보수',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 결재자/참조자 타입
|
||||||
|
export interface ApprovalPerson {
|
||||||
|
id: string;
|
||||||
|
department: string;
|
||||||
|
position: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 경리/회계/재무 부서 샘플 직원 (참조용)
|
||||||
|
const ACCOUNTING_STAFF: ApprovalPerson[] = [
|
||||||
|
{ id: 'acc-1', department: '경리팀', position: '대리', name: '김경리' },
|
||||||
|
{ id: 'acc-2', department: '경리팀', position: '사원', name: '박경리' },
|
||||||
|
{ id: 'acc-3', department: '회계팀', position: '과장', name: '이회계' },
|
||||||
|
{ id: 'acc-4', department: '회계팀', position: '대리', name: '최회계' },
|
||||||
|
{ id: 'acc-5', department: '재무팀', position: '차장', name: '정재무' },
|
||||||
|
{ id: 'acc-6', department: '재무팀', position: '과장', name: '강재무' },
|
||||||
|
];
|
||||||
|
|
||||||
export interface PurchaseApprovalFormData {
|
export interface PurchaseApprovalFormData {
|
||||||
basicInfo: {
|
basicInfo: {
|
||||||
drafter: string;
|
drafter: string;
|
||||||
@@ -194,6 +212,8 @@ export interface PurchaseApprovalFormData {
|
|||||||
documentNo: string;
|
documentNo: string;
|
||||||
documentType: string;
|
documentType: string;
|
||||||
};
|
};
|
||||||
|
approvalLine: ApprovalPerson[];
|
||||||
|
references: ApprovalPerson[];
|
||||||
proposalData: {
|
proposalData: {
|
||||||
vendor: string;
|
vendor: string;
|
||||||
vendorPaymentDate: string;
|
vendorPaymentDate: string;
|
||||||
@@ -207,19 +227,42 @@ export interface PurchaseApprovalFormData {
|
|||||||
export interface GeneratePurchaseApprovalDataOptions {
|
export interface GeneratePurchaseApprovalDataOptions {
|
||||||
vendors?: Array<{ id: string; name: string }>;
|
vendors?: Array<{ id: string; name: string }>;
|
||||||
documentType?: string;
|
documentType?: string;
|
||||||
|
currentUser?: ApprovalPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 문서번호 생성 (YYYYMMDD-HHMMSS-XXX 형식)
|
||||||
|
function generateDocumentNo(): string {
|
||||||
|
const now = new Date();
|
||||||
|
const date = now.toISOString().slice(0, 10).replace(/-/g, '');
|
||||||
|
const time = now.toTimeString().slice(0, 8).replace(/:/g, '');
|
||||||
|
const random = String(randomInt(100, 999));
|
||||||
|
return `DEV-${date}-${time.slice(0, 4)}-${random}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generatePurchaseApprovalData(options: GeneratePurchaseApprovalDataOptions = {}): PurchaseApprovalFormData {
|
export function generatePurchaseApprovalData(options: GeneratePurchaseApprovalDataOptions = {}): PurchaseApprovalFormData {
|
||||||
const { vendors = SAMPLE_VENDORS, documentType = 'proposal' } = options;
|
const { vendors = SAMPLE_VENDORS, documentType = 'proposal' } = options;
|
||||||
const vendor = randomPick(vendors);
|
const vendor = randomPick(vendors);
|
||||||
|
|
||||||
|
// 현재 사용자를 결재선에 추가 (기본값: 홍길동)
|
||||||
|
const currentUser: ApprovalPerson = options.currentUser || {
|
||||||
|
id: 'user-1',
|
||||||
|
department: '개발팀',
|
||||||
|
position: '사원',
|
||||||
|
name: '홍길동',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 경리/회계/재무 직원 중 랜덤으로 1명 참조 추가
|
||||||
|
const randomReference = randomPick(ACCOUNTING_STAFF);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
basicInfo: {
|
basicInfo: {
|
||||||
drafter: '홍길동',
|
drafter: currentUser.name,
|
||||||
draftDate: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
draftDate: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
||||||
documentNo: '',
|
documentNo: generateDocumentNo(),
|
||||||
documentType,
|
documentType,
|
||||||
},
|
},
|
||||||
|
approvalLine: [currentUser],
|
||||||
|
references: [randomReference],
|
||||||
proposalData: {
|
proposalData: {
|
||||||
vendor: vendor.name,
|
vendor: vendor.name,
|
||||||
vendorPaymentDate: today(),
|
vendorPaymentDate: today(),
|
||||||
@@ -229,4 +272,71 @@ export function generatePurchaseApprovalData(options: GeneratePurchaseApprovalDa
|
|||||||
estimatedCost: randomInt(100000, 5000000),
|
estimatedCost: randomInt(100000, 5000000),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 카드 거래 관련 =====
|
||||||
|
|
||||||
|
// 가맹점명 목록
|
||||||
|
const MERCHANT_NAMES = [
|
||||||
|
'스타벅스 강남점',
|
||||||
|
'GS25 역삼점',
|
||||||
|
'이마트 성수점',
|
||||||
|
'쿠팡 결제',
|
||||||
|
'네이버페이',
|
||||||
|
'배달의민족',
|
||||||
|
'카카오택시',
|
||||||
|
'주유소(SK에너지)',
|
||||||
|
'올리브영 신촌점',
|
||||||
|
'다이소 홍대점',
|
||||||
|
'맥도날드 종로점',
|
||||||
|
'교보문고 광화문점',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 카드 적요 목록
|
||||||
|
const CARD_MEMOS = [
|
||||||
|
'업무용 점심식대',
|
||||||
|
'사무용품 구매',
|
||||||
|
'출장 교통비',
|
||||||
|
'고객 접대비',
|
||||||
|
'회의 다과비',
|
||||||
|
'업무 차량 주유',
|
||||||
|
'직원 간식 구매',
|
||||||
|
'택배비',
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 사용유형 (usageType) - 카드 결제 분류
|
||||||
|
const CARD_USAGE_TYPES = ['unset', 'meal', 'transport', 'supplies', 'entertainment', 'other'];
|
||||||
|
|
||||||
|
export interface CardTransactionFormData {
|
||||||
|
cardId: string;
|
||||||
|
usedAt: string;
|
||||||
|
merchantName: string;
|
||||||
|
amount: number;
|
||||||
|
memo: string;
|
||||||
|
usageType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateCardTransactionDataOptions {
|
||||||
|
cards?: Array<{ id: string | number; name: string; cardNumber?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// datetime-local 형식으로 현재 시간 반환 (YYYY-MM-DDTHH:mm)
|
||||||
|
function nowDateTimeLocal(): string {
|
||||||
|
const now = new Date();
|
||||||
|
return now.toISOString().slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateCardTransactionData(options: GenerateCardTransactionDataOptions = {}): CardTransactionFormData {
|
||||||
|
const { cards } = options;
|
||||||
|
const card = cards && cards.length > 0 ? randomPick(cards) : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
cardId: card ? String(card.id) : '',
|
||||||
|
usedAt: nowDateTimeLocal(),
|
||||||
|
merchantName: randomPick(MERCHANT_NAMES),
|
||||||
|
amount: randomInt(5000, 500000),
|
||||||
|
memo: randomPick(CARD_MEMOS),
|
||||||
|
usageType: 'unset', // 미설정으로 고정
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user