numberingService->setContext( $this->tenantId(), $this->apiUserId() ); $number = $this->numberingService->generate('quote', [ 'product_category' => $productCategory, ]); if ($number !== null) { return $number; } return $this->generateLegacy($productCategory); } /** * 기본 견적번호 생성 * * 형식: QT{YYYYMMDD}{NNNN} * 예시: QT202602070001 */ private function generateLegacy(?string $productCategory = null): string { $tenantId = $this->tenantId(); $prefix = 'QT'; $date = now()->format('Ymd'); $lastNo = Quote::withTrashed() ->where('tenant_id', $tenantId) ->where('quote_number', 'like', "{$prefix}{$date}%") ->orderByDesc('quote_number') ->value('quote_number'); if ($lastNo) { $seq = (int) substr($lastNo, -4) + 1; } else { $seq = 1; } return sprintf('%s%s%04d', $prefix, $date, $seq); } /** * 견적번호 미리보기 */ public function preview(?string $productCategory = null): array { $this->numberingService->setContext( $this->tenantId(), $this->apiUserId() ); $rulePreview = $this->numberingService->preview('quote', [ 'product_category' => $productCategory, ]); if ($rulePreview !== null) { return [ 'quote_number' => $rulePreview['preview_number'], 'product_category' => $productCategory ?? Quote::CATEGORY_SCREEN, 'rule_name' => $rulePreview['rule_name'], 'generated_at' => now()->toDateTimeString(), ]; } $quoteNumber = $this->generateLegacy($productCategory); return [ 'quote_number' => $quoteNumber, 'product_category' => $productCategory ?? Quote::CATEGORY_SCREEN, 'generated_at' => now()->toDateTimeString(), ]; } /** * 견적번호 형식 검증 * * 지원 형식: * - 기본: QT{YYYYMMDD}{NNNN} (예: QT202602070001) * - 채번규칙: KD-PR-{YYMMDD}-{NN} (예: KD-PR-260207-01) */ public function validate(string $quoteNumber): bool { return (bool) preg_match('/^(QT\d{8}\d{4,}|KD-[A-Z]{2}-\d{6}-\d{2,})$/', $quoteNumber); } /** * 견적번호 파싱 */ public function parse(string $quoteNumber): ?array { if (! $this->validate($quoteNumber)) { return null; } // 채번규칙 형식: KD-PR-260207-01 if (str_starts_with($quoteNumber, 'KD-')) { $parts = explode('-', $quoteNumber); return [ 'prefix' => $parts[0], 'category_code' => $parts[1], 'date' => $parts[2], 'sequence' => (int) $parts[3], ]; } // 기본 형식: QT202602070001 return [ 'prefix' => 'QT', 'date' => substr($quoteNumber, 2, 8), 'sequence' => (int) substr($quoteNumber, 10), ]; } /** * 견적번호 중복 체크 */ public function isUnique(string $quoteNumber, ?int $excludeId = null): bool { $tenantId = $this->tenantId(); $query = Quote::withTrashed() ->where('tenant_id', $tenantId) ->where('quote_number', $quoteNumber); if ($excludeId) { $query->where('id', '!=', $excludeId); } return ! $query->exists(); } }