feat: 회계/결재/생산/출하/대시보드 다수 개선 및 QA 수정

- BadDebtCollection, BillManagement, CardTransaction, TaxInvoice 회계 개선
- VendorManagement/VendorDetailClient 소폭 추가
- DocumentCreate/DraftBox 결재 기능 개선
- WorkOrder Create/Detail/Edit, ShipmentEdit 생산/출하 개선
- CEO 대시보드: PurchaseStatusSection, receivable/status-issue transformer 정비
- dashboard types/invalidation 확장
- LoginPage, Sidebar, HeaderFavoritesBar 레이아웃 수정
- QMS 페이지, StockStatusDetail, OrderRegistration 소폭 수정
- AttendanceManagement, VacationManagement HR 수정
- ConstructionDetailClient 건설 상세 개선
- claudedocs: 주간 구현내역, 대시보드 QA/수정계획, 결재/품질/생산/출하 문서 추가
This commit is contained in:
유병철
2026-03-09 21:06:01 +09:00
parent 7d369d1404
commit 68331be0ef
39 changed files with 1363 additions and 139 deletions

View File

@@ -93,31 +93,11 @@ function generateDebtCollectionCheckPoints(api: BadDebtApiResponse): CheckPoint[
return checkPoints;
}
// TODO: 백엔드 per-card sub_label/count 제공 시 더미값 제거
// 채권추심 카드별 더미 서브라벨 (회사명 + 건수)
const DEBT_COLLECTION_FALLBACK_SUB_LABELS: Record<string, { company: string; count: number }> = {
dc1: { company: '(주)부산화학 외', count: 5 },
dc2: { company: '(주)삼성테크 외', count: 3 },
dc3: { company: '(주)대한전자 외', count: 2 },
dc4: { company: '(주)한국정밀 외', count: 3 },
};
/**
* 채권추심 subLabel 생성 헬퍼
* dc1(누적)은 API client_count 사용, 나머지는 더미값
* 채권추심 subLabel: 백엔드 sub_labels 필드 직접 사용
*/
function buildDebtSubLabel(cardId: string, clientCount?: number): string | undefined {
const fallback = DEBT_COLLECTION_FALLBACK_SUB_LABELS[cardId];
if (!fallback) return undefined;
const count = cardId === 'dc1' && clientCount !== undefined ? clientCount : fallback.count;
if (count <= 0) return undefined;
const remaining = count - 1;
if (remaining > 0) {
return `${fallback.company} ${remaining}`;
}
return fallback.company.replace(/ 외$/, '');
function buildDebtSubLabel(cardId: string, subLabels?: Record<string, string | null>): string | undefined {
return subLabels?.[cardId] || undefined;
}
/**
@@ -130,25 +110,25 @@ export function transformDebtCollectionResponse(api: BadDebtApiResponse): DebtCo
id: 'dc1',
label: '누적 악성채권',
amount: api.total_amount,
subLabel: buildDebtSubLabel('dc1', api.client_count),
subLabel: buildDebtSubLabel('dc1', api.sub_labels),
},
{
id: 'dc2',
label: '추심중',
amount: api.collecting_amount,
subLabel: buildDebtSubLabel('dc2'),
subLabel: buildDebtSubLabel('dc2', api.sub_labels),
},
{
id: 'dc3',
label: '법적조치',
amount: api.legal_action_amount,
subLabel: buildDebtSubLabel('dc3'),
subLabel: buildDebtSubLabel('dc3', api.sub_labels),
},
{
id: 'dc4',
label: '회수완료',
amount: api.recovered_amount,
subLabel: buildDebtSubLabel('dc4'),
subLabel: buildDebtSubLabel('dc4', api.sub_labels),
},
],
checkPoints: generateDebtCollectionCheckPoints(api),

View File

@@ -14,42 +14,26 @@ import { normalizePath } from './common';
// ============================================
// 현황판 (StatusBoard)
// ============================================
// TODO: 백엔드 sub_label 필드 제공 시 더미값 제거
// API id 기준: orders, bad_debts, safety_stock, tax_deadline, new_clients, leaves, purchases, approvals
const STATUS_BOARD_FALLBACK_SUB_LABELS: Record<string, string> = {
orders: '(주)삼성전자 외',
bad_debts: '주식회사 부산화학 외',
safety_stock: '',
tax_deadline: '',
new_clients: '대한철강 외',
leaves: '',
// purchases: '(유)한국정밀 외', // [2026-03-03] 비활성화 — 백엔드 path 오류 + 데이터 정합성 이슈 (N4 참조)
approvals: '구매 결재 외',
};
//
// [대시보드 vs 원본 페이지 쿼리 조건 차이 — 건수 불일치는 버그 아님]
//
// | 항목 | 대시보드 조건 | 원본 페이지 |
// |----------------|---------------------------------------------------|------------------------------------------|
// | 수주 현황 | 오늘 날짜 + status=confirmed만 | /sales/order-management-sales (전체 기간) |
// | 채권 추심 | status=collecting + is_active=true만 | /accounting/bad-debt-collection (전체) |
// | 안전 재고 | safety_stock>0 && stock_qty<safety_stock (날짜무관) | /material/stock-status (날짜 필터 적용) |
// | 세금 신고 | 가장 가까운 tax 일정 D-day | /accounting/tax-invoices |
// | 신규 업체 | 최근 7일 이내 등록된 거래처만 | /accounting/vendors (전체 목록) |
// | 연차 현황 | 오늘 기준 approved 휴가만 | /hr/vacation-management (전체 기간) |
// | 발주 현황 | status=draft(임시저장)만 | /construction/order (전체 상태) |
// | 결재 요청 | 현재 로그인 사용자의 pending 결재만 | /approval/inbox (필터 조건 다름) |
//
/**
* 현황판 subLabel 생성 헬퍼
* API sub_label이 있으면 사용, 없으면 더미값 + 건수로 조합
* 현황판 subLabel: 백엔드 sub_label 필드 직접 사용
*/
function buildStatusSubLabel(item: { id: string; count: number | string; sub_label?: string }): string | undefined {
// API에서 sub_label 제공 시 우선 사용
if (item.sub_label) return item.sub_label;
// 건수가 0이거나 문자열이면 subLabel 불필요
const count = typeof item.count === 'number' ? item.count : parseInt(String(item.count), 10);
if (isNaN(count) || count <= 0) return undefined;
const fallback = STATUS_BOARD_FALLBACK_SUB_LABELS[item.id];
if (!fallback) return undefined;
// "대한철강 외" + 나머지 건수
const remaining = count - 1;
if (remaining > 0) {
return `${fallback} ${remaining}`;
}
// 1건이면 "외" 제거하고 이름만
return fallback.replace(/ 외$/, '');
function buildStatusSubLabel(item: { sub_label?: string }): string | undefined {
return item.sub_label || undefined;
}
/**

View File

@@ -107,6 +107,7 @@ export interface BadDebtApiResponse {
recovered_amount: number; // 회수완료
bad_debt_amount: number; // 대손처리
client_count?: number; // 거래처 수
sub_labels?: Record<string, string | null>; // 카드별 거래처 sub_label (dc1~dc4)
}
// ============================================

View File

@@ -31,7 +31,17 @@ type DomainKey =
| 'expectedExpense'
| 'bill'
| 'giftCertificate'
| 'journalEntry';
| 'journalEntry'
| 'order'
| 'stock'
| 'schedule'
| 'client'
| 'leave'
| 'approval'
| 'attendance'
| 'production'
| 'shipment'
| 'construction';
const DOMAIN_SECTION_MAP: Record<DomainKey, DashboardSectionKey[]> = {
deposit: ['dailyReport', 'receivable'],
@@ -43,6 +53,16 @@ const DOMAIN_SECTION_MAP: Record<DomainKey, DashboardSectionKey[]> = {
bill: ['dailyReport', 'receivable'],
giftCertificate: ['entertainment', 'cardManagement'],
journalEntry: ['entertainment', 'welfare', 'monthlyExpense'],
order: ['statusBoard', 'salesStatus'],
stock: ['statusBoard'],
schedule: ['statusBoard'],
client: ['statusBoard'],
leave: ['statusBoard', 'dailyAttendance'],
approval: ['statusBoard'],
attendance: ['statusBoard', 'dailyAttendance'],
production: ['statusBoard', 'dailyProduction'],
shipment: ['statusBoard', 'unshipped'],
construction: ['statusBoard', 'construction'],
};
const STORAGE_KEY = 'dashboard:stale-sections';