Files
sam-api/app/Console/Commands/Verify5130Calculation.php
권혁성 6d05ab815f feat:테넌트설정 API 및 다수 서비스 개선
- TenantSetting CRUD API 추가
- Calendar, Entertainment, VAT 서비스 개선
- 5130 BOM 계산 로직 수정
- quote_items에 item_type 컬럼 추가
- tenant_settings 테이블 마이그레이션
- Swagger 문서 업데이트
2026-01-26 20:29:22 +09:00

218 lines
8.6 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Helpers\Legacy5130Calculator;
use App\Services\Quote\FormulaEvaluatorService;
use Illuminate\Console\Command;
/**
* 5130 견적 계산 결과 검증 커맨드
*
* 5130 레거시 시스템의 계산 결과와 SAM의 계산 결과가 동일한지 검증합니다.
*
* Usage:
* php artisan migration:verify-5130-calculation
* php artisan migration:verify-5130-calculation --W0=3000 --H0=2500 --type=screen
*/
class Verify5130Calculation extends Command
{
protected $signature = 'migration:verify-5130-calculation
{--W0=2500 : 개구부 폭 (mm)}
{--H0=2000 : 개구부 높이 (mm)}
{--qty=1 : 수량}
{--type=screen : 제품 유형 (screen, steel)}
{--tenant-id=1 : 테넌트 ID}
{--finished-goods= : 완제품 코드 (BOM 계산 시)}
{--verbose-mode : 상세 출력 모드}';
protected $description = '5130 레거시 계산 결과와 SAM 계산 결과 비교 검증';
public function handle(FormulaEvaluatorService $formulaEvaluator): int
{
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
$this->info(' 5130 → SAM 견적 계산 검증');
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
$this->newLine();
// 입력 파라미터
$W0 = (float) $this->option('W0');
$H0 = (float) $this->option('H0');
$qty = (int) $this->option('qty');
$productType = $this->option('type');
$tenantId = (int) $this->option('tenant-id');
$finishedGoodsCode = $this->option('finished-goods');
$verboseMode = $this->option('verbose-mode');
$this->info('📥 입력 파라미터:');
$this->table(
['항목', '값'],
[
['개구부 폭 (W0)', "{$W0} mm"],
['개구부 높이 (H0)', "{$H0} mm"],
['수량 (QTY)', $qty],
['제품 유형', $productType],
['테넌트 ID', $tenantId],
]
);
$this->newLine();
// 1. Legacy5130Calculator로 계산 (5130 호환 모드)
$this->info('🔄 Step 1: Legacy5130Calculator 계산 (5130 호환)');
$legacyResult = Legacy5130Calculator::calculateEstimate($W0, $H0, $qty, $productType);
$this->table(
['항목', '값'],
[
['제작 폭 (W1)', "{$legacyResult['calculated']['W1']} mm"],
['제작 높이 (H1)', "{$legacyResult['calculated']['H1']} mm"],
['면적 (M)', "{$legacyResult['calculated']['area_m2']}"],
['중량 (K)', "{$legacyResult['calculated']['weight_kg']} kg"],
['브라켓 인치', "{$legacyResult['calculated']['bracket_inch']} inch"],
['모터 용량', $legacyResult['motor']['capacity']],
['브라켓 사이즈', $legacyResult['motor']['bracket_dimensions']],
]
);
$this->newLine();
// 2. SAM FormulaEvaluatorService로 계산
$this->info('🔄 Step 2: SAM FormulaEvaluatorService 계산');
if ($finishedGoodsCode) {
// BOM 기반 계산
$samResult = $formulaEvaluator->calculateBomWithDebug(
$finishedGoodsCode,
['W0' => $W0, 'H0' => $H0, 'QTY' => $qty],
$tenantId
);
if (! $samResult['success']) {
$this->error('SAM 계산 실패: '.($samResult['error'] ?? '알 수 없는 오류'));
return Command::FAILURE;
}
$samVariables = $samResult['variables'];
} else {
// 직접 계산 (변수만)
$samVariables = $this->calculateSamVariables($W0, $H0, $productType);
$samResult = ['success' => true, 'variables' => $samVariables];
}
$this->table(
['항목', '값'],
[
['제작 폭 (W1)', ($samVariables['W1'] ?? 'N/A').' mm'],
['제작 높이 (H1)', ($samVariables['H1'] ?? 'N/A').' mm'],
['면적 (M)', round($samVariables['M'] ?? 0, 4).' m²'],
['중량 (K)', round($samVariables['K'] ?? 0, 2).' kg'],
]
);
$this->newLine();
// 3. 결과 비교
$this->info('🔍 Step 3: 결과 비교');
$comparison = [
['W1 (제작 폭)', $legacyResult['calculated']['W1'], $samVariables['W1'] ?? 0],
['H1 (제작 높이)', $legacyResult['calculated']['H1'], $samVariables['H1'] ?? 0],
['M (면적)', $legacyResult['calculated']['area_m2'], round($samVariables['M'] ?? 0, 4)],
['K (중량)', $legacyResult['calculated']['weight_kg'], round($samVariables['K'] ?? 0, 2)],
];
$allMatch = true;
$comparisonTable = [];
foreach ($comparison as [$name, $legacy, $sam]) {
$diff = abs($legacy - $sam);
$percentDiff = $legacy != 0 ? ($diff / abs($legacy)) * 100 : 0;
$match = $percentDiff < 1; // 1% 이내 허용
if (! $match) {
$allMatch = false;
}
$comparisonTable[] = [
$name,
$legacy,
$sam,
round($diff, 4),
round($percentDiff, 2).'%',
$match ? '✅ 일치' : '❌ 불일치',
];
}
$this->table(
['항목', '5130 (Legacy)', 'SAM', '차이', '차이율', '결과'],
$comparisonTable
);
$this->newLine();
// 4. 최종 결과
if ($allMatch) {
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
$this->info(' ✅ 검증 성공: 5130과 SAM 계산 결과가 일치합니다.');
$this->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} else {
$this->error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
$this->error(' ❌ 검증 실패: 5130과 SAM 계산 결과가 불일치합니다.');
$this->error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
}
// 상세 출력 모드
if ($verboseMode) {
$this->newLine();
$this->info('📊 상세 정보 (Legacy5130Calculator):');
$this->line(json_encode($legacyResult, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
if ($finishedGoodsCode && isset($samResult['debug_steps'])) {
$this->newLine();
$this->info('📊 상세 정보 (SAM Debug Steps):');
foreach ($samResult['debug_steps'] as $step) {
$this->line("Step {$step['step']}: {$step['name']}");
$this->line(json_encode($step['data'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
}
}
return $allMatch ? Command::SUCCESS : Command::FAILURE;
}
/**
* SAM 방식으로 변수 계산 (BOM 없이)
*/
private function calculateSamVariables(float $W0, float $H0, string $productType): array
{
// 마진값 결정
if (strtolower($productType) === 'steel') {
$marginW = 110;
$marginH = 350;
} else {
$marginW = 140;
$marginH = 350;
}
$W1 = $W0 + $marginW;
$H1 = $H0 + $marginH;
$M = ($W1 * $H1) / 1000000;
// 중량 계산
if (strtolower($productType) === 'steel') {
$K = $M * 25;
} else {
$K = $M * 2 + ($W0 / 1000) * 14.17;
}
return [
'W0' => $W0,
'H0' => $H0,
'W1' => $W1,
'H1' => $H1,
'W' => $W1,
'H' => $H1,
'M' => $M,
'K' => $K,
];
}
}