feat(WEB): 부실채권, 재고, 입고, 수주 UI 개선
- BadDebtCollection 액션/타입 리팩토링 - ReceivingProcessDialog 입고처리 개선 - StockStatusList 재고현황 UI 개선 - OrderSalesDetailView 수주 상세 수정 - UniversalListPage 범용 리스트 개선 - production-order 페이지 수정
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* 재고 현황 서버 액션
|
||||
*
|
||||
* API Endpoints:
|
||||
* - GET /api/v1/stocks - 목록 조회
|
||||
* API Endpoints (Item 기준):
|
||||
* - GET /api/v1/stocks - 목록 조회 (Item + Stock LEFT JOIN)
|
||||
* - GET /api/v1/stocks/stats - 통계 조회
|
||||
* - GET /api/v1/stocks/stats-by-type - 품목유형별 통계 조회
|
||||
* - GET /api/v1/stocks/{id} - 상세 조회 (LOT 포함)
|
||||
* - GET /api/v1/stocks/{id} - 상세 조회 (Item 기준, LOT 포함)
|
||||
*/
|
||||
|
||||
'use server';
|
||||
@@ -23,16 +23,12 @@ import type {
|
||||
LotStatusType,
|
||||
} from './types';
|
||||
|
||||
// ===== API 데이터 타입 =====
|
||||
interface StockApiData {
|
||||
// ===== API 데이터 타입 (Item 기준) =====
|
||||
|
||||
// Stock 관계 데이터
|
||||
interface StockRelationData {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
item_code: string;
|
||||
item_name: string;
|
||||
item_type: ItemType;
|
||||
item_type_label?: string;
|
||||
specification?: string;
|
||||
unit: string;
|
||||
item_id: number;
|
||||
stock_qty: string | number;
|
||||
safety_stock: string | number;
|
||||
reserved_qty: string | number;
|
||||
@@ -42,12 +38,30 @@ interface StockApiData {
|
||||
days_elapsed?: number;
|
||||
location?: string;
|
||||
status: StockStatusType;
|
||||
status_label?: string;
|
||||
last_receipt_date?: string;
|
||||
last_issue_date?: string;
|
||||
lots?: StockLotApiData[];
|
||||
}
|
||||
|
||||
// Item API 응답 데이터
|
||||
interface ItemApiData {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
code: string; // Item.code (기존 item_code)
|
||||
name: string; // Item.name (기존 item_name)
|
||||
item_type: ItemType; // Item.item_type (RM, SM, CS)
|
||||
unit: string;
|
||||
category_id?: number;
|
||||
category?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
description?: string;
|
||||
attributes?: Record<string, unknown>;
|
||||
is_active: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
lots?: StockLotApiData[];
|
||||
stock?: StockRelationData | null; // Stock 관계 (없으면 null)
|
||||
}
|
||||
|
||||
interface StockLotApiData {
|
||||
@@ -71,8 +85,8 @@ interface StockLotApiData {
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
interface StockApiPaginatedResponse {
|
||||
data: StockApiData[];
|
||||
interface ItemApiPaginatedResponse {
|
||||
data: ItemApiData[];
|
||||
current_page: number;
|
||||
last_page: number;
|
||||
per_page: number;
|
||||
@@ -84,6 +98,7 @@ interface StockApiStatsResponse {
|
||||
normal_count: number;
|
||||
low_count: number;
|
||||
out_count: number;
|
||||
no_stock_count: number;
|
||||
}
|
||||
|
||||
interface StockApiStatsByTypeResponse {
|
||||
@@ -95,19 +110,23 @@ interface StockApiStatsByTypeResponse {
|
||||
}
|
||||
|
||||
// ===== API → Frontend 변환 (목록용) =====
|
||||
function transformApiToListItem(data: StockApiData): StockItem {
|
||||
function transformApiToListItem(data: ItemApiData): StockItem {
|
||||
const stock = data.stock;
|
||||
const hasStock = !!stock;
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
itemCode: data.item_code,
|
||||
itemName: data.item_name,
|
||||
itemCode: data.code,
|
||||
itemName: data.name,
|
||||
itemType: data.item_type,
|
||||
unit: data.unit || 'EA',
|
||||
stockQty: parseFloat(String(data.stock_qty)) || 0,
|
||||
safetyStock: parseFloat(String(data.safety_stock)) || 0,
|
||||
lotCount: data.lot_count || 0,
|
||||
lotDaysElapsed: data.days_elapsed || 0,
|
||||
status: data.status,
|
||||
location: data.location || '-',
|
||||
stockQty: hasStock ? (parseFloat(String(stock.stock_qty)) || 0) : 0,
|
||||
safetyStock: hasStock ? (parseFloat(String(stock.safety_stock)) || 0) : 0,
|
||||
lotCount: hasStock ? (stock.lot_count || 0) : 0,
|
||||
lotDaysElapsed: hasStock ? (stock.days_elapsed || 0) : 0,
|
||||
status: hasStock ? stock.status : null,
|
||||
location: hasStock ? (stock.location || '-') : '-',
|
||||
hasStock,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -129,22 +148,38 @@ function transformApiToLot(data: StockLotApiData): LotDetail {
|
||||
}
|
||||
|
||||
// ===== API → Frontend 변환 (상세용) =====
|
||||
function transformApiToDetail(data: StockApiData): StockDetail {
|
||||
function transformApiToDetail(data: ItemApiData): StockDetail {
|
||||
const stock = data.stock;
|
||||
const hasStock = !!stock;
|
||||
|
||||
// description 또는 attributes에서 규격 정보 추출
|
||||
let specification = '-';
|
||||
if (data.description) {
|
||||
specification = data.description;
|
||||
} else if (data.attributes && typeof data.attributes === 'object') {
|
||||
// attributes에서 규격 관련 정보 추출 시도
|
||||
const attrs = data.attributes as Record<string, unknown>;
|
||||
if (attrs.specification) {
|
||||
specification = String(attrs.specification);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
itemCode: data.item_code,
|
||||
itemName: data.item_name,
|
||||
itemCode: data.code,
|
||||
itemName: data.name,
|
||||
itemType: data.item_type,
|
||||
category: '-', // API에서 category 제공 안 함
|
||||
specification: data.specification || '-',
|
||||
category: data.category?.name || '-',
|
||||
specification,
|
||||
unit: data.unit || 'EA',
|
||||
currentStock: parseFloat(String(data.stock_qty)) || 0,
|
||||
safetyStock: parseFloat(String(data.safety_stock)) || 0,
|
||||
location: data.location || '-',
|
||||
lotCount: data.lot_count || 0,
|
||||
lastReceiptDate: data.last_receipt_date || '-',
|
||||
status: data.status,
|
||||
lots: (data.lots || []).map(transformApiToLot),
|
||||
currentStock: hasStock ? (parseFloat(String(stock.stock_qty)) || 0) : 0,
|
||||
safetyStock: hasStock ? (parseFloat(String(stock.safety_stock)) || 0) : 0,
|
||||
location: hasStock ? (stock.location || '-') : '-',
|
||||
lotCount: hasStock ? (stock.lot_count || 0) : 0,
|
||||
lastReceiptDate: hasStock ? (stock.last_receipt_date || '-') : '-',
|
||||
status: hasStock ? stock.status : null,
|
||||
hasStock,
|
||||
lots: hasStock && stock.lots ? stock.lots.map(transformApiToLot) : [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,6 +190,7 @@ function transformApiToStats(data: StockApiStatsResponse): StockStats {
|
||||
normalCount: data.normal_count,
|
||||
lowCount: data.low_count,
|
||||
outCount: data.out_count,
|
||||
noStockCount: data.no_stock_count || 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,7 +274,7 @@ export async function getStocks(params?: {
|
||||
};
|
||||
}
|
||||
|
||||
const paginatedData: StockApiPaginatedResponse = result.data || {
|
||||
const paginatedData: ItemApiPaginatedResponse = result.data || {
|
||||
data: [],
|
||||
current_page: 1,
|
||||
last_page: 1,
|
||||
@@ -340,7 +376,7 @@ export async function getStockStatsByType(): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 재고 상세 조회 (LOT 포함) =====
|
||||
// ===== 재고 상세 조회 (Item 기준, LOT 포함) =====
|
||||
export async function getStockById(id: string): Promise<{
|
||||
success: boolean;
|
||||
data?: StockDetail;
|
||||
|
||||
Reference in New Issue
Block a user