feat: [출하/배차/회계] 배차 다중행 + 어음 리팩토링 + 출고관리

- 배차차량관리 목업→API 연동, 배차정보 다중 행
- ShipmentManagement 출고관리 API 매핑
- BillManagement 리팩토링 (섹션 분리, hooks, constants)
- 상품권 actions/types 확장
- 출하관리 캘린더 기본 뷰 week-time
This commit is contained in:
2026-03-07 03:03:27 +09:00
parent 9ad4c8ee9f
commit a4f99ae339
43 changed files with 4135 additions and 983 deletions

View File

@@ -32,9 +32,10 @@ import {
CATEGORY_LABELS,
SORT_OPTIONS,
} from './types';
import { getReceivablesList, getReceivablesSummary, updateOverdueStatus, updateMemos, exportReceivablesExcel } from './actions';
import { getReceivablesList, getReceivablesSummary, updateOverdueStatus, updateMemos } from './actions';
import { toast } from 'sonner';
import { filterByText } from '@/lib/utils/search';
import { downloadExcel, type ExcelColumn } from '@/lib/utils/excel-download';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { usePermission } from '@/hooks/usePermission';
@@ -213,27 +214,45 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
});
}, []);
// ===== 엑셀 다운로드 핸들러 =====
// ===== 엑셀 다운로드 핸들러 (프론트 xlsx 생성) =====
const handleExcelDownload = useCallback(async () => {
const result = await exportReceivablesExcel({
year: selectedYear,
search: searchQuery || undefined,
});
if (result.success && result.data) {
const url = URL.createObjectURL(result.data);
const a = document.createElement('a');
a.href = url;
a.download = result.filename || '채권현황.xlsx';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('엑셀 파일이 다운로드되었습니다.');
} else {
toast.error(result.error || '엑셀 다운로드에 실패했습니다.');
try {
toast.info('엑셀 파일 생성 중...');
// 데이터가 이미 로드되어 있으므로 sortedData 사용
if (sortedData.length === 0) {
toast.warning('다운로드할 데이터가 없습니다.');
return;
}
// 동적 월 컬럼 포함 엑셀 컬럼 생성
const columns: ExcelColumn<Record<string, unknown>>[] = [
{ header: '거래처', key: 'vendorName', width: 20 },
{ header: '연체', key: 'isOverdue', width: 8 },
...monthLabels.map((label, idx) => ({
header: label, key: `month_${idx}`, width: 12,
})),
{ header: '합계', key: 'total', width: 14 },
{ header: '메모', key: 'memo', width: 20 },
];
// 미수금 카테고리 기준으로 플랫 데이터 생성
const exportData = sortedData.map(vendor => {
const receivable = vendor.categories.find(c => c.category === 'receivable');
const row: Record<string, unknown> = {
vendorName: vendor.vendorName,
isOverdue: vendor.isOverdue ? '연체' : '',
};
monthLabels.forEach((_, idx) => {
row[`month_${idx}`] = receivable?.amounts.values[idx] || 0;
});
row.total = receivable?.amounts.total || 0;
row.memo = vendor.memo || '';
return row;
});
await downloadExcel({ data: exportData, columns, filename: '미수금현황', sheetName: '미수금현황' });
toast.success('엑셀 다운로드 완료');
} catch {
toast.error('엑셀 다운로드에 실패했습니다.');
}
}, [selectedYear, searchQuery]);
}, [sortedData, monthLabels]);
// ===== 변경된 연체 항목 확인 =====
const changedOverdueItems = useMemo(() => {