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:
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user