From 7726b8193389d0bc206f76ccc8cd8bf410436523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 22 Mar 2026 22:04:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[barobill]=20Fake=20SOAP=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20+=20=EA=B2=BD=EB=8F=99=20=EB=8B=A8?= =?UTF-8?q?=EA=B0=80=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=AA=A8=EB=8D=B8/?= =?UTF-8?q?=EC=8B=9C=EB=8D=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BarobillFakeSoapService: SOAP 호출 없이 샘플 데이터 반환 (46개 메서드) - AppServiceProvider: BAROBILL_FAKE_MODE 시 자동 바인딩 전환 - services.php: fake_mode 설정 추가 - KdPriceTable 모델 + KdPriceTableSeeder (5130 레거시 마이그레이션) --- app/Models/Kyungdong/KdPriceTable.php | 341 +++++++++++ app/Providers/AppServiceProvider.php | 8 + .../Barobill/BarobillFakeSoapService.php | 533 +++++++++++++++++ config/services.php | 1 + .../seeders/Kyungdong/KdPriceTableSeeder.php | 565 ++++++++++++++++++ 5 files changed, 1448 insertions(+) create mode 100644 app/Models/Kyungdong/KdPriceTable.php create mode 100644 app/Services/Barobill/BarobillFakeSoapService.php create mode 100644 database/seeders/Kyungdong/KdPriceTableSeeder.php diff --git a/app/Models/Kyungdong/KdPriceTable.php b/app/Models/Kyungdong/KdPriceTable.php new file mode 100644 index 00000000..fa5c682d --- /dev/null +++ b/app/Models/Kyungdong/KdPriceTable.php @@ -0,0 +1,341 @@ + 'decimal:2', + 'raw_data' => 'array', + 'is_active' => 'boolean', + ]; + + // ========================================================================= + // Scopes + // ========================================================================= + + /** + * 테이블 유형으로 필터링 + */ + public function scopeOfType(Builder $query, string $type): Builder + { + return $query->where('table_type', $type); + } + + /** + * 활성 데이터만 + */ + public function scopeActive(Builder $query): Builder + { + return $query->where('is_active', true); + } + + /** + * 모터 단가 조회 + */ + public function scopeMotor(Builder $query): Builder + { + return $query->ofType(self::TYPE_MOTOR)->active(); + } + + /** + * 샤프트 단가 조회 + */ + public function scopeShaft(Builder $query): Builder + { + return $query->ofType(self::TYPE_SHAFT)->active(); + } + + /** + * 파이프 단가 조회 + */ + public function scopePipeType(Builder $query): Builder + { + return $query->ofType(self::TYPE_PIPE)->active(); + } + + /** + * 앵글 단가 조회 + */ + public function scopeAngle(Builder $query): Builder + { + return $query->ofType(self::TYPE_ANGLE)->active(); + } + + // ========================================================================= + // Static Query Methods + // ========================================================================= + + /** + * 모터 단가 조회 + * + * @param string $motorCapacity 모터 용량 (150K, 300K, 400K, 500K, 600K, 800K, 1000K) + */ + public static function getMotorPrice(string $motorCapacity): float + { + $record = self::motor() + ->where('category', $motorCapacity) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 제어기 단가 조회 + * + * @param string $controllerType 제어기 타입 (매립형, 노출형, 뒷박스) + */ + public static function getControllerPrice(string $controllerType): float + { + $record = self::motor() + ->where('category', $controllerType) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 샤프트 단가 조회 + * + * @param string $size 사이즈 (3, 4, 5인치) + * @param float $length 길이 (m 단위) + */ + public static function getShaftPrice(string $size, float $length): float + { + // 길이를 소수점 1자리 문자열로 변환 (DB 저장 형식: '3.0', '4.0') + $lengthStr = number_format($length, 1, '.', ''); + + $record = self::shaft() + ->where('spec1', $size) + ->where('spec2', $lengthStr) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 파이프 단가 조회 + * + * @param string $thickness 두께 (1.4 등) + * @param int $length 길이 (3000, 6000) + */ + public static function getPipePrice(string $thickness, int $length): float + { + $record = self::pipeType() + ->where('spec1', $thickness) + ->where('spec2', (string) $length) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 앵글 단가 조회 + * + * @param string $type 타입 (스크린용, 철재용) + * @param string $bracketSize 브라켓크기 (530*320, 600*350, 690*390) + * @param string $angleType 앵글타입 (앵글3T, 앵글4T) + */ + public static function getAnglePrice(string $type, string $bracketSize, string $angleType): float + { + $record = self::angle() + ->where('category', $type) + ->where('spec1', $bracketSize) + ->where('spec2', $angleType) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 원자재 단가 조회 + * + * @param string $materialName 원자재명 (실리카, 스크린 등) + */ + public static function getRawMaterialPrice(string $materialName): float + { + $record = self::ofType(self::TYPE_RAW_MATERIAL) + ->active() + ->where('item_name', $materialName) + ->first(); + + return (float) ($record?->unit_price ?? 0); + } + + // ========================================================================= + // BDmodels 단가 조회 (절곡품) + // ========================================================================= + + /** + * BDmodels 스코프 + */ + public function scopeBdmodels(Builder $query): Builder + { + return $query->ofType(self::TYPE_BDMODELS)->active(); + } + + /** + * BDmodels 단가 조회 (케이스, 가이드레일, 하단마감재 등) + * + * @param string $secondItem 부품분류 (케이스, 가이드레일, 하단마감재, L-BAR 등) + * @param string|null $modelName 모델코드 (KSS01, KWS01 등) + * @param string|null $finishingType 마감재질 (SUS, EGI) + * @param string|null $spec 규격 (120*70, 650*550 등) + */ + public static function getBDModelPrice( + string $secondItem, + ?string $modelName = null, + ?string $finishingType = null, + ?string $spec = null + ): float { + $query = self::bdmodels()->where('category', $secondItem); + + if ($modelName) { + $query->where('item_code', $modelName); + } + + if ($finishingType) { + $query->where('spec1', $finishingType); + } + + if ($spec) { + $query->where('spec2', $spec); + } + + $record = $query->first(); + + return (float) ($record?->unit_price ?? 0); + } + + /** + * 케이스 단가 조회 + * + * @param string $spec 케이스 규격 (500*380, 650*550 등) + */ + public static function getCasePrice(string $spec): float + { + return self::getBDModelPrice('케이스', null, null, $spec); + } + + /** + * 가이드레일 단가 조회 + * + * @param string $modelName 모델코드 (KSS01 등) + * @param string $finishingType 마감재질 (SUS, EGI) + * @param string $spec 규격 (120*70, 120*100) + */ + public static function getGuideRailPrice(string $modelName, string $finishingType, string $spec): float + { + return self::getBDModelPrice('가이드레일', $modelName, $finishingType, $spec); + } + + /** + * 하단마감재(하장바) 단가 조회 + * + * @param string $modelName 모델코드 + * @param string $finishingType 마감재질 + */ + public static function getBottomBarPrice(string $modelName, string $finishingType): float + { + return self::getBDModelPrice('하단마감재', $modelName, $finishingType); + } + + /** + * L-BAR 단가 조회 + * + * @param string $modelName 모델코드 + */ + public static function getLBarPrice(string $modelName): float + { + return self::getBDModelPrice('L-BAR', $modelName); + } + + /** + * 보강평철 단가 조회 + */ + public static function getFlatBarPrice(): float + { + return self::getBDModelPrice('보강평철'); + } + + /** + * 케이스 마구리 단가 조회 + * + * @param string $spec 규격 + */ + public static function getCaseCapPrice(string $spec): float + { + return self::getBDModelPrice('마구리', null, null, $spec); + } + + /** + * 케이스용 연기차단재 단가 조회 + */ + public static function getCaseSmokeBlockPrice(): float + { + return self::getBDModelPrice('케이스용 연기차단재'); + } + + /** + * 가이드레일용 연기차단재 단가 조회 + */ + public static function getRailSmokeBlockPrice(): float + { + return self::getBDModelPrice('가이드레일용 연기차단재'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 424a54ae..aed9e32c 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -52,6 +52,14 @@ public function register(): void DB::enableQueryLog(); } } + + // 바로빌 Fake 모드: SOAP 호출 없이 샘플 데이터 반환 + if (config('services.barobill.fake_mode')) { + $this->app->bind( + \App\Services\Barobill\BarobillSoapService::class, + \App\Services\Barobill\BarobillFakeSoapService::class + ); + } } /** diff --git a/app/Services/Barobill/BarobillFakeSoapService.php b/app/Services/Barobill/BarobillFakeSoapService.php new file mode 100644 index 00000000..57efd768 --- /dev/null +++ b/app/Services/Barobill/BarobillFakeSoapService.php @@ -0,0 +1,533 @@ +initialized = true; + + return $this; + } + + public function setServerMode(string $mode): self + { + $this->initialized = true; + + return $this; + } + + public function getServerMode(): string + { + return 'test'; + } + + public function checkConfiguration(): array + { + return [ + 'cert_key_set' => true, + 'corp_num_set' => true, + 'test_mode' => true, + 'mode_label' => 'FAKE 모드 (테스트용)', + 'soap_url' => 'fake://localhost', + 'config_source' => 'fake', + 'fake_mode' => true, + ]; + } + + public function initForMember(BarobillMember $member): self + { + $this->fakeCorpNum = $member->biz_no ?? $this->fakeCorpNum; + $this->initialized = true; + + return $this; + } + + // ─── 핵심 SOAP 호출 (가짜 응답) ─── + + public function call(string $service, string $method, array $params = []): array + { + // Fake 모드에서는 모든 call()을 성공으로 반환 + return ['success' => true, 'data' => 0]; + } + + // ─── 회원관리 (CORPSTATE) ─── + + public function registCorp(array $data): array + { + return $this->ok(1); + } + + public function updateCorpInfo(array $data): array + { + return $this->ok(1); + } + + public function getCorpState(string $corpNum): array + { + return $this->ok(1); // 1 = 활성 회원 + } + + // ─── 계좌조회 (BANKACCOUNT) ─── + + public function getBankAccounts(string $corpNum, bool $availOnly = true): array + { + return $this->ok([ + (object) [ + 'BankAccountNum' => '110-123-456789', + 'BankCode' => '088', + 'BankName' => '신한은행', + 'BankAccountType' => '보통예금', + 'State' => 1, + ], + (object) [ + 'BankAccountNum' => '123-45-6789012', + 'BankCode' => '004', + 'BankName' => '국민은행', + 'BankAccountType' => '보통예금', + 'State' => 1, + ], + ]); + } + + public function getPeriodBankAccountTransLog( + string $corpNum, string $id, string $bankAccountNum, + string $startDate, string $endDate, + int $countPerPage = 1000, int $currentPage = 1 + ): array { + $transactions = []; + $balance = 50000000; + $days = max(1, (int) ((strtotime($endDate) - strtotime($startDate)) / 86400)); + + for ($i = 0; $i < min($days, 15); $i++) { + $date = date('Ymd', strtotime($startDate) + $i * 86400); + $isDeposit = $i % 3 !== 0; + $amount = rand(100000, 5000000); + $balance += $isDeposit ? $amount : -$amount; + + $transactions[] = (object) [ + 'TransDT' => $date.'120000', + 'TransDate' => $date, + 'TransTime' => '120000', + 'Deposit' => $isDeposit ? $amount : 0, + 'Withdraw' => $isDeposit ? 0 : $amount, + 'Balance' => max(0, $balance), + 'Summary' => $isDeposit ? '입금' : '출금', + 'Cast' => $this->fakeName(), + 'Memo' => '', + 'TransOffice' => $this->fakeCompany(), + ]; + } + + return $this->ok($transactions); + } + + public function getDailyBankAccountTransLog( + string $corpNum, string $id, string $bankAccountNum, + string $baseDate, string $transDirection = '', + int $countPerPage = 20, int $currentPage = 1 + ): array { + return $this->getPeriodBankAccountTransLog($corpNum, $id, $bankAccountNum, $baseDate, $baseDate); + } + + public function getMonthlyBankAccountTransLog( + string $corpNum, string $id, string $bankAccountNum, + string $baseMonth, string $transDirection = '', + int $countPerPage = 20, int $currentPage = 1 + ): array { + $start = $baseMonth.'01'; + $end = date('Ymt', strtotime($start)); + + return $this->getPeriodBankAccountTransLog($corpNum, $id, $bankAccountNum, $start, $end); + } + + public function getBankAccountScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/bank-scrap?corp='.$corpNum); + } + + public function getCertificateRegistUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/cert-regist?corp='.$corpNum); + } + + public function checkCertificateValid(string $corpNum): array + { + return $this->ok(1); // 1 = 유효 + } + + public function getCertificateExpireDate(string $corpNum): array + { + return $this->ok(date('Ymd', strtotime('+1 year'))); + } + + public function getCertificateRegistDate(string $corpNum): array + { + return $this->ok(date('Ymd', strtotime('-6 months'))); + } + + public function getBalanceCostAmount(string $corpNum): array + { + return $this->ok(85000); // 충전잔액 85,000원 + } + + public function getCashChargeUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/cash-charge?corp='.$corpNum); + } + + public function getBankAccountManagementUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/bank-manage?corp='.$corpNum); + } + + public function getBankAccountLogUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/bank-log?corp='.$corpNum); + } + + public function getBarobillUrl(string $corpNum, string $userId, string $userPwd, string $togo = ''): array + { + return $this->ok('https://testws.baroservice.com/fake/main?corp='.$corpNum); + } + + // ─── 카드조회 (CARD) ─── + + public function getCards(string $corpNum, bool $availOnly = true): array + { + return $this->ok([ + (object) [ + 'CardNum' => '5365-12**-****-1234', + 'CardCompany' => '02', + 'CardCompanyName' => '삼성카드', + 'CardType' => '법인', + 'State' => 1, + ], + (object) [ + 'CardNum' => '4567-89**-****-5678', + 'CardCompany' => '04', + 'CardCompanyName' => '현대카드', + 'CardType' => '법인', + 'State' => 1, + ], + ]); + } + + public function registCard(string $corpNum, array $cardData): array + { + return $this->ok(1); + } + + public function updateCard(string $corpNum, array $cardData): array + { + return $this->ok(1); + } + + public function stopCard(string $corpNum, string $cardNum): array + { + return $this->ok(1); + } + + public function cancelStopCard(string $corpNum, string $cardNum): array + { + return $this->ok(1); + } + + public function getPeriodCardLog( + string $corpNum, string $id, string $cardNum, + string $startDate, string $endDate, + int $countPerPage = 1000, int $currentPage = 1 + ): array { + $transactions = []; + $merchants = ['스타벅스 강남점', '이마트 역삼점', 'GS25 편의점', '교보문고', '쿠팡', '배달의민족', '카카오택시', '주유소']; + $days = max(1, (int) ((strtotime($endDate) - strtotime($startDate)) / 86400)); + + for ($i = 0; $i < min($days * 2, 20); $i++) { + $date = date('Ymd', strtotime($startDate) + intdiv($i, 2) * 86400); + $amount = rand(5000, 500000); + $supply = (int) round($amount / 1.1); + $tax = $amount - $supply; + + $transactions[] = (object) [ + 'UseDT' => $date.sprintf('%02d%02d00', rand(8, 22), rand(0, 59)), + 'UseDate' => $date, + 'UseTime' => sprintf('%02d%02d00', rand(8, 22), rand(0, 59)), + 'ApprovalNum' => sprintf('%08d', rand(10000000, 99999999)), + 'ApprovalType' => '승인', + 'ApprovalAmount' => $amount, + 'Tax' => $tax, + 'ServiceCharge' => 0, + 'PaymentPlan' => '일시불', + 'CurrencyCode' => 'KRW', + 'MerchantName' => $merchants[array_rand($merchants)], + 'MerchantBizNum' => sprintf('%010d', rand(1000000000, 9999999999)), + 'MerchantAddr' => '서울시 강남구', + 'UseKey' => $cardNum.'|'.$date.'|'.rand(1000, 9999), + ]; + } + + return $this->ok($transactions); + } + + public function getDailyCardLog( + string $corpNum, string $id, string $cardNum, + string $baseDate, int $countPerPage = 20, int $currentPage = 1 + ): array { + return $this->getPeriodCardLog($corpNum, $id, $cardNum, $baseDate, $baseDate); + } + + public function getMonthlyCardLog( + string $corpNum, string $id, string $cardNum, + string $baseMonth, int $countPerPage = 20, int $currentPage = 1 + ): array { + $start = $baseMonth.'01'; + $end = date('Ymt', strtotime($start)); + + return $this->getPeriodCardLog($corpNum, $id, $cardNum, $start, $end); + } + + public function getCardScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/card-scrap?corp='.$corpNum); + } + + public function getCardManagementUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/card-manage?corp='.$corpNum); + } + + public function getCardLogUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/card-log?corp='.$corpNum); + } + + // ─── 홈택스 (TI) ─── + + public function getHomeTaxUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/hometax?corp='.$corpNum); + } + + public function getTaxInvoiceIssueUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/tax-invoice-issue?corp='.$corpNum); + } + + public function getTaxInvoiceListUrl(string $corpNum, string $userId, string $userPwd): array + { + return $this->ok('https://testws.baroservice.com/fake/tax-invoice-list?corp='.$corpNum); + } + + // ─── 카카오톡 (KAKAOTALK) ─── + + public function getKakaotalkChannels(string $corpNum): array + { + return $this->ok([ + (object) [ + 'ChannelId' => '@sam-test', + 'ChannelName' => 'SAM 테스트 채널', + 'SearchId' => '@sam-test', + 'CategoryCode' => 'BIZ', + ], + ]); + } + + public function getKakaotalkChannelManagementUrl(string $corpNum, string $id): array + { + return $this->ok('https://testws.baroservice.com/fake/kakao-channel?corp='.$corpNum); + } + + public function getKakaotalkTemplates(string $corpNum, string $channelId): array + { + return $this->ok([ + (object) [ + 'TemplateName' => '주문확인', + 'TemplateCode' => 'TPL001', + 'TemplateContent' => '#{고객명}님, 주문이 확인되었습니다.', + 'Status' => 'A', + ], + (object) [ + 'TemplateName' => '발송완료', + 'TemplateCode' => 'TPL002', + 'TemplateContent' => '#{고객명}님, 상품이 발송되었습니다.', + 'Status' => 'A', + ], + ]); + } + + public function getKakaotalkTemplateManagementUrl(string $corpNum, string $id): array + { + return $this->ok('https://testws.baroservice.com/fake/kakao-template?corp='.$corpNum); + } + + public function sendATKakaotalk( + string $corpNum, string $senderId, string $yellowId, + string $templateName, string $receiverName, string $receiverNum, + string $title, string $message, + string $smsMessage = '', string $smsSubject = '', + string $smsSenderNum = '', string $reserveDT = '' + ): array { + return $this->ok('FAKE-KKO-'.Str::random(12)); + } + + public function sendATKakaotalkEx( + string $corpNum, string $senderId, string $yellowId, + string $templateName, string $receiverName, string $receiverNum, + string $title, string $message, array $buttons = [], + string $smsMessage = '', string $smsSubject = '', + string $smsSenderNum = '', string $reserveDT = '' + ): array { + return $this->ok('FAKE-KKO-'.Str::random(12)); + } + + public function sendATKakaotalks( + string $corpNum, string $senderId, string $yellowId, + string $templateName, array $messages, string $reserveDT = '' + ): array { + return $this->ok('FAKE-KKO-BULK-'.Str::random(8)); + } + + public function sendFTKakaotalk( + string $corpNum, string $senderId, string $channelId, + string $receiverName, string $receiverNum, string $message, + array $buttons = [], string $smsMessage = '', string $smsSubject = '', + bool $adYn = false, string $reserveDT = '' + ): array { + return $this->ok('FAKE-FT-'.Str::random(12)); + } + + public function sendFTKakaotalks( + string $corpNum, string $senderId, string $channelId, + array $messages, bool $adYn = false, string $reserveDT = '' + ): array { + return $this->ok('FAKE-FT-BULK-'.Str::random(8)); + } + + public function sendFIKakaotalk( + string $corpNum, string $senderId, string $channelId, + string $receiverName, string $receiverNum, string $message, + string $imageUrl, array $buttons = [], + string $smsMessage = '', string $smsSubject = '', + bool $adYn = false, string $reserveDT = '' + ): array { + return $this->ok('FAKE-FI-'.Str::random(12)); + } + + public function sendFWKakaotalk( + string $corpNum, string $senderId, string $channelId, + string $receiverName, string $receiverNum, string $message, + string $imageUrl, array $buttons = [], + string $smsMessage = '', string $smsSubject = '', + bool $adYn = false, string $reserveDT = '' + ): array { + return $this->ok('FAKE-FW-'.Str::random(12)); + } + + public function getSendKakaotalk(string $corpNum, string $sendKey): array + { + return $this->ok((object) [ + 'SendKey' => $sendKey, + 'SendState' => 3, // 전송 성공 + 'SendStateName' => '전송성공', + 'SendDT' => now()->format('YmdHis'), + 'ResultDT' => now()->format('YmdHis'), + ]); + } + + public function getSendKakaotalks(string $corpNum, array $sendKeyList): array + { + $results = []; + foreach ($sendKeyList as $key) { + $results[] = (object) [ + 'SendKey' => $key, + 'SendState' => 3, + 'SendStateName' => '전송성공', + ]; + } + + return $this->ok($results); + } + + public function cancelReservedKakaotalk(string $corpNum, string $sendKey): array + { + return $this->ok(1); + } + + // ─── SMS ─── + + public function sendSMSMessage( + string $corpNum, string $senderId, string $fromNumber, + string $toName, string $toNumber, string $contents, + string $sendDT = '', string $refKey = '' + ): array { + return $this->ok('FAKE-SMS-'.Str::random(12)); + } + + public function checkSMSFromNumber(string $corpNum, string $fromNumber): array + { + return $this->ok(1); // 등록된 번호 + } + + public function getSMSFromNumbers(string $corpNum): array + { + return $this->ok([ + (object) ['FromNumber' => '02-1234-5678', 'State' => 1], + (object) ['FromNumber' => '010-9876-5432', 'State' => 1], + ]); + } + + public function getSMSSendState(string $corpNum, string $sendKey): array + { + return $this->ok((object) [ + 'SendKey' => $sendKey, + 'SendState' => 3, + 'SendStateName' => '전송성공', + 'SendDT' => now()->format('YmdHis'), + ]); + } + + // ─── 헬퍼 ─── + + private function ok(mixed $data): array + { + return ['success' => true, 'data' => $data]; + } + + private function fakeName(): string + { + $names = ['홍길동', '김영수', '이미영', '박지훈', '최서연', '정태우', '강민지']; + + return $names[array_rand($names)]; + } + + private function fakeCompany(): string + { + $companies = ['(주)한국전력', '삼성전자', 'LG화학', '현대건설', '롯데마트', 'CJ물류', 'SKT', '네이버']; + + return $companies[array_rand($companies)]; + } +} diff --git a/config/services.php b/config/services.php index 1f29e1e9..c1c514aa 100644 --- a/config/services.php +++ b/config/services.php @@ -69,6 +69,7 @@ 'cert_key_prod' => env('BAROBILL_CERT_KEY_PROD', ''), 'corp_num' => env('BAROBILL_CORP_NUM', ''), 'test_mode' => env('BAROBILL_TEST_MODE', true), + 'fake_mode' => env('BAROBILL_FAKE_MODE', false), ], ]; diff --git a/database/seeders/Kyungdong/KdPriceTableSeeder.php b/database/seeders/Kyungdong/KdPriceTableSeeder.php new file mode 100644 index 00000000..490553a9 --- /dev/null +++ b/database/seeders/Kyungdong/KdPriceTableSeeder.php @@ -0,0 +1,565 @@ +command->info(''); + $this->command->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + $this->command->info('🔧 경동기업 단가 테이블 마이그레이션 (kd_price_tables)'); + $this->command->info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + + // 기존 데이터 삭제 + DB::table('kd_price_tables')->where('tenant_id', self::TENANT_ID)->delete(); + $this->command->info(' → 기존 데이터 삭제 완료'); + + // chandj 연결 가능 여부 확인 + $chandjAvailable = $this->checkChandjConnection(); + + if ($chandjAvailable) { + $this->command->info(' → chandj 데이터베이스 연결됨'); + $this->migrateFromChandj(); + } else { + $this->command->warn(' → chandj 데이터베이스 연결 불가 - 샘플 데이터 사용'); + $this->insertSampleData(); + } + + $count = DB::table('kd_price_tables')->where('tenant_id', self::TENANT_ID)->count(); + $this->command->info(''); + $this->command->info("✅ 완료: kd_price_tables {$count}건"); + } + + /** + * chandj 데이터베이스 연결 확인 + */ + private function checkChandjConnection(): bool + { + try { + DB::connection('chandj')->getPdo(); + + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * chandj 데이터베이스에서 마이그레이션 + */ + private function migrateFromChandj(): void + { + $this->migrateMotorPrices(); + $this->migrateShaftPrices(); + $this->migratePipePrices(); + $this->migrateAnglePrices(); + $this->migrateRawMaterialPrices(); + } + + /** + * price_motor → kd_price_tables + */ + private function migrateMotorPrices(): void + { + $this->command->info(''); + $this->command->info('📦 [1/5] price_motor 마이그레이션...'); + + $priceMotor = DB::connection('chandj') + ->table('price_motor') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceMotor || empty($priceMotor->itemList)) { + $this->command->info(' → 데이터 없음'); + + return; + } + + $itemList = json_decode($priceMotor->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return; + } + + $count = 0; + $now = now(); + + foreach ($itemList as $item) { + $col1 = $item['col1'] ?? ''; // 전압/카테고리 (220, 380, 제어기 등) + $col2 = $item['col2'] ?? ''; // 용량/품목명 + $salesPrice = (float) str_replace(',', '', $item['col13'] ?? '0'); + + if (empty($col2) || $salesPrice <= 0) { + continue; + } + + // 카테고리 결정 + $category = match ($col1) { + '220', '380' => $col2, // 모터 용량 (150K, 300K 등) + default => $col1, // 제어기, 방화, 방범 등 + }; + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_MOTOR, + 'item_name' => trim("{$col1} {$col2}"), + 'category' => $category, + 'spec1' => $col1, + 'spec2' => $col2, + 'unit_price' => $salesPrice, + 'unit' => 'EA', + 'raw_data' => $item, + 'is_active' => true, + ]); + $count++; + } + + $this->command->info(" → {$count}건 완료"); + } + + /** + * price_shaft → kd_price_tables + */ + private function migrateShaftPrices(): void + { + $this->command->info(''); + $this->command->info('📦 [2/5] price_shaft 마이그레이션...'); + + $priceShaft = DB::connection('chandj') + ->table('price_shaft') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceShaft || empty($priceShaft->itemList)) { + $this->command->info(' → 데이터 없음'); + + return; + } + + $itemList = json_decode($priceShaft->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return; + } + + $count = 0; + + foreach ($itemList as $item) { + $size = $item['col4'] ?? ''; // 사이즈 (3, 4, 5인치) + $length = $item['col10'] ?? ''; // 길이 (m 단위) + $salesPrice = (float) str_replace(',', '', $item['col19'] ?? '0'); + + if (empty($size) || $salesPrice <= 0) { + continue; + } + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_SHAFT, + 'item_name' => "감기샤프트 {$size}인치 {$length}m", + 'category' => '감기샤프트', + 'spec1' => $size, + 'spec2' => $length, + 'unit_price' => $salesPrice, + 'unit' => 'EA', + 'raw_data' => $item, + 'is_active' => true, + ]); + $count++; + } + + $this->command->info(" → {$count}건 완료"); + } + + /** + * price_pipe → kd_price_tables + */ + private function migratePipePrices(): void + { + $this->command->info(''); + $this->command->info('📦 [3/5] price_pipe 마이그레이션...'); + + $pricePipe = DB::connection('chandj') + ->table('price_pipe') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $pricePipe || empty($pricePipe->itemList)) { + $this->command->info(' → 데이터 없음'); + + return; + } + + $itemList = json_decode($pricePipe->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return; + } + + $count = 0; + + foreach ($itemList as $item) { + $length = $item['col2'] ?? ''; // 길이 (3000, 6000) + $thickness = $item['col4'] ?? ''; // 두께 (1.4) + $salesPrice = (float) str_replace(',', '', $item['col8'] ?? '0'); + + if (empty($thickness) || $salesPrice <= 0) { + continue; + } + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_PIPE, + 'item_name' => "각파이프 {$thickness}T {$length}mm", + 'category' => '각파이프', + 'spec1' => $thickness, + 'spec2' => $length, + 'unit_price' => $salesPrice, + 'unit' => 'EA', + 'raw_data' => $item, + 'is_active' => true, + ]); + $count++; + } + + $this->command->info(" → {$count}건 완료"); + } + + /** + * price_angle → kd_price_tables + */ + private function migrateAnglePrices(): void + { + $this->command->info(''); + $this->command->info('📦 [4/5] price_angle 마이그레이션...'); + + $priceAngle = DB::connection('chandj') + ->table('price_angle') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceAngle || empty($priceAngle->itemList)) { + $this->command->info(' → 데이터 없음'); + + return; + } + + $itemList = json_decode($priceAngle->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return; + } + + $count = 0; + + foreach ($itemList as $item) { + $type = $item['col2'] ?? ''; // 타입 (스크린용, 철재용) + $bracketSize = $item['col3'] ?? ''; // 브라켓크기 + $angleType = $item['col4'] ?? ''; // 앵글타입 (앵글3T, 앵글4T) + $thickness = $item['col10'] ?? ''; // 두께 + $salesPrice = (float) str_replace(',', '', $item['col19'] ?? '0'); + + if (empty($type) || $salesPrice <= 0) { + continue; + } + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_ANGLE, + 'item_name' => "앵글 {$type} {$bracketSize} {$angleType}", + 'category' => $type, + 'spec1' => $bracketSize, + 'spec2' => $angleType, + 'spec3' => $thickness, + 'unit_price' => $salesPrice, + 'unit' => 'EA', + 'raw_data' => $item, + 'is_active' => true, + ]); + $count++; + } + + $this->command->info(" → {$count}건 완료"); + } + + /** + * price_raw_materials → kd_price_tables + */ + private function migrateRawMaterialPrices(): void + { + $this->command->info(''); + $this->command->info('📦 [5/5] price_raw_materials 마이그레이션...'); + + $priceRaw = DB::connection('chandj') + ->table('price_raw_materials') + ->where(function ($q) { + $q->where('is_deleted', 0)->orWhereNull('is_deleted'); + }) + ->orderByDesc('registedate') + ->first(); + + if (! $priceRaw || empty($priceRaw->itemList)) { + $this->command->info(' → 데이터 없음'); + + return; + } + + $itemList = json_decode($priceRaw->itemList, true); + if (! is_array($itemList)) { + $this->command->info(' → JSON 파싱 실패'); + + return; + } + + $count = 0; + + foreach ($itemList as $item) { + $name = $item['col2'] ?? ''; + $spec = $item['col3'] ?? ''; + $salesPrice = (float) str_replace(',', '', $item['col19'] ?? $item['col13'] ?? '0'); + + if (empty($name) || $salesPrice <= 0) { + continue; + } + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_RAW_MATERIAL, + 'item_name' => $name, + 'category' => '원자재', + 'spec1' => $spec, + 'unit_price' => $salesPrice, + 'unit' => '㎡', + 'raw_data' => $item, + 'is_active' => true, + ]); + $count++; + } + + $this->command->info(" → {$count}건 완료"); + } + + /** + * chandj 연결 불가 시 샘플 데이터 삽입 + */ + private function insertSampleData(): void + { + $this->command->info(''); + $this->command->info('📦 샘플 데이터 삽입 중...'); + + // 모터 샘플 데이터 (5130 분석 결과 기반) + $motorData = [ + ['category' => '150K', 'spec1' => '220', 'spec2' => '150K', 'unit_price' => 85000], + ['category' => '300K', 'spec1' => '220', 'spec2' => '300K', 'unit_price' => 120000], + ['category' => '400K', 'spec1' => '220', 'spec2' => '400K', 'unit_price' => 150000], + ['category' => '500K', 'spec1' => '220', 'spec2' => '500K', 'unit_price' => 180000], + ['category' => '600K', 'spec1' => '220', 'spec2' => '600K', 'unit_price' => 220000], + ['category' => '800K', 'spec1' => '220', 'spec2' => '800K', 'unit_price' => 280000], + ['category' => '1000K', 'spec1' => '220', 'spec2' => '1000K', 'unit_price' => 350000], + ['category' => '매립형', 'spec1' => '제어기', 'spec2' => '매립형', 'unit_price' => 45000], + ['category' => '노출형', 'spec1' => '제어기', 'spec2' => '노출형', 'unit_price' => 55000], + ['category' => '뒷박스', 'spec1' => '제어기', 'spec2' => '뒷박스', 'unit_price' => 35000], + ]; + + foreach ($motorData as $data) { + KdPriceTable::create(array_merge($data, [ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_MOTOR, + 'item_name' => "모터/제어기 {$data['category']}", + 'unit' => 'EA', + 'is_active' => true, + ])); + } + $this->command->info(' → 모터/제어기 '.count($motorData).'건'); + + // 샤프트 샘플 데이터 + $shaftData = [ + ['spec1' => '3', 'spec2' => '3.0', 'unit_price' => 45000], + ['spec1' => '4', 'spec2' => '3.0', 'unit_price' => 55000], + ['spec1' => '5', 'spec2' => '3.0', 'unit_price' => 65000], + ['spec1' => '3', 'spec2' => '4.0', 'unit_price' => 60000], + ['spec1' => '4', 'spec2' => '4.0', 'unit_price' => 75000], + ['spec1' => '5', 'spec2' => '4.0', 'unit_price' => 90000], + ]; + + foreach ($shaftData as $data) { + KdPriceTable::create(array_merge($data, [ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_SHAFT, + 'item_name' => "감기샤프트 {$data['spec1']}인치 {$data['spec2']}m", + 'category' => '감기샤프트', + 'unit' => 'EA', + 'is_active' => true, + ])); + } + $this->command->info(' → 샤프트 '.count($shaftData).'건'); + + // 파이프 샘플 데이터 + $pipeData = [ + ['spec1' => '1.4', 'spec2' => '3000', 'unit_price' => 12000], + ['spec1' => '1.4', 'spec2' => '6000', 'unit_price' => 24000], + ]; + + foreach ($pipeData as $data) { + KdPriceTable::create(array_merge($data, [ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_PIPE, + 'item_name' => "각파이프 {$data['spec1']}T {$data['spec2']}mm", + 'category' => '각파이프', + 'unit' => 'EA', + 'is_active' => true, + ])); + } + $this->command->info(' → 파이프 '.count($pipeData).'건'); + + // 앵글 샘플 데이터 + $angleData = [ + ['category' => '스크린용', 'spec1' => '530*320', 'spec2' => '앵글3T', 'unit_price' => 8000], + ['category' => '스크린용', 'spec1' => '600*350', 'spec2' => '앵글3T', 'unit_price' => 10000], + ['category' => '스크린용', 'spec1' => '690*390', 'spec2' => '앵글4T', 'unit_price' => 12000], + ['category' => '철재용', 'spec1' => '530*320', 'spec2' => '앵글3T', 'unit_price' => 9000], + ['category' => '철재용', 'spec1' => '600*350', 'spec2' => '앵글3T', 'unit_price' => 11000], + ['category' => '철재용', 'spec1' => '690*390', 'spec2' => '앵글4T', 'unit_price' => 14000], + ]; + + foreach ($angleData as $data) { + KdPriceTable::create(array_merge($data, [ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_ANGLE, + 'item_name' => "앵글 {$data['category']} {$data['spec1']} {$data['spec2']}", + 'unit' => 'EA', + 'is_active' => true, + ])); + } + $this->command->info(' → 앵글 '.count($angleData).'건'); + + // 원자재 샘플 데이터 + $rawData = [ + ['item_name' => '실리카', 'spec1' => '스크린용', 'unit_price' => 25000], + ['item_name' => '불투명', 'spec1' => '스크린용', 'unit_price' => 22000], + ['item_name' => '화이바원단', 'spec1' => '스크린용', 'unit_price' => 28000], + ]; + + foreach ($rawData as $data) { + KdPriceTable::create(array_merge($data, [ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_RAW_MATERIAL, + 'category' => '원자재', + 'unit' => '㎡', + 'is_active' => true, + ])); + } + $this->command->info(' → 원자재 '.count($rawData).'건'); + + // BDmodels 샘플 데이터 (절곡품) + $this->insertBDModelsSampleData(); + } + + /** + * BDmodels 샘플 데이터 삽입 (절곡품) + */ + private function insertBDModelsSampleData(): void + { + $bdmodelsData = [ + // 케이스 (규격별 단가 - 원/m) + ['category' => '케이스', 'spec2' => '500*380', 'unit_price' => 15000, 'unit' => 'm'], + ['category' => '케이스', 'spec2' => '550*430', 'unit_price' => 18000, 'unit' => 'm'], + ['category' => '케이스', 'spec2' => '650*550', 'unit_price' => 22000, 'unit' => 'm'], + + // 케이스 마구리 (규격별 단가 - 원/개) + ['category' => '마구리', 'spec2' => '500*380', 'unit_price' => 5000, 'unit' => 'EA'], + ['category' => '마구리', 'spec2' => '550*430', 'unit_price' => 6000, 'unit' => 'EA'], + ['category' => '마구리', 'spec2' => '650*550', 'unit_price' => 7500, 'unit' => 'EA'], + + // 케이스용 연기차단재 (공통 단가 - 원/m) + ['category' => '케이스용 연기차단재', 'unit_price' => 3500, 'unit' => 'm'], + + // 가이드레일 (모델+마감+규격별 단가 - 원/m) + ['category' => '가이드레일', 'item_code' => 'KSS01', 'spec1' => 'SUS', 'spec2' => '120*70', 'unit_price' => 12000, 'unit' => 'm'], + ['category' => '가이드레일', 'item_code' => 'KSS01', 'spec1' => 'SUS', 'spec2' => '120*100', 'unit_price' => 15000, 'unit' => 'm'], + ['category' => '가이드레일', 'item_code' => 'KSS01', 'spec1' => 'EGI', 'spec2' => '120*70', 'unit_price' => 10000, 'unit' => 'm'], + ['category' => '가이드레일', 'item_code' => 'KSS01', 'spec1' => 'EGI', 'spec2' => '120*100', 'unit_price' => 13000, 'unit' => 'm'], + ['category' => '가이드레일', 'item_code' => 'KWS01', 'spec1' => 'SUS', 'spec2' => '120*70', 'unit_price' => 13000, 'unit' => 'm'], + ['category' => '가이드레일', 'item_code' => 'KWS01', 'spec1' => 'SUS', 'spec2' => '120*100', 'unit_price' => 16000, 'unit' => 'm'], + + // 가이드레일용 연기차단재 (공통 단가 - 원/m) + ['category' => '가이드레일용 연기차단재', 'unit_price' => 2500, 'unit' => 'm'], + + // 하단마감재/하장바 (모델+마감별 단가 - 원/m) + ['category' => '하단마감재', 'item_code' => 'KSS01', 'spec1' => 'SUS', 'unit_price' => 8000, 'unit' => 'm'], + ['category' => '하단마감재', 'item_code' => 'KSS01', 'spec1' => 'EGI', 'unit_price' => 6500, 'unit' => 'm'], + ['category' => '하단마감재', 'item_code' => 'KWS01', 'spec1' => 'SUS', 'unit_price' => 8500, 'unit' => 'm'], + + // L-BAR (모델별 단가 - 원/m) + ['category' => 'L-BAR', 'item_code' => 'KSS01', 'unit_price' => 4500, 'unit' => 'm'], + ['category' => 'L-BAR', 'item_code' => 'KWS01', 'unit_price' => 5000, 'unit' => 'm'], + + // 보강평철 (공통 단가 - 원/m) + ['category' => '보강평철', 'unit_price' => 3000, 'unit' => 'm'], + ]; + + foreach ($bdmodelsData as $data) { + $itemCode = $data['item_code'] ?? null; + $spec1 = $data['spec1'] ?? null; + $spec2 = $data['spec2'] ?? null; + + $itemName = $data['category']; + if ($itemCode) { + $itemName .= " {$itemCode}"; + } + if ($spec1) { + $itemName .= " {$spec1}"; + } + if ($spec2) { + $itemName .= " {$spec2}"; + } + + KdPriceTable::create([ + 'tenant_id' => self::TENANT_ID, + 'table_type' => KdPriceTable::TYPE_BDMODELS, + 'item_code' => $itemCode, + 'item_name' => $itemName, + 'category' => $data['category'], + 'spec1' => $spec1, + 'spec2' => $spec2, + 'unit_price' => $data['unit_price'], + 'unit' => $data['unit'], + 'is_active' => true, + ]); + } + $this->command->info(' → BDmodels(절곡품) '.count($bdmodelsData).'건'); + } +}