feat(WEB): 자재/영업/품질 모듈 기능 개선 및 문서 컴포넌트 추가
- 입고관리: 상세/목록 UI 개선, actions 로직 강화 - 재고현황: 상세/목록 개선, StockAuditModal 신규 추가 - 영업주문관리: 페이지 구조 개선, OrderSalesDetailEdit 기능 강화 - 주문: OrderRegistration 개선, SalesOrderDocument 신규 추가 - 견적: QuoteTransactionModal 기능 개선 - 품질: InspectionModalV2, ImportInspectionDocument 대폭 개선 - UniversalListPage: 템플릿 기능 확장 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -114,17 +114,33 @@ function transformApiToListItem(data: ItemApiData): StockItem {
|
||||
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') {
|
||||
const attrs = data.attributes as Record<string, unknown>;
|
||||
if (attrs.specification) {
|
||||
specification = String(attrs.specification);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
stockNumber: hasStock ? String((stock as unknown as Record<string, unknown>).stock_number ?? stock.id ?? data.id) : String(data.id),
|
||||
itemCode: data.code,
|
||||
itemName: data.name,
|
||||
itemType: data.item_type,
|
||||
specification,
|
||||
unit: data.unit || 'EA',
|
||||
calculatedQty: hasStock ? (parseFloat(String((stock as unknown as Record<string, unknown>).calculated_qty ?? stock.stock_qty)) || 0) : 0,
|
||||
actualQty: hasStock ? (parseFloat(String((stock as unknown as Record<string, unknown>).actual_qty ?? stock.stock_qty)) || 0) : 0,
|
||||
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,
|
||||
useStatus: data.is_active === false ? 'inactive' : 'active',
|
||||
location: hasStock ? (stock.location || '-') : '-',
|
||||
hasStock,
|
||||
};
|
||||
@@ -210,9 +226,12 @@ export async function getStocks(params?: {
|
||||
search?: string;
|
||||
itemType?: string;
|
||||
status?: string;
|
||||
useStatus?: string;
|
||||
location?: string;
|
||||
sortBy?: string;
|
||||
sortDir?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data: StockItem[];
|
||||
@@ -232,9 +251,14 @@ export async function getStocks(params?: {
|
||||
if (params?.status && params.status !== 'all') {
|
||||
searchParams.set('status', params.status);
|
||||
}
|
||||
if (params?.useStatus && params.useStatus !== 'all') {
|
||||
searchParams.set('is_active', params.useStatus === 'active' ? '1' : '0');
|
||||
}
|
||||
if (params?.location) searchParams.set('location', params.location);
|
||||
if (params?.sortBy) searchParams.set('sort_by', params.sortBy);
|
||||
if (params?.sortDir) searchParams.set('sort_dir', params.sortDir);
|
||||
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
||||
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks${queryString ? `?${queryString}` : ''}`;
|
||||
@@ -410,3 +434,99 @@ export async function getStockById(id: string): Promise<{
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 재고 단건 수정 =====
|
||||
export async function updateStock(
|
||||
id: string,
|
||||
data: {
|
||||
actualQty: number;
|
||||
safetyStock: number;
|
||||
useStatus: 'active' | 'inactive';
|
||||
}
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
actual_qty: data.actualQty,
|
||||
safety_stock: data.safetyStock,
|
||||
is_active: data.useStatus === 'active',
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '재고 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '재고 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] updateStock error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 재고 실사 (일괄 업데이트) =====
|
||||
export async function updateStockAudit(updates: { id: string; actualQty: number }[]): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/stocks/audit`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
items: updates.map((u) => ({
|
||||
item_id: u.id,
|
||||
actual_qty: u.actualQty,
|
||||
})),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return { success: false, error: error.message, __authError: error.code === 'UNAUTHORIZED' };
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '재고 실사 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
return { success: false, error: result.message || '재고 실사 저장에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (isNextRedirectError(error)) throw error;
|
||||
console.error('[StockActions] updateStockAudit error:', error);
|
||||
return { success: false, error: '서버 오류가 발생했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user