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) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,22 @@ public function store(int $id, Request $request)
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
$item = $this->getItem($id);
|
||||
$inputItems = $request->input('items', []);
|
||||
$tenantId = app('tenant_id');
|
||||
|
||||
// child_item_id 존재 검증
|
||||
$childIds = collect($inputItems)->pluck('child_item_id')->filter()->unique()->values()->toArray();
|
||||
if (! empty($childIds)) {
|
||||
$validIds = Item::where('tenant_id', $tenantId)
|
||||
->whereIn('id', $childIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
$invalidIds = array_diff($childIds, $validIds);
|
||||
if (! empty($invalidIds)) {
|
||||
throw new \InvalidArgumentException(
|
||||
__('error.bom.invalid_child_items', ['ids' => implode(', ', $invalidIds)])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$existingBom = $item->bom ?? [];
|
||||
$existingMap = collect($existingBom)->keyBy('child_item_id')->toArray();
|
||||
@@ -273,6 +289,22 @@ public function replace(int $id, Request $request)
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
$item = $this->getItem($id);
|
||||
$inputItems = $request->input('items', []);
|
||||
$tenantId = app('tenant_id');
|
||||
|
||||
// child_item_id 존재 검증
|
||||
$childIds = collect($inputItems)->pluck('child_item_id')->filter()->unique()->values()->toArray();
|
||||
if (! empty($childIds)) {
|
||||
$validIds = Item::where('tenant_id', $tenantId)
|
||||
->whereIn('id', $childIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
$invalidIds = array_diff($childIds, $validIds);
|
||||
if (! empty($invalidIds)) {
|
||||
throw new \InvalidArgumentException(
|
||||
__('error.bom.invalid_child_items', ['ids' => implode(', ', $invalidIds)])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$newBom = [];
|
||||
foreach ($inputItems as $inputItem) {
|
||||
|
||||
@@ -1587,7 +1587,14 @@ private function calculateKyungdongBom(
|
||||
$H0 = (float) ($inputVariables['H0'] ?? 0);
|
||||
$QTY = (int) ($inputVariables['QTY'] ?? 1);
|
||||
$bracketInch = $inputVariables['bracket_inch'] ?? '5';
|
||||
$productType = $inputVariables['product_type'] ?? 'screen';
|
||||
// product_type: 프론트 입력값 우선, 없으면 FG item_category에서 자동 추론
|
||||
$finishedGoods = $this->getItemDetails($finishedGoodsCode, $tenantId);
|
||||
$itemCategory = $finishedGoods['item_category'] ?? null;
|
||||
$productType = $inputVariables['product_type'] ?? match (true) {
|
||||
$itemCategory === '철재' => 'steel',
|
||||
str_contains($itemCategory ?? '', '슬랫') => 'slat',
|
||||
default => 'screen',
|
||||
};
|
||||
|
||||
$this->addDebugStep(1, '입력값수집', [
|
||||
'formulas' => [
|
||||
@@ -1602,9 +1609,7 @@ private function calculateKyungdongBom(
|
||||
],
|
||||
]);
|
||||
|
||||
// Step 2: 완제품 조회 (경동 전용 계산은 완제품 없이도 동작)
|
||||
$finishedGoods = $this->getItemDetails($finishedGoodsCode, $tenantId);
|
||||
|
||||
// Step 2: 완제품 정보 활용 (Step 1에서 이미 조회됨)
|
||||
if ($finishedGoods) {
|
||||
$this->addDebugStep(2, '완제품선택', [
|
||||
'code' => $finishedGoods['code'],
|
||||
@@ -1672,6 +1677,12 @@ private function calculateKyungdongBom(
|
||||
$finishingType = $inputVariables['finishing_type'] ?? 'SUS';
|
||||
$installationType = $inputVariables['installation_type'] ?? '벽면형';
|
||||
|
||||
// 모터 전압: 프론트 MP(single/three) → motor_voltage(220V/380V) 매핑
|
||||
$motorVoltage = $inputVariables['motor_voltage'] ?? match ($inputVariables['MP'] ?? 'single') {
|
||||
'three' => '380V',
|
||||
default => '220V',
|
||||
};
|
||||
|
||||
$calculatedVariables = array_merge($inputVariables, [
|
||||
'W0' => $W0,
|
||||
'H0' => $H0,
|
||||
@@ -1687,6 +1698,7 @@ private function calculateKyungdongBom(
|
||||
'product_model' => $productModel,
|
||||
'finishing_type' => $finishingType,
|
||||
'installation_type' => $installationType,
|
||||
'motor_voltage' => $motorVoltage,
|
||||
]);
|
||||
|
||||
$this->addDebugStep(3, '변수계산', [
|
||||
|
||||
@@ -22,49 +22,12 @@ public function __construct(?EstimatePriceService $priceService = null)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 아이템 매핑 헬퍼 메서드 및 상수
|
||||
// 아이템 매핑 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 고정 매핑 아이템 코드 → ID 테이블
|
||||
* items master에서 조회한 고정 값들
|
||||
*/
|
||||
private const FIXED_ITEM_MAPPINGS = [
|
||||
// 연기차단재
|
||||
'EST-SMOKE-케이스용' => 14912,
|
||||
'EST-SMOKE-레일용' => 14911,
|
||||
// 하장바
|
||||
'00035' => 14158, // SUS
|
||||
'00036' => 14159, // EGI
|
||||
// 보강평철
|
||||
'BD-보강평철-50' => 14790,
|
||||
// 무게평철12T (평철12T와 동일)
|
||||
'00021' => 14147,
|
||||
// 환봉 (파이별)
|
||||
'90201' => 14407, // 30파이
|
||||
'90202' => 14408, // 35파이
|
||||
'90203' => 14409, // 45파이
|
||||
'90204' => 14410, // 50파이
|
||||
// 조인트바
|
||||
'800361' => 14733,
|
||||
// 검사비
|
||||
'EST-INSPECTION' => 14913,
|
||||
// 제어기
|
||||
'EST-CTRL-노출형' => 14861,
|
||||
'EST-CTRL-매립형' => 14862,
|
||||
'EST-CTRL-뒷박스' => 14863,
|
||||
// 파이프
|
||||
'EST-PIPE-1.4-3000' => 14900,
|
||||
'EST-PIPE-1.4-6000' => 14901,
|
||||
// 모터받침 앵글
|
||||
'EST-ANGLE-BRACKET-스크린용' => 14907,
|
||||
'EST-ANGLE-BRACKET-철제300K' => 14908,
|
||||
'EST-ANGLE-BRACKET-철제400K' => 14909,
|
||||
'EST-ANGLE-BRACKET-철제800K' => 14910,
|
||||
];
|
||||
|
||||
/**
|
||||
* items master에서 코드로 아이템 조회 (캐싱 적용, id + name)
|
||||
* seeder 재실행 시 ID가 변경될 수 있으므로 항상 DB에서 동적 조회
|
||||
*
|
||||
* @param string $code 아이템 코드
|
||||
* @return array{id: int|null, name: string|null}
|
||||
@@ -76,16 +39,6 @@ private function lookupItem(string $code): array
|
||||
return $cache[$code];
|
||||
}
|
||||
|
||||
// 1. 고정 매핑에 ID만 있는 경우 → DB에서 name 조회
|
||||
if (isset(self::FIXED_ITEM_MAPPINGS[$code])) {
|
||||
$fixedId = self::FIXED_ITEM_MAPPINGS[$code];
|
||||
$name = \App\Models\Items\Item::where('id', $fixedId)->value('name');
|
||||
$cache[$code] = ['id' => $fixedId, 'name' => $name];
|
||||
|
||||
return $cache[$code];
|
||||
}
|
||||
|
||||
// 2. DB에서 동적 조회 (BD-*, EST-* 패턴)
|
||||
$item = \App\Models\Items\Item::where('tenant_id', self::TENANT_ID)
|
||||
->where('code', $code)
|
||||
->first(['id', 'name']);
|
||||
@@ -968,8 +921,9 @@ public function calculatePartItems(array $params): array
|
||||
], $itemCode);
|
||||
}
|
||||
|
||||
// 5. 조인트바 (슬랫 전용, 5130: price × col76, QTY 미적용)
|
||||
if ($productType === 'slat') {
|
||||
// 5. 조인트바 (슬랫/철재 공통, 5130: price × col76, QTY 미적용)
|
||||
// 5130 레거시: 철재(KQTS01)도 슬랫 공정에서 조인트바 사용
|
||||
if (in_array($productType, ['slat', 'steel'])) {
|
||||
$jointBarQty = (int) ($params['joint_bar_qty'] ?? 0);
|
||||
if ($jointBarQty > 0) {
|
||||
$jointBarPrice = $this->getRawMaterialPrice('조인트바');
|
||||
@@ -1044,20 +998,21 @@ public function calculateDynamicItems(array $inputs): array
|
||||
], 'EST-INSPECTION');
|
||||
}
|
||||
|
||||
// 1. 주자재 (스크린 또는 슬랫)
|
||||
if ($productType === 'slat') {
|
||||
$materialResult = $this->calculateSlatPrice($width, $height);
|
||||
$materialName = '주자재(슬랫)';
|
||||
// 슬랫 타입에 따른 코드 (기본: 방화)
|
||||
$slatType = $inputs['slat_type'] ?? '방화';
|
||||
$materialCode = "EST-RAW-슬랫-{$slatType}";
|
||||
} else {
|
||||
// 1. 주자재 (스크린 = 실리카, 철재/슬랫 = EGI 코일 슬랫)
|
||||
// 5130: KQTS01(철재)도 슬랫 공정에서 EGI 코일로 슬랫 생산 (viewSlatWork.php 참조)
|
||||
if ($productType === 'screen') {
|
||||
$materialResult = $this->calculateScreenPrice($width, $height);
|
||||
$materialName = '주자재(스크린)';
|
||||
// 스크린 타입에 따른 코드 (기본: 실리카)
|
||||
$screenType = $inputs['screen_type'] ?? '실리카';
|
||||
$materialCode = "EST-RAW-스크린-{$screenType}";
|
||||
} else {
|
||||
// steel, slat 모두 슬랫(EGI 코일) 사용
|
||||
$materialResult = $this->calculateSlatPrice($width, $height);
|
||||
$materialName = '주자재(슬랫)';
|
||||
$slatType = $inputs['slat_type'] ?? '방화';
|
||||
$materialCode = "EST-RAW-슬랫-{$slatType}";
|
||||
}
|
||||
|
||||
$items[] = $this->withItemMapping([
|
||||
'category' => 'material',
|
||||
'item_name' => $materialName,
|
||||
|
||||
@@ -76,7 +76,7 @@ public function run(): void
|
||||
|
||||
private function cleanup(): void
|
||||
{
|
||||
$this->command->info('CLEANUP: tenant_id=' . $this->tenantId . ' 데이터 삭제');
|
||||
$this->command->info('CLEANUP: tenant_id='.$this->tenantId.' 데이터 삭제');
|
||||
|
||||
// FK 역순 삭제
|
||||
// prices - tenant_id로 직접 삭제
|
||||
@@ -100,7 +100,7 @@ private function cleanup(): void
|
||||
->whereNotNull('parent_id')
|
||||
->delete();
|
||||
$c = DB::table('categories')->where('tenant_id', $this->tenantId)->delete();
|
||||
$this->command->info(" categories: " . count($catIds) . "건 삭제");
|
||||
$this->command->info(' categories: '.count($catIds).'건 삭제');
|
||||
|
||||
// entity_relationships
|
||||
$c = DB::table('entity_relationships')->where('tenant_id', $this->tenantId)->delete();
|
||||
@@ -140,7 +140,7 @@ private function seedItemPages(): void
|
||||
$this->pageMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_pages: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_pages: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItemSections(): void
|
||||
@@ -158,7 +158,7 @@ private function seedItemSections(): void
|
||||
$this->sectionMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_sections: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_sections: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItemFields(): void
|
||||
@@ -176,7 +176,7 @@ private function seedItemFields(): void
|
||||
$this->fieldMap[$originalId] = $newId;
|
||||
}
|
||||
|
||||
$this->command->info(" item_fields: " . count($rows) . "건 삽입");
|
||||
$this->command->info(' item_fields: '.count($rows).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedEntityRelationships(): void
|
||||
@@ -197,6 +197,7 @@ private function seedEntityRelationships(): void
|
||||
|
||||
if ($data['parent_id'] === null || $data['child_id'] === null) {
|
||||
$this->command->warn(" entity_relationships: 매핑 실패 건 스킵 (parent_type={$row['parent_type']}, child_type={$row['child_type']})");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -314,6 +315,7 @@ private function seedCategories(): void
|
||||
|
||||
if (! isset($this->categoryMap[$parentOriginalId])) {
|
||||
$nextRemaining[] = $row;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -329,10 +331,10 @@ private function seedCategories(): void
|
||||
}
|
||||
|
||||
if (! empty($remaining)) {
|
||||
$this->command->warn(" categories: " . count($remaining) . "건 매핑 실패 (depth 초과)");
|
||||
$this->command->warn(' categories: '.count($remaining).'건 매핑 실패 (depth 초과)');
|
||||
}
|
||||
|
||||
$this->command->info(" categories: " . count($this->categoryMap) . "건 삽입");
|
||||
$this->command->info(' categories: '.count($this->categoryMap).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedItems(): void
|
||||
@@ -369,7 +371,57 @@ private function seedItems(): void
|
||||
->pluck('id', 'code')
|
||||
->toArray();
|
||||
|
||||
$this->command->info(" items: {$total}건 삽입 (itemMap: " . count($this->itemMap) . "건)");
|
||||
$this->command->info(" items: {$total}건 삽입 (itemMap: ".count($this->itemMap).'건)');
|
||||
|
||||
// BOM의 child_item_code → child_item_id 변환
|
||||
$this->remapBomCodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* BOM JSON의 child_item_code를 실제 child_item_id로 변환
|
||||
*
|
||||
* items.json의 bom 필드가 child_item_code(품목코드) 기반으로 저장되어 있으므로,
|
||||
* INSERT 후 itemMap(code→id)을 이용해 child_item_id로 교체한다.
|
||||
*/
|
||||
private function remapBomCodes(): void
|
||||
{
|
||||
$bomItems = DB::table('items')
|
||||
->where('tenant_id', $this->tenantId)
|
||||
->whereNotNull('bom')
|
||||
->get(['id', 'code', 'bom']);
|
||||
|
||||
$updated = 0;
|
||||
|
||||
foreach ($bomItems as $item) {
|
||||
$bom = json_decode($item->bom, true);
|
||||
if (! is_array($bom)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = false;
|
||||
foreach ($bom as &$entry) {
|
||||
// child_item_code → child_item_id 변환
|
||||
if (isset($entry['child_item_code'])) {
|
||||
$code = $entry['child_item_code'];
|
||||
if (isset($this->itemMap[$code])) {
|
||||
$entry['child_item_id'] = $this->itemMap[$code];
|
||||
unset($entry['child_item_code']);
|
||||
$changed = true;
|
||||
} else {
|
||||
$this->command->warn(" BOM 매핑 실패: {$item->code} → {$code} (품목 미존재)");
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($entry);
|
||||
|
||||
if ($changed) {
|
||||
DB::table('items')->where('id', $item->id)
|
||||
->update(['bom' => json_encode($bom, JSON_UNESCAPED_UNICODE)]);
|
||||
$updated++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(" BOM code→id 매핑: {$updated}건 변환");
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -399,6 +451,7 @@ private function seedItemDetails(): void
|
||||
$code = $originalIdToCode[$originalItemId] ?? null;
|
||||
if ($code === null || ! isset($this->itemMap[$code])) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -417,7 +470,7 @@ private function seedItemDetails(): void
|
||||
}
|
||||
|
||||
$this->expectedCounts['item_details'] = count($insertData);
|
||||
$this->command->info(" item_details: " . count($insertData) . "건 삽입");
|
||||
$this->command->info(' item_details: '.count($insertData).'건 삽입');
|
||||
}
|
||||
|
||||
private function seedPrices(): void
|
||||
@@ -445,6 +498,7 @@ private function seedPrices(): void
|
||||
$code = $originalIdToCode[$originalItemId] ?? null;
|
||||
if ($code === null || ! isset($this->itemMap[$code])) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -463,7 +517,7 @@ private function seedPrices(): void
|
||||
}
|
||||
|
||||
$this->expectedCounts['prices'] = count($insertData);
|
||||
$this->command->info(" prices: " . count($insertData) . "건 삽입");
|
||||
$this->command->info(' prices: '.count($insertData).'건 삽입');
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -528,7 +582,7 @@ private function verifyRelationshipIntegrity(): void
|
||||
if ($orphans > 0) {
|
||||
$this->command->warn(" entity_relationships 참조 무결성: {$orphans}건 고아 레코드");
|
||||
} else {
|
||||
$this->command->info(" entity_relationships 참조 무결성: OK");
|
||||
$this->command->info(' entity_relationships 참조 무결성: OK');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,12 +601,12 @@ private function printSummary(): void
|
||||
{
|
||||
$this->command->newLine();
|
||||
$this->command->info('=== 경동기업 품목 기준 데이터 시더 완료 ===');
|
||||
$this->command->info(" 매핑 현황:");
|
||||
$this->command->info(" pageMap: " . count($this->pageMap) . "건");
|
||||
$this->command->info(" sectionMap: " . count($this->sectionMap) . "건");
|
||||
$this->command->info(" fieldMap: " . count($this->fieldMap) . "건");
|
||||
$this->command->info(" categoryMap: " . count($this->categoryMap) . "건");
|
||||
$this->command->info(" itemMap: " . count($this->itemMap) . "건");
|
||||
$this->command->info(' 매핑 현황:');
|
||||
$this->command->info(' pageMap: '.count($this->pageMap).'건');
|
||||
$this->command->info(' sectionMap: '.count($this->sectionMap).'건');
|
||||
$this->command->info(' fieldMap: '.count($this->fieldMap).'건');
|
||||
$this->command->info(' categoryMap: '.count($this->categoryMap).'건');
|
||||
$this->command->info(' itemMap: '.count($this->itemMap).'건');
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -561,11 +615,11 @@ private function printSummary(): void
|
||||
|
||||
private function loadJson(string $filename): array
|
||||
{
|
||||
$path = $this->dataPath . '/' . $filename;
|
||||
$path = $this->dataPath.'/'.$filename;
|
||||
|
||||
if (! file_exists($path)) {
|
||||
$this->command->error("JSON 파일 없음: {$path}");
|
||||
$this->command->error("먼저 php artisan kyungdong:export-item-master 를 실행하세요.");
|
||||
$this->command->error('먼저 php artisan kyungdong:export-item-master 를 실행하세요.');
|
||||
|
||||
throw new \RuntimeException("JSON 파일 없음: {$path}");
|
||||
}
|
||||
|
||||
@@ -1107,13 +1107,13 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "RM",
|
||||
"code": "20011",
|
||||
"name": "sus1.2*1219*3000 P\/L",
|
||||
"name": "sus1.2*1219*3000 P/L",
|
||||
"unit": "EA",
|
||||
"category_id": 364,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
"attributes": "{\"spec\": \" \", \"width\": 1219, \"length\": 3000, \"item_div\": \"[원재료]\", \"thickness\": 1.2, \"legacy_num\": 49, \"100_item_name\": \"SUS(스테인리스)\", \"legacy_source\": \"KDunitprice\", \"101_specification_1\": \"1.2\", \"102_specification_2\": \"1219\", \"103_specification_3\": \"3000 P\/L\"}",
|
||||
"attributes": "{\"spec\": \" \", \"width\": 1219, \"length\": 3000, \"item_div\": \"[원재료]\", \"thickness\": 1.2, \"legacy_num\": 49, \"100_item_name\": \"SUS(스테인리스)\", \"legacy_source\": \"KDunitprice\", \"101_specification_1\": \"1.2\", \"102_specification_2\": \"1219\", \"103_specification_3\": \"3000 P/L\"}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
"description": null,
|
||||
@@ -2533,7 +2533,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80015",
|
||||
"name": "P\/S버튼",
|
||||
"name": "P/S버튼",
|
||||
"unit": "EA",
|
||||
"category_id": 329,
|
||||
"process_type": null,
|
||||
@@ -2717,7 +2717,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80026",
|
||||
"name": "A\/L무지개셔터",
|
||||
"name": "A/L무지개셔터",
|
||||
"unit": " ",
|
||||
"category_id": 355,
|
||||
"process_type": null,
|
||||
@@ -3177,7 +3177,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80047",
|
||||
"name": "SUS 1.5T (절곡가공\/㎡)",
|
||||
"name": "SUS 1.5T (절곡가공/㎡)",
|
||||
"unit": "㎡",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3200,7 +3200,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80047-1",
|
||||
"name": "SUS 1.5T (절곡가공\/㎏)",
|
||||
"name": "SUS 1.5T (절곡가공/㎏)",
|
||||
"unit": "kg",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3246,7 +3246,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80048",
|
||||
"name": "EGI 1.2 T (절곡가공\/㎡)",
|
||||
"name": "EGI 1.2 T (절곡가공/㎡)",
|
||||
"unit": "㎡",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3269,7 +3269,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80048-1",
|
||||
"name": "EGI 1.2 T (절곡가공\/㎏)",
|
||||
"name": "EGI 1.2 T (절곡가공/㎏)",
|
||||
"unit": "kg",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3338,7 +3338,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80051",
|
||||
"name": "SUS 1.2T (절곡가공\/㎡)",
|
||||
"name": "SUS 1.2T (절곡가공/㎡)",
|
||||
"unit": "㎡",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3361,7 +3361,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80051-1",
|
||||
"name": "SUS 1.2T (절곡가공\/㎏)",
|
||||
"name": "SUS 1.2T (절곡가공/㎏)",
|
||||
"unit": "kg",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3384,7 +3384,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80051-2",
|
||||
"name": "SUS 1.2T (미러 절곡가공\/㎡)",
|
||||
"name": "SUS 1.2T (미러 절곡가공/㎡)",
|
||||
"unit": "kg",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3407,7 +3407,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80052",
|
||||
"name": "EGI 1.6 T (절곡가공\/㎡)",
|
||||
"name": "EGI 1.6 T (절곡가공/㎡)",
|
||||
"unit": "㎡",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -3430,7 +3430,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80052-1",
|
||||
"name": "EGI 1.6 T (절곡가공\/㎏)",
|
||||
"name": "EGI 1.6 T (절곡가공/㎏)",
|
||||
"unit": "kg",
|
||||
"category_id": 320,
|
||||
"process_type": null,
|
||||
@@ -4902,13 +4902,13 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80113",
|
||||
"name": "P.B-S\/W",
|
||||
"name": "P.B-S/W",
|
||||
"unit": " ",
|
||||
"category_id": 329,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
"attributes": "{\"spec\": \"P.B-S\/W 2P\", \"item_div\": \"[상품]\", \"Part_type\": \"구매 부품(Purchased Part)\", \"legacy_num\": 214, \"legacy_source\": \"KDunitprice\"}",
|
||||
"attributes": "{\"spec\": \"P.B-S/W 2P\", \"item_div\": \"[상품]\", \"Part_type\": \"구매 부품(Purchased Part)\", \"legacy_num\": 214, \"legacy_source\": \"KDunitprice\"}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
"description": null,
|
||||
@@ -5684,7 +5684,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "80152",
|
||||
"name": "P\/B스위치",
|
||||
"name": "P/B스위치",
|
||||
"unit": " ",
|
||||
"category_id": 329,
|
||||
"process_type": null,
|
||||
@@ -7731,13 +7731,13 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "CS",
|
||||
"code": "90716",
|
||||
"name": "모터A\/S",
|
||||
"name": "모터A/S",
|
||||
"unit": " ",
|
||||
"category_id": 366,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
"attributes": "{\"spec\": \" \", \"item_div\": \"[무형상품]\", \"item_name\": \"모터A\/S\", \"legacy_num\": 338, \"legacy_source\": \"KDunitprice\"}",
|
||||
"attributes": "{\"spec\": \" \", \"item_div\": \"[무형상품]\", \"item_name\": \"모터A/S\", \"legacy_num\": 338, \"legacy_source\": \"KDunitprice\"}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
"description": null,
|
||||
@@ -10675,7 +10675,7 @@
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "M0019",
|
||||
"name": "P\/S세트",
|
||||
"name": "P/S세트",
|
||||
"unit": " ",
|
||||
"category_id": 329,
|
||||
"process_type": null,
|
||||
@@ -13831,7 +13831,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}, {\"quantity\": 2, \"child_item_id\": 13170}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}, {\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}]",
|
||||
"attributes": "{\"model_name\": \"KSS01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 12}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13854,7 +13854,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}, {\"quantity\": 2, \"child_item_id\": 13170}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}, {\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}]",
|
||||
"attributes": "{\"model_name\": \"KSS01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 13}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13877,7 +13877,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 14}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13900,7 +13900,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 15}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13923,7 +13923,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 16}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13946,7 +13946,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 17}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13969,7 +13969,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KWE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 18}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -13992,7 +13992,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KWE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 19}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14015,7 +14015,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KWE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 20}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14038,7 +14038,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KWE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 21}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14061,7 +14061,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KQTS01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"철재\", \"legacy_model_id\": 22}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14084,7 +14084,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KQTS01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"철재\", \"legacy_model_id\": 23}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14107,7 +14107,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KTE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"철재\", \"legacy_model_id\": 24}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14130,7 +14130,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KTE01\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"철재\", \"legacy_model_id\": 25}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14153,7 +14153,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KTE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"철재\", \"legacy_model_id\": 26}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14176,7 +14176,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "철재",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13170}, {\"quantity\": 1, \"child_item_id\": 13174}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}]",
|
||||
"attributes": "{\"model_name\": \"KTE01\", \"legacy_source\": \"models\", \"finishing_type\": \"EGI마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"철재\", \"legacy_model_id\": 27}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14199,7 +14199,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSS02\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"측면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 28}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -14222,7 +14222,7 @@
|
||||
"category_id": 311,
|
||||
"process_type": null,
|
||||
"item_category": "스크린",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_id\": 13174}, {\"quantity\": 2, \"child_item_id\": 13170}, {\"quantity\": 2, \"child_item_id\": 13175}]",
|
||||
"bom": "[{\"quantity\": 1, \"child_item_code\": \"PT-하단마감재\"}, {\"quantity\": 2, \"child_item_code\": \"PT-가이드레일\"}, {\"quantity\": 2, \"child_item_code\": \"PT-L-BAR\"}]",
|
||||
"attributes": "{\"model_name\": \"KSS02\", \"legacy_source\": \"models\", \"finishing_type\": \"SUS마감\", \"guiderail_type\": \"벽면형\", \"major_category\": \"스크린\", \"legacy_model_id\": 29}",
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
@@ -15254,7 +15254,7 @@
|
||||
"code": "BD-케이스-500*350",
|
||||
"name": "케이스 500*350",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15277,7 +15277,7 @@
|
||||
"code": "BD-케이스-500*380",
|
||||
"name": "케이스 500*380",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15300,7 +15300,7 @@
|
||||
"code": "BD-케이스-600*500",
|
||||
"name": "케이스 600*500",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15323,7 +15323,7 @@
|
||||
"code": "BD-케이스-600*550",
|
||||
"name": "케이스 600*550",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15346,7 +15346,7 @@
|
||||
"code": "BD-케이스-650*500",
|
||||
"name": "케이스 650*500",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15369,7 +15369,7 @@
|
||||
"code": "BD-케이스-650*550",
|
||||
"name": "케이스 650*550",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15392,7 +15392,7 @@
|
||||
"code": "BD-케이스-700*550",
|
||||
"name": "케이스 700*550",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15415,7 +15415,7 @@
|
||||
"code": "BD-케이스-700*600",
|
||||
"name": "케이스 700*600",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15438,7 +15438,7 @@
|
||||
"code": "BD-케이스-780*600",
|
||||
"name": "케이스 780*600",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -15461,7 +15461,7 @@
|
||||
"code": "BD-케이스-780*650",
|
||||
"name": "케이스 780*650",
|
||||
"unit": "EA",
|
||||
"category_id": 368,
|
||||
"category_id": 372,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
@@ -17938,5 +17938,28 @@
|
||||
"updated_at": "2026-02-04 22:20:41",
|
||||
"deleted_at": null,
|
||||
"_original_id": 14132
|
||||
},
|
||||
{
|
||||
"tenant_id": 287,
|
||||
"item_type": "PT",
|
||||
"code": "EST-INSPECTION",
|
||||
"name": "검사비",
|
||||
"unit": "EA",
|
||||
"category_id": 366,
|
||||
"process_type": null,
|
||||
"item_category": null,
|
||||
"bom": null,
|
||||
"attributes": null,
|
||||
"attributes_archive": null,
|
||||
"options": null,
|
||||
"description": "견적 검사비",
|
||||
"is_active": 1,
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"deleted_by": null,
|
||||
"created_at": "2026-02-19 14:30:00",
|
||||
"updated_at": "2026-02-19 14:30:00",
|
||||
"deleted_at": null,
|
||||
"_original_id": 14133
|
||||
}
|
||||
]
|
||||
@@ -38,6 +38,12 @@
|
||||
'invalid_status_transition' => 'Cannot change status from the current state.',
|
||||
],
|
||||
|
||||
// BOM related
|
||||
'bom' => [
|
||||
'line_not_found' => 'BOM line not found.',
|
||||
'invalid_child_items' => 'Invalid child item IDs: :ids',
|
||||
],
|
||||
|
||||
// BOM template related
|
||||
'bom_template' => [
|
||||
'not_found' => 'No applicable BOM template found.',
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
'invalid_status_transition' => '현재 상태에서는 변경할 수 없습니다.',
|
||||
],
|
||||
|
||||
// BOM 관련
|
||||
'bom' => [
|
||||
'line_not_found' => 'BOM 라인을 찾을 수 없습니다.',
|
||||
'invalid_child_items' => '존재하지 않는 품목이 포함되어 있습니다. (ID: :ids)',
|
||||
],
|
||||
|
||||
// BOM 템플릿 관련
|
||||
'bom_template' => [
|
||||
'not_found' => '적용 가능한 BOM 템플릿을 찾을 수 없습니다.',
|
||||
|
||||
Reference in New Issue
Block a user