feat: 회계 모듈 전면 개선 — 계정과목 공통화·전표·세금계산서·어음·상품권

- AccountSubjectSelect 공통 컴포넌트 신규 (계정과목 선택 통합)
- 일반전표 수동입력/수정 모달 계정과목 연동
- 세금계산서 관리 타입 시스템 재정의 + 전표 연동 모달
- 어음관리 리팩토링 + 상품권 접대비 연동
- 카드거래 조회 전표 연동 모달 개선
- 악성채권/입출금/매입매출/거래처 상세 뷰 보강
This commit is contained in:
2026-03-10 11:35:26 +09:00
parent 72cf5d86a2
commit 81373281ea
40 changed files with 863 additions and 531 deletions

View File

@@ -23,7 +23,8 @@ import {
SelectValue,
} from '@/components/ui/select';
import type { CardTransaction, JournalEntryItem } from './types';
import { DEDUCTION_OPTIONS, ACCOUNT_SUBJECT_OPTIONS } from './types';
import { DEDUCTION_OPTIONS } from './types';
import { AccountSubjectSelect } from '@/components/accounting/common';
import { saveJournalEntries } from './actions';
interface JournalEntryModalProps {
@@ -194,23 +195,16 @@ export function JournalEntryModal({ open, onOpenChange, transaction, onSuccess }
{/* 계정과목 + 공제 + 증빙/판매자상호 */}
<div className="grid grid-cols-3 gap-3">
{/* Select - FormField 예외 */}
<div>
<Label className="text-xs"></Label>
<Select
value={item.accountSubject || 'none'}
onValueChange={(v) => updateItem(index, 'accountSubject', v === 'none' ? '' : v)}
>
<SelectTrigger className="mt-1 h-8 text-sm">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{ACCOUNT_SUBJECT_OPTIONS.filter(o => o.value !== '').map(opt => (
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
))}
</SelectContent>
</Select>
<div className="mt-1">
<AccountSubjectSelect
value={item.accountSubject}
onValueChange={(v) => updateItem(index, 'accountSubject', v)}
placeholder="선택"
size="sm"
/>
</div>
</div>
{/* Select - FormField 예외 */}
<div>

View File

@@ -25,7 +25,8 @@ import {
} from '@/components/ui/select';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import type { ManualInputFormData } from './types';
import { DEDUCTION_OPTIONS, ACCOUNT_SUBJECT_OPTIONS } from './types';
import { DEDUCTION_OPTIONS } from './types';
import { AccountSubjectSelect } from '@/components/accounting/common';
import { getCardList, createCardTransaction } from './actions';
import { getTodayString } from '@/lib/utils/date';
@@ -254,20 +255,13 @@ export function ManualInputModal({ open, onOpenChange, onSuccess }: ManualInputM
</div>
<div>
<Label className="text-sm font-medium"></Label>
<Select
value={formData.accountSubject || 'none'}
onValueChange={(v) => handleChange('accountSubject', v === 'none' ? '' : v)}
>
<SelectTrigger className="mt-1">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{ACCOUNT_SUBJECT_OPTIONS.filter(o => o.value !== '').map(opt => (
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
))}
</SelectContent>
</Select>
<div className="mt-1">
<AccountSubjectSelect
value={formData.accountSubject}
onValueChange={(v) => handleChange('accountSubject', v)}
placeholder="선택"
/>
</div>
</div>
</div>

View File

@@ -42,6 +42,7 @@ import type { CardTransaction, InlineEditData, SortOption } from './types';
import {
SORT_OPTIONS, DEDUCTION_OPTIONS, ACCOUNT_SUBJECT_OPTIONS,
} from './types';
import { AccountSubjectSelect } from '@/components/accounting/common';
import {
getCardTransactionList,
getCardTransactionSummary,
@@ -89,7 +90,7 @@ const tableColumns = [
{ key: 'deductionType', label: '공제', className: 'min-w-[95px]', sortable: false },
{ key: 'businessNumber', label: '사업자번호', className: 'min-w-[110px]' },
{ key: 'merchantName', label: '가맹점명', className: 'min-w-[100px]' },
{ key: 'vendorName', label: '증빙/판매자상호', className: 'min-w-[130px]', sortable: false },
{ key: 'vendorName', label: '증빙/판매자상호', className: 'min-w-[160px]', sortable: false },
{ key: 'description', label: '내역', className: 'min-w-[120px]', sortable: false },
{ key: 'totalAmount', label: '합계금액', className: 'min-w-[100px] text-right' },
{ key: 'supplyAmount', label: '공급가액', className: 'min-w-[110px] text-right', sortable: false },
@@ -599,20 +600,13 @@ export function CardTransactionInquiry() {
</TableCell>
{/* 계정과목 (인라인 Select) */}
<TableCell onClick={(e) => e.stopPropagation()}>
<Select
value={getEditValue(item.id, 'accountSubject', item.accountSubject) || 'none'}
onValueChange={(v) => handleInlineEdit(item.id, 'accountSubject', v === 'none' ? '' : v)}
>
<SelectTrigger className="h-7 text-xs min-w-[90px] w-auto">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
{ACCOUNT_SUBJECT_OPTIONS.filter(o => o.value !== '').map(opt => (
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
))}
</SelectContent>
</Select>
<AccountSubjectSelect
value={getEditValue(item.id, 'accountSubject', item.accountSubject) || ''}
onValueChange={(v) => handleInlineEdit(item.id, 'accountSubject', v)}
placeholder="선택"
size="sm"
className="min-w-[90px] w-auto"
/>
</TableCell>
{/* 분개 버튼 */}
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>