fix: 경동 BOM 계산 수정 및 품목-공정 매핑
- KyungdongFormulaHandler: product_type 자동 추론(item_category 기반), 철재 주자재 EGI코일로 변경, 조인트바 steel 공통 지원 - FormulaEvaluatorService: FG item_category에서 product_type 자동 판별 - MapItemsToProcesses: 경동 품목-공정 매핑 커맨드 정비 - KyungdongItemMasterSeeder: BOM child_item_id code 기반 재매핑 - ItemsBomController: ghost ID 유효성 검증 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,31 +41,37 @@ class MapItemsToProcesses extends Command
|
||||
* - G: 연기차단재
|
||||
* - L: L-Bar
|
||||
*/
|
||||
/**
|
||||
* FG(완제품), RM(원자재) 제외 - 공정별 생산 품목만 매핑
|
||||
* EST-INSPECTION(검사비), EST-MOTOR/EST-CTRL(구매품)도 제외
|
||||
*/
|
||||
private array $globalExcludes = ['FG-%', 'RM-%', 'EST-INSPECTION'];
|
||||
|
||||
private array $mappingRules = [
|
||||
'P-001' => [
|
||||
'name' => '슬랫',
|
||||
'code_patterns' => [],
|
||||
'name_keywords' => ['철재용', '철재', '슬랫'],
|
||||
'name_excludes' => ['스크린', '가이드레일', '하단마감', '연기차단', '케이스'], // 재고생산 품목 제외
|
||||
'code_patterns' => ['EST-RAW-슬랫-%'], // 슬랫 원자재 (방화/방범/조인트바)
|
||||
'name_keywords' => ['슬랫'],
|
||||
'name_excludes' => ['스크린', '가이드레일', '하단마감', '연기차단', '케이스'],
|
||||
],
|
||||
'P-002' => [
|
||||
'name' => '스크린',
|
||||
'code_patterns' => [],
|
||||
'code_patterns' => ['EST-RAW-스크린-%'], // 스크린 원자재 (실리카/와이어 등)
|
||||
'name_keywords' => ['스크린용', '스크린', '원단', '실리카', '방충', '와이어'],
|
||||
'name_excludes' => ['가이드레일', '하단마감', '연기차단', '케이스'], // 재고생산 품목 제외
|
||||
'name_excludes' => ['가이드레일', '하단마감', '연기차단', '케이스'],
|
||||
],
|
||||
'P-003' => [
|
||||
'name' => '절곡',
|
||||
'code_patterns' => ['BD-%'], // BD 코드는 절곡
|
||||
'name_keywords' => ['절곡'], // 절곡 키워드만 (나머지는 P-004로)
|
||||
'name_keywords' => ['절곡', '연기차단재'], // 연기차단재는 절곡 공정에서 조립
|
||||
'name_excludes' => [],
|
||||
],
|
||||
'P-004' => [
|
||||
'name' => '재고생산',
|
||||
'code_patterns' => ['PT-%'], // PT 코드는 재고생산 부품
|
||||
'name_keywords' => ['가이드레일', '케이스', '연기차단', 'L-Bar', 'L-BAR', 'LBar', '하단마감', '린텔', '하장바'],
|
||||
'name_keywords' => ['가이드레일', '케이스', 'L-Bar', 'L-BAR', 'LBar', '하단마감', '린텔', '하장바', '환봉', '감기샤프트', '각파이프', '앵글'],
|
||||
'name_excludes' => [],
|
||||
'code_excludes' => ['BD-%'], // BD 코드는 P-003으로
|
||||
'code_excludes' => ['BD-%', 'EST-SMOKE-%'], // BD는 P-003, EST-SMOKE는 P-003
|
||||
],
|
||||
];
|
||||
|
||||
@@ -175,7 +181,7 @@ public function handle(): int
|
||||
|
||||
// 미분류 샘플
|
||||
if ($unmappedItems->isNotEmpty()) {
|
||||
$this->info("[미분류] 샘플 (최대 10개):");
|
||||
$this->info('[미분류] 샘플 (최대 10개):');
|
||||
foreach ($unmappedItems->take(10) as $item) {
|
||||
$this->line(" - {$item->code}: {$item->name}");
|
||||
}
|
||||
@@ -233,28 +239,78 @@ private function classifyItem(Item $item): ?string
|
||||
$code = $item->code ?? '';
|
||||
$name = $item->name ?? '';
|
||||
|
||||
// B. BD 코드 → P-003 절곡 (최우선)
|
||||
// 0. 글로벌 제외 (FG 완제품, RM 원자재, EST-INSPECTION 서비스)
|
||||
foreach ($this->globalExcludes as $excludePattern) {
|
||||
$prefix = rtrim($excludePattern, '-%');
|
||||
if (str_starts_with($code, $prefix)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 코드 패턴 우선 매핑 (정확한 매칭)
|
||||
// EST-RAW-슬랫-* → P-001
|
||||
if (str_starts_with($code, 'EST-RAW-슬랫-')) {
|
||||
return 'P-001';
|
||||
}
|
||||
|
||||
// EST-RAW-스크린-* → P-002
|
||||
if (str_starts_with($code, 'EST-RAW-스크린-')) {
|
||||
return 'P-002';
|
||||
}
|
||||
|
||||
// BD-* → P-003 절곡
|
||||
if (str_starts_with($code, 'BD-')) {
|
||||
return 'P-003';
|
||||
}
|
||||
|
||||
// C. PT 코드 → P-004 재고생산 (코드 기반 우선)
|
||||
// EST-SMOKE-* → P-003 절곡 (연기차단재는 절곡 공정에서 조립)
|
||||
if (str_starts_with($code, 'EST-SMOKE-')) {
|
||||
return 'P-003';
|
||||
}
|
||||
|
||||
// PT-* → P-004 재고생산
|
||||
if (str_starts_with($code, 'PT-')) {
|
||||
return 'P-004';
|
||||
}
|
||||
|
||||
// C. P-004 재고생산 키워드 체크 (가이드레일, 케이스, 연기차단재, L-Bar, 하단마감, 린텔)
|
||||
// EST-MOTOR/EST-CTRL → 구매품, 공정 없음
|
||||
if (str_starts_with($code, 'EST-MOTOR-') || str_starts_with($code, 'EST-CTRL-')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// EST-SHAFT/EST-PIPE/EST-ANGLE → P-004 재고생산 (조달 품목)
|
||||
if (str_starts_with($code, 'EST-SHAFT-') || str_starts_with($code, 'EST-PIPE-') || str_starts_with($code, 'EST-ANGLE-')) {
|
||||
return 'P-004';
|
||||
}
|
||||
|
||||
// 2. P-004 재고생산 키워드 체크
|
||||
foreach ($this->mappingRules['P-004']['name_keywords'] as $keyword) {
|
||||
if (mb_stripos($name, $keyword) !== false) {
|
||||
return 'P-004';
|
||||
// code_excludes 체크
|
||||
$excluded = false;
|
||||
foreach ($this->mappingRules['P-004']['code_excludes'] ?? [] as $excludePattern) {
|
||||
$prefix = rtrim($excludePattern, '-%');
|
||||
if (str_starts_with($code, $prefix)) {
|
||||
$excluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $excluded) {
|
||||
return 'P-004';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A. 품목명 키워드 기반 분류
|
||||
// P-002 스크린 먼저 체크 (스크린용, 스크린 키워드)
|
||||
// 3. P-003 절곡 키워드 체크
|
||||
foreach ($this->mappingRules['P-003']['name_keywords'] as $keyword) {
|
||||
if (mb_stripos($name, $keyword) !== false) {
|
||||
return 'P-003';
|
||||
}
|
||||
}
|
||||
|
||||
// 4. P-002 스크린 키워드 체크
|
||||
foreach ($this->mappingRules['P-002']['name_keywords'] as $keyword) {
|
||||
if (mb_stripos($name, $keyword) !== false) {
|
||||
// 재고생산 품목 제외
|
||||
$excluded = false;
|
||||
foreach ($this->mappingRules['P-002']['name_excludes'] as $exclude) {
|
||||
if (mb_stripos($name, $exclude) !== false) {
|
||||
@@ -268,10 +324,9 @@ private function classifyItem(Item $item): ?string
|
||||
}
|
||||
}
|
||||
|
||||
// P-001 슬랫 체크 (철재용, 철재, 슬랫 키워드)
|
||||
// 5. P-001 슬랫 키워드 체크
|
||||
foreach ($this->mappingRules['P-001']['name_keywords'] as $keyword) {
|
||||
if (mb_stripos($name, $keyword) !== false) {
|
||||
// 재고생산 품목 제외
|
||||
$excluded = false;
|
||||
foreach ($this->mappingRules['P-001']['name_excludes'] as $exclude) {
|
||||
if (mb_stripos($name, $exclude) !== false) {
|
||||
@@ -285,13 +340,6 @@ private function classifyItem(Item $item): ?string
|
||||
}
|
||||
}
|
||||
|
||||
// P-003 절곡 키워드 체크 (BD 코드 외에 키워드로도 분류)
|
||||
foreach ($this->mappingRules['P-003']['name_keywords'] as $keyword) {
|
||||
if (mb_stripos($name, $keyword) !== false) {
|
||||
return 'P-003';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user