Files
sam-react-prod/src/components/pricing-distribution/actions.ts
유병철 c1b63b850a feat(WEB): 자재/출고/생산/품질/단가 기능 대폭 개선 및 신규 페이지 추가
자재관리:
- 입고관리 재고조정 다이얼로그 신규 추가, 상세/목록 기능 확장
- 재고현황 컴포넌트 리팩토링

출고관리:
- 출하관리 생성/수정/목록/상세 개선
- 차량배차관리 상세/수정/목록 기능 보강

생산관리:
- 작업지시서 WIP 생산 모달 신규 추가
- 벤딩WIP/슬랫조인트바 검사 콘텐츠 신규 추가
- 작업자화면 기능 대폭 확장 (카드/목록 개선)
- 검사성적서 모달 개선

품질관리:
- 실적보고서 관리 페이지 신규 추가
- 검사관리 문서/타입/목데이터 개선

단가관리:
- 단가배포 페이지 및 컴포넌트 신규 추가
- 단가표 관리 페이지 및 컴포넌트 신규 추가

공통:
- 권한 시스템 추가 개선 (PermissionContext, usePermission, PermissionGuard)
- 메뉴 폴링 훅 개선, 레이아웃 수정
- 모바일 줌/패닝 CSS 수정
- locale 유틸 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 12:46:19 +09:00

445 lines
11 KiB
TypeScript

'use server';
import type {
PriceDistributionListItem,
PriceDistributionDetail,
PriceDistributionFormData,
PriceDistributionStats,
DistributionStatus,
PriceDistributionItem,
} from './types';
// ============================================================================
// 목데이터
// ============================================================================
const MOCK_ITEMS: PriceDistributionItem[] = [
{
id: 'item-1',
pricingCode: '121212',
itemCode: '123123',
itemType: '반제품',
itemName: '품목명A',
specification: 'ST',
unit: 'EA',
purchasePrice: 10000,
processingCost: 5000,
marginRate: 50.0,
salesPrice: 20000,
status: '사용',
author: '홍길동',
changedDate: '2026-01-15',
},
{
id: 'item-2',
pricingCode: '121213',
itemCode: '123124',
itemType: '완제품',
itemName: '품목명B',
specification: '규격B',
unit: 'SET',
purchasePrice: 8000,
processingCost: 3000,
marginRate: 40.0,
salesPrice: 14000,
status: '사용',
author: '김철수',
changedDate: '2026-01-16',
},
{
id: 'item-3',
pricingCode: '121214',
itemCode: '123125',
itemType: '반제품',
itemName: '품목명C',
specification: 'ST',
unit: 'EA',
purchasePrice: 15000,
processingCost: 5000,
marginRate: 50.0,
salesPrice: 27000,
status: '사용',
author: '이영희',
changedDate: '2026-01-17',
},
{
id: 'item-4',
pricingCode: '121215',
itemCode: '123126',
itemType: '원자재',
itemName: '품목명D',
specification: 'AL',
unit: 'KG',
purchasePrice: 5000,
processingCost: 2000,
marginRate: 60.0,
salesPrice: 10000,
status: '사용',
author: '박민수',
changedDate: '2026-01-18',
},
{
id: 'item-5',
pricingCode: '121216',
itemCode: '123127',
itemType: '완제품',
itemName: '품목명E',
specification: '규격E',
unit: 'SET',
purchasePrice: 20000,
processingCost: 8000,
marginRate: 50.0,
salesPrice: 38000,
status: '사용',
author: '홍길동',
changedDate: '2026-01-19',
},
{
id: 'item-6',
pricingCode: '121217',
itemCode: '123128',
itemType: '반제품',
itemName: '품목명F',
specification: 'ST',
unit: 'EA',
purchasePrice: 12000,
processingCost: 4000,
marginRate: 50.0,
salesPrice: 22000,
status: '사용',
author: '김철수',
changedDate: '2026-01-20',
},
{
id: 'item-7',
pricingCode: '121218',
itemCode: '123129',
itemType: '원자재',
itemName: '품목명G',
specification: 'SUS',
unit: 'KG',
purchasePrice: 7000,
processingCost: 3000,
marginRate: 45.0,
salesPrice: 13000,
status: '사용',
author: '이영희',
changedDate: '2026-01-21',
},
];
const MOCK_LIST: PriceDistributionListItem[] = [
{
id: '1',
distributionNo: '121212',
distributionName: '2025년 1월',
status: 'finalized',
author: '김대표',
createdAt: '2026-01-15',
revisionCount: 3,
},
{
id: '2',
distributionNo: '121213',
distributionName: '2025년 1월',
status: 'revision',
author: '김대표',
createdAt: '2026-01-20',
revisionCount: 1,
},
{
id: '3',
distributionNo: '121214',
distributionName: '2025년 1월',
status: 'initial',
author: '김대표',
createdAt: '2026-01-25',
revisionCount: 0,
},
{
id: '4',
distributionNo: '121215',
distributionName: '2025년 1월',
status: 'revision',
author: '김대표',
createdAt: '2026-01-28',
revisionCount: 2,
},
{
id: '5',
distributionNo: '121216',
distributionName: '2025년 1월',
status: 'finalized',
author: '김대표',
createdAt: '2026-02-01',
revisionCount: 4,
},
{
id: '6',
distributionNo: '121217',
distributionName: '2025년 1월',
status: 'initial',
author: '김대표',
createdAt: '2026-02-03',
revisionCount: 0,
},
{
id: '7',
distributionNo: '121218',
distributionName: '2025년 1월',
status: 'revision',
author: '김대표',
createdAt: '2026-02-03',
revisionCount: 1,
},
];
const MOCK_DETAILS: Record<string, PriceDistributionDetail> = {
'1': {
id: '1',
distributionNo: '121212',
distributionName: '2025년 1월',
status: 'finalized',
createdAt: '2026-01-15',
documentNo: '',
effectiveDate: '2026-01-01',
officePhone: '02-1234-1234',
orderPhone: '02-1234-1234',
author: '김대표',
items: MOCK_ITEMS,
},
'2': {
id: '2',
distributionNo: '121213',
distributionName: '2025년 1월',
status: 'revision',
createdAt: '2026-01-20',
documentNo: '',
effectiveDate: '2026-01-01',
officePhone: '02-1234-1234',
orderPhone: '02-1234-1234',
author: '김대표',
items: MOCK_ITEMS.slice(0, 5),
},
'3': {
id: '3',
distributionNo: '121214',
distributionName: '2025년 1월',
status: 'initial',
createdAt: '2026-01-25',
documentNo: '',
effectiveDate: '2026-02-01',
officePhone: '02-1234-1234',
orderPhone: '02-1234-1234',
author: '김대표',
items: MOCK_ITEMS.slice(0, 3),
},
};
// ============================================================================
// API 함수 (목데이터 기반)
// ============================================================================
/**
* 단가배포 목록 조회
*/
export async function getPriceDistributionList(params?: {
page?: number;
size?: number;
q?: string;
status?: DistributionStatus;
dateFrom?: string;
dateTo?: string;
}): Promise<{
success: boolean;
data?: { items: PriceDistributionListItem[]; total: number };
error?: string;
}> {
let items = [...MOCK_LIST];
if (params?.status) {
items = items.filter((item) => item.status === params.status);
}
if (params?.q) {
const q = params.q.toLowerCase();
items = items.filter(
(item) =>
item.distributionNo.toLowerCase().includes(q) ||
item.distributionName.toLowerCase().includes(q) ||
item.author.toLowerCase().includes(q)
);
}
if (params?.dateFrom) {
items = items.filter((item) => item.createdAt >= params.dateFrom!);
}
if (params?.dateTo) {
items = items.filter((item) => item.createdAt <= params.dateTo!);
}
return {
success: true,
data: { items, total: items.length },
};
}
/**
* 단가배포 통계
*/
export async function getPriceDistributionStats(): Promise<{
success: boolean;
data?: PriceDistributionStats;
error?: string;
}> {
const total = MOCK_LIST.length;
const initial = MOCK_LIST.filter((p) => p.status === 'initial').length;
const revision = MOCK_LIST.filter((p) => p.status === 'revision').length;
const finalized = MOCK_LIST.filter((p) => p.status === 'finalized').length;
return { success: true, data: { total, initial, revision, finalized } };
}
/**
* 단가배포 상세 조회
*/
export async function getPriceDistributionById(id: string): Promise<{
success: boolean;
data?: PriceDistributionDetail;
error?: string;
}> {
const detail = MOCK_DETAILS[id];
if (!detail) {
// 목록에 있는 항목은 기본 상세 데이터 생성
const listItem = MOCK_LIST.find((item) => item.id === id);
if (listItem) {
return {
success: true,
data: {
id: listItem.id,
distributionNo: listItem.distributionNo,
distributionName: listItem.distributionName,
status: listItem.status,
createdAt: listItem.createdAt,
documentNo: '',
effectiveDate: '2026-01-01',
officePhone: '02-1234-1234',
orderPhone: '02-1234-1234',
author: listItem.author,
items: MOCK_ITEMS,
},
};
}
return { success: false, error: '단가배포를 찾을 수 없습니다.' };
}
return { success: true, data: detail };
}
/**
* 단가배포 등록 (현재 단가표 기준 자동 생성)
*/
export async function createPriceDistribution(): Promise<{
success: boolean;
data?: PriceDistributionDetail;
error?: string;
}> {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
let name = `${year}${month}`;
// 중복 체크 → (N) 추가
const existing = MOCK_LIST.filter((item) =>
item.distributionName.startsWith(name)
);
if (existing.length > 0) {
name = `${name}(${existing.length})`;
}
const newId = String(Date.now());
const newItem: PriceDistributionDetail = {
id: newId,
distributionNo: String(Math.floor(100000 + Math.random() * 900000)),
distributionName: name,
status: 'initial',
createdAt: now.toISOString().split('T')[0],
documentNo: '',
effectiveDate: now.toISOString().split('T')[0],
officePhone: '',
orderPhone: '',
author: '현재사용자',
items: MOCK_ITEMS,
};
return { success: true, data: newItem };
}
/**
* 단가배포 수정
*/
export async function updatePriceDistribution(
id: string,
data: PriceDistributionFormData
): Promise<{
success: boolean;
data?: PriceDistributionDetail;
error?: string;
}> {
const detailData = MOCK_DETAILS[id];
const listItem = MOCK_LIST.find((item) => item.id === id);
if (!detailData && !listItem) {
return { success: false, error: '단가배포를 찾을 수 없습니다.' };
}
const detail: PriceDistributionDetail = detailData || {
id,
distributionNo: listItem!.distributionNo,
distributionName: data.distributionName,
status: 'revision' as DistributionStatus,
createdAt: listItem!.createdAt,
documentNo: data.documentNo,
effectiveDate: data.effectiveDate,
officePhone: data.officePhone,
orderPhone: data.orderPhone,
author: listItem!.author,
items: MOCK_ITEMS,
};
const updated: PriceDistributionDetail = {
...detail,
distributionName: data.distributionName,
documentNo: data.documentNo,
effectiveDate: data.effectiveDate,
officePhone: data.officePhone,
orderPhone: data.orderPhone,
status: detail.status === 'initial' ? 'revision' : detail.status,
};
return { success: true, data: updated };
}
/**
* 단가배포 최종확정
*/
export async function finalizePriceDistribution(id: string): Promise<{
success: boolean;
error?: string;
}> {
const exists = MOCK_LIST.find((item) => item.id === id) || MOCK_DETAILS[id];
if (!exists) {
return { success: false, error: '단가배포를 찾을 수 없습니다.' };
}
return { success: true };
}
/**
* 단가배포 삭제
*/
export async function deletePriceDistribution(ids: string[]): Promise<{
success: boolean;
deletedCount?: number;
error?: string;
}> {
return { success: true, deletedCount: ids.length };
}