diff --git a/app/Console/Commands/FixMenuUrlCommand.php b/app/Console/Commands/FixMenuUrlCommand.php index 92d34e4f..2fcee57e 100644 --- a/app/Console/Commands/FixMenuUrlCommand.php +++ b/app/Console/Commands/FixMenuUrlCommand.php @@ -8,6 +8,7 @@ class FixMenuUrlCommand extends Command { protected $signature = 'menu:fix-url {name} {new-url} {--tenant=}'; + protected $description = '메뉴 URL 수정 (이름으로 검색)'; public function handle(): int @@ -26,6 +27,7 @@ public function handle(): int if ($menus->isEmpty()) { $this->error("'{$name}' 메뉴를 찾을 수 없습니다."); + return 1; } @@ -37,6 +39,7 @@ public function handle(): int } $this->info("{$menus->count()}건 수정 완료."); + return 0; } } diff --git a/app/Console/Commands/SeedQuoteFormulasCommand.php b/app/Console/Commands/SeedQuoteFormulasCommand.php index e2f76185..2c30725c 100644 --- a/app/Console/Commands/SeedQuoteFormulasCommand.php +++ b/app/Console/Commands/SeedQuoteFormulasCommand.php @@ -800,4 +800,4 @@ private function getItemData(): array ], ]; } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/Admin/BankAccountController.php b/app/Http/Controllers/Api/Admin/BankAccountController.php index 7d975340..82968328 100644 --- a/app/Http/Controllers/Api/Admin/BankAccountController.php +++ b/app/Http/Controllers/Api/Admin/BankAccountController.php @@ -3,7 +3,6 @@ namespace App\Http\Controllers\Api\Admin; use App\Http\Controllers\Controller; -use App\Models\Finance\BankAccount; use App\Services\BankAccountService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -86,7 +85,7 @@ public function store(Request $request): JsonResponse // 기본값 설정 $validated['status'] = $validated['status'] ?? 'active'; - $validated['account_name'] = $validated['account_name'] ?? $validated['bank_name'] . ' 계좌'; + $validated['account_name'] = $validated['account_name'] ?? $validated['bank_name'].' 계좌'; $account = $this->bankAccountService->createAccount($validated); @@ -156,6 +155,7 @@ public function destroy(Request $request, int $id): JsonResponse|Response // HTMX 요청인 경우 갱신된 테이블 반환 if ($request->header('HX-Request')) { $accounts = $this->bankAccountService->getAccounts($request->all(), $request->integer('per_page', 15)); + return response(view('finance.accounts.partials.table', compact('accounts'))); } @@ -184,6 +184,7 @@ public function restore(Request $request, int $id): JsonResponse|Response // HTMX 요청인 경우 갱신된 테이블 반환 if ($request->header('HX-Request')) { $accounts = $this->bankAccountService->getAccounts($request->all(), $request->integer('per_page', 15)); + return response(view('finance.accounts.partials.table', compact('accounts'))); } @@ -212,6 +213,7 @@ public function forceDelete(Request $request, int $id): JsonResponse|Response // HTMX 요청인 경우 갱신된 테이블 반환 if ($request->header('HX-Request')) { $accounts = $this->bankAccountService->getAccounts($request->all(), $request->integer('per_page', 15)); + return response(view('finance.accounts.partials.table', compact('accounts'))); } @@ -240,6 +242,7 @@ public function toggleActive(Request $request, int $id): JsonResponse|Response // HTMX 요청인 경우 갱신된 테이블 반환 if ($request->header('HX-Request')) { $accounts = $this->bankAccountService->getAccounts($request->all(), $request->integer('per_page', 15)); + return response(view('finance.accounts.partials.table', compact('accounts'))); } diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php index 70479c97..19267748 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillBillingController.php @@ -42,7 +42,7 @@ public function subscriptions(Request $request): JsonResponse|Response ->orderBy('service_type'); // 테넌트 필터링 - if (!$isHeadquarters && !$allTenants) { + if (! $isHeadquarters && ! $allTenants) { $query->whereHas('member', function ($q) use ($tenantId) { $q->where('tenant_id', $tenantId); }); @@ -102,7 +102,7 @@ public function cancelSubscription(int $id): JsonResponse { $result = $this->billingService->cancelSubscription($id); - if (!$result) { + if (! $result) { return response()->json([ 'success' => false, 'message' => '구독을 찾을 수 없습니다.', @@ -121,7 +121,7 @@ public function cancelSubscription(int $id): JsonResponse public function memberSubscriptions(int $memberId): JsonResponse|Response { $member = BarobillMember::with('tenant')->find($memberId); - if (!$member) { + if (! $member) { return response()->json([ 'success' => false, 'message' => '회원사를 찾을 수 없습니다.', @@ -157,7 +157,7 @@ public function billingList(Request $request): JsonResponse|Response $billingMonth = $request->input('billing_month', now()->format('Y-m')); // 테넌트 필터링 - $filterTenantId = (!$isHeadquarters && !$allTenants) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants) ? $tenantId : null; $summaries = BarobillMonthlySummary::with(['member.tenant']) ->where('billing_month', $billingMonth) @@ -202,7 +202,7 @@ public function billingStats(Request $request): JsonResponse|Response $billingMonth = $request->input('billing_month', now()->format('Y-m')); - $filterTenantId = (!$isHeadquarters && !$allTenants) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants) ? $tenantId : null; $stats = $this->billingService->getMonthlyTotal($billingMonth, $filterTenantId); if ($request->header('HX-Request')) { @@ -225,7 +225,7 @@ public function billingStats(Request $request): JsonResponse|Response public function memberBilling(Request $request, int $memberId): JsonResponse|Response { $member = BarobillMember::with('tenant')->find($memberId); - if (!$member) { + if (! $member) { return response()->json([ 'success' => false, 'message' => '회원사를 찾을 수 없습니다.', @@ -293,7 +293,7 @@ public function yearlyTrend(Request $request): JsonResponse $year = $request->input('year', now()->year); - $filterTenantId = (!$isHeadquarters && !$allTenants) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants) ? $tenantId : null; $trend = $this->billingService->getYearlyTrend($year, $filterTenantId); return response()->json([ @@ -313,7 +313,7 @@ public function export(Request $request) $billingMonth = $request->input('billing_month', now()->format('Y-m')); - $filterTenantId = (!$isHeadquarters && !$allTenants) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants) ? $tenantId : null; $summaries = BarobillMonthlySummary::with(['member.tenant']) ->where('billing_month', $billingMonth) @@ -336,7 +336,7 @@ public function export(Request $request) $callback = function () use ($summaries, $total, $isHeadquarters, $allTenants) { $file = fopen('php://output', 'w'); - fprintf($file, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); // 헤더 $headerRow = ['사업자번호', '상호', '계좌조회', '카드내역', '홈텍스', '월정액합계', '세금계산서(건)', '세금계산서(원)', '건별합계', '총합계']; @@ -411,7 +411,7 @@ public function pricingPolicies(Request $request): JsonResponse|Response public function updatePricingPolicy(Request $request, int $id): JsonResponse { $policy = BarobillPricingPolicy::find($id); - if (!$policy) { + if (! $policy) { return response()->json([ 'success' => false, 'message' => '정책을 찾을 수 없습니다.', @@ -444,7 +444,7 @@ public function updatePricingPolicy(Request $request, int $id): JsonResponse public function getPricingPolicy(int $id): JsonResponse { $policy = BarobillPricingPolicy::find($id); - if (!$policy) { + if (! $policy) { return response()->json([ 'success' => false, 'message' => '정책을 찾을 수 없습니다.', diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillConfigController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillConfigController.php index f002493f..044e9bf2 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillConfigController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillConfigController.php @@ -94,7 +94,7 @@ public function show(int $id): JsonResponse { $config = BarobillConfig::find($id); - if (!$config) { + if (! $config) { return response()->json([ 'success' => false, 'message' => '설정을 찾을 수 없습니다.', @@ -114,7 +114,7 @@ public function update(Request $request, int $id): JsonResponse { $config = BarobillConfig::find($id); - if (!$config) { + if (! $config) { return response()->json([ 'success' => false, 'message' => '설정을 찾을 수 없습니다.', @@ -167,7 +167,7 @@ public function destroy(int $id): JsonResponse { $config = BarobillConfig::find($id); - if (!$config) { + if (! $config) { return response()->json([ 'success' => false, 'message' => '설정을 찾을 수 없습니다.', @@ -196,7 +196,7 @@ public function toggleActive(int $id): JsonResponse { $config = BarobillConfig::find($id); - if (!$config) { + if (! $config) { return response()->json([ 'success' => false, 'message' => '설정을 찾을 수 없습니다.', @@ -205,14 +205,14 @@ public function toggleActive(int $id): JsonResponse DB::beginTransaction(); try { - if (!$config->is_active) { + if (! $config->is_active) { // 활성화하려면 같은 환경의 다른 설정들 비활성화 BarobillConfig::where('environment', $config->environment) ->where('id', '!=', $id) ->update(['is_active' => false]); } - $config->update(['is_active' => !$config->is_active]); + $config->update(['is_active' => ! $config->is_active]); DB::commit(); @@ -231,5 +231,4 @@ public function toggleActive(int $id): JsonResponse ], 500); } } - } diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillSettingController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillSettingController.php index a33cbe0e..fa0f7544 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillSettingController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillSettingController.php @@ -25,7 +25,7 @@ public function show(): JsonResponse // 바로빌 회원사 정보 조회 (담당자 정보 기본값용) $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$setting) { + if (! $setting) { // 설정이 없으면 바로빌 회원사 정보를 기본값으로 사용 return response()->json([ 'success' => true, @@ -158,7 +158,7 @@ public function checkService(string $service): JsonResponse $setting = BarobillSetting::where('tenant_id', $tenantId)->first(); - if (!$setting) { + if (! $setting) { return response()->json([ 'success' => true, 'enabled' => false, diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillUsageController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillUsageController.php index 8220f138..c14704b6 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillUsageController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillUsageController.php @@ -8,7 +8,6 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Illuminate\Support\Facades\Log; /** * 바로빌 사용량조회 API 컨트롤러 @@ -44,7 +43,7 @@ public function index(Request $request): JsonResponse|Response $apiEndDate = str_replace('-', '', $endDate); // 테넌트 필터링 - $filterTenantId = (!$isHeadquarters && !$allTenants && $tenantId) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants && $tenantId) ? $tenantId : null; // 사용량 목록 조회 $usageList = $this->usageService->getUsageList($apiStartDate, $apiEndDate, $filterTenantId); @@ -117,7 +116,7 @@ public function stats(Request $request): JsonResponse|Response $apiEndDate = str_replace('-', '', $endDate); // 테넌트 필터링 - $filterTenantId = (!$isHeadquarters && !$allTenants && $tenantId) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants && $tenantId) ? $tenantId : null; // 사용량 목록 조회 및 통계 집계 $usageList = $this->usageService->getUsageList($apiStartDate, $apiEndDate, $filterTenantId); @@ -145,7 +144,7 @@ public function show(Request $request, int $memberId): JsonResponse|Response { $member = BarobillMember::with('tenant:id,company_name')->find($memberId); - if (!$member) { + if (! $member) { return response()->json([ 'success' => false, 'message' => '회원사를 찾을 수 없습니다.', @@ -209,7 +208,7 @@ public function export(Request $request) $apiEndDate = str_replace('-', '', $endDate); // 테넌트 필터링 - $filterTenantId = (!$isHeadquarters && !$allTenants && $tenantId) ? $tenantId : null; + $filterTenantId = (! $isHeadquarters && ! $allTenants && $tenantId) ? $tenantId : null; // 사용량 목록 조회 $usageList = $this->usageService->getUsageList($apiStartDate, $apiEndDate, $filterTenantId); @@ -227,7 +226,7 @@ public function export(Request $request) $file = fopen('php://output', 'w'); // BOM for Excel UTF-8 - fprintf($file, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); // 헤더 $headerRow = ['사업자번호', '상호', '바로빌ID', '세금계산서(건)', '계좌조회(건)', '카드내역(건)', '홈텍스(건)', '과금액(원)']; diff --git a/app/Http/Controllers/Api/Admin/CategoryApiController.php b/app/Http/Controllers/Api/Admin/CategoryApiController.php index cfd5506f..cd948e65 100644 --- a/app/Http/Controllers/Api/Admin/CategoryApiController.php +++ b/app/Http/Controllers/Api/Admin/CategoryApiController.php @@ -335,4 +335,4 @@ public function reorder(Request $request): JsonResponse ], 500); } } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/Admin/DocumentApiController.php b/app/Http/Controllers/Api/Admin/DocumentApiController.php index fa7d4f81..e0c88d58 100644 --- a/app/Http/Controllers/Api/Admin/DocumentApiController.php +++ b/app/Http/Controllers/Api/Admin/DocumentApiController.php @@ -236,7 +236,7 @@ public function destroy(int $id): JsonResponse */ public function forceDestroy(int $id): JsonResponse { - if (!auth()->user()?->is_super_admin) { + if (! auth()->user()?->is_super_admin) { return response()->json([ 'success' => false, 'message' => '슈퍼관리자만 영구 삭제할 수 있습니다.', @@ -263,7 +263,7 @@ public function forceDestroy(int $id): JsonResponse */ public function restore(int $id): JsonResponse { - if (!auth()->user()?->is_super_admin) { + if (! auth()->user()?->is_super_admin) { return response()->json([ 'success' => false, 'message' => '슈퍼관리자만 복원할 수 있습니다.', diff --git a/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php index a05462c2..c417e28a 100644 --- a/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php +++ b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php @@ -66,7 +66,7 @@ public function index(Request $request): View } // 활성 상태 필터 - if ($request->filled('is_active') && !$showTrashed) { + if ($request->filled('is_active') && ! $showTrashed) { $query->where('is_active', $request->boolean('is_active')); } @@ -290,7 +290,7 @@ public function destroy(int $id): JsonResponse */ public function forceDestroy(int $id): JsonResponse { - if (!auth()->user()?->is_super_admin) { + if (! auth()->user()?->is_super_admin) { return response()->json([ 'success' => false, 'message' => '슈퍼관리자만 영구 삭제할 수 있습니다.', @@ -337,7 +337,7 @@ public function forceDestroy(int $id): JsonResponse */ public function restore(int $id): JsonResponse { - if (!auth()->user()?->is_super_admin) { + if (! auth()->user()?->is_super_admin) { return response()->json([ 'success' => false, 'message' => '슈퍼관리자만 복원할 수 있습니다.', @@ -535,7 +535,7 @@ public function uploadImage(Request $request): JsonResponse } // API 토큰 교환 - $tokenService = new \App\Services\ApiTokenService(); + $tokenService = new \App\Services\ApiTokenService; $userId = auth()->id(); $tenantId = session('selected_tenant_id', 1); diff --git a/app/Http/Controllers/Api/Admin/FundScheduleController.php b/app/Http/Controllers/Api/Admin/FundScheduleController.php index e962146f..c6ab9d47 100644 --- a/app/Http/Controllers/Api/Admin/FundScheduleController.php +++ b/app/Http/Controllers/Api/Admin/FundScheduleController.php @@ -182,6 +182,7 @@ public function destroy(Request $request, int $id): JsonResponse|Response $month = $request->integer('month', now()->month); $calendarData = $this->fundScheduleService->getCalendarData($year, $month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); + return response(view('finance.fund-schedules.partials.calendar', compact('year', 'month', 'calendarData', 'summary'))); } @@ -229,6 +230,7 @@ public function updateStatus(Request $request, int $id): JsonResponse|Response $month = $request->integer('month', now()->month); $calendarData = $this->fundScheduleService->getCalendarData($year, $month); $summary = $this->fundScheduleService->getMonthlySummary($year, $month); + return response(view('finance.fund-schedules.partials.calendar', compact('year', 'month', 'calendarData', 'summary'))); } diff --git a/app/Http/Controllers/Api/Admin/GlobalCategoryApiController.php b/app/Http/Controllers/Api/Admin/GlobalCategoryApiController.php index 1acc24bd..e8f3f671 100644 --- a/app/Http/Controllers/Api/Admin/GlobalCategoryApiController.php +++ b/app/Http/Controllers/Api/Admin/GlobalCategoryApiController.php @@ -266,6 +266,7 @@ public function bulkCopyToTenant(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -288,6 +289,7 @@ public function bulkCopyToTenant(Request $request): JsonResponse ]); $idMap[$gc->id] = $trashed->id; $copied++; + continue; } @@ -336,4 +338,4 @@ public function bulkCopyToTenant(Request $request): JsonResponse ], 500); } } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/Admin/TenantUserApiController.php b/app/Http/Controllers/Api/Admin/TenantUserApiController.php index 02d1d437..abe88a72 100644 --- a/app/Http/Controllers/Api/Admin/TenantUserApiController.php +++ b/app/Http/Controllers/Api/Admin/TenantUserApiController.php @@ -23,7 +23,7 @@ public function search(Request $request): JsonResponse ->where('user_tenants.tenant_id', $tenantId) ->where('user_tenants.is_active', true); }) - ->leftJoin('departments', function ($join) use ($tenantId) { + ->leftJoin('departments', function ($join) { $join->on('departments.id', '=', DB::raw('( SELECT du.department_id FROM department_user du WHERE du.user_id = users.id AND du.is_primary = 1 diff --git a/app/Http/Controllers/AppVersionController.php b/app/Http/Controllers/AppVersionController.php index cf06bfc3..5e8c0f96 100644 --- a/app/Http/Controllers/AppVersionController.php +++ b/app/Http/Controllers/AppVersionController.php @@ -6,8 +6,8 @@ use App\Services\AppVersionService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; use Illuminate\Support\Facades\Storage; +use Illuminate\View\View; use Symfony\Component\HttpFoundation\StreamedResponse; class AppVersionController extends Controller diff --git a/app/Http/Controllers/Barobill/BarobillController.php b/app/Http/Controllers/Barobill/BarobillController.php index ae665a7a..aeb61658 100644 --- a/app/Http/Controllers/Barobill/BarobillController.php +++ b/app/Http/Controllers/Barobill/BarobillController.php @@ -77,9 +77,9 @@ public function settings(Request $request): View|Response $hasBarobillLogin = false; if ($barobillMember) { try { - $hasBarobillLogin = !empty($barobillMember->barobill_id) && !empty($barobillMember->barobill_pwd); + $hasBarobillLogin = ! empty($barobillMember->barobill_id) && ! empty($barobillMember->barobill_pwd); } catch (\Throwable $e) { - $hasBarobillLogin = !empty($barobillMember->barobill_id) && !empty($barobillMember->getRawOriginal('barobill_pwd')); + $hasBarobillLogin = ! empty($barobillMember->barobill_id) && ! empty($barobillMember->getRawOriginal('barobill_pwd')); } } @@ -179,8 +179,8 @@ public function searchTradingPartners(Request $request): JsonResponse if ($keyword) { $query->where(function ($q) use ($keyword) { $q->where('name', 'like', "%{$keyword}%") - ->orWhere('biz_no', 'like', "%{$keyword}%") - ->orWhere('manager', 'like', "%{$keyword}%"); + ->orWhere('biz_no', 'like', "%{$keyword}%") + ->orWhere('manager', 'like', "%{$keyword}%"); }); } diff --git a/app/Http/Controllers/Barobill/EtaxController.php b/app/Http/Controllers/Barobill/EtaxController.php index 976f9b2e..3ea549c3 100644 --- a/app/Http/Controllers/Barobill/EtaxController.php +++ b/app/Http/Controllers/Barobill/EtaxController.php @@ -21,9 +21,13 @@ class EtaxController extends Controller * 바로빌 SOAP 설정 */ private ?string $certKey = null; + private ?string $corpNum = null; + private bool $isTestMode = false; + private ?string $soapUrl = null; + private ?\SoapClient $soapClient = null; public function __construct() @@ -35,7 +39,7 @@ public function __construct() $this->certKey = $activeConfig->cert_key; $this->corpNum = $activeConfig->corp_num; $this->isTestMode = $activeConfig->environment === 'test'; - $this->soapUrl = $activeConfig->base_url . '/TI.asmx?WSDL'; + $this->soapUrl = $activeConfig->base_url.'/TI.asmx?WSDL'; } else { // 설정이 없으면 기본값 사용 $this->isTestMode = config('services.barobill.test_mode', true); @@ -57,14 +61,14 @@ public function __construct() */ private function initSoapClient(): void { - if (!empty($this->certKey) || $this->isTestMode) { + if (! empty($this->certKey) || $this->isTestMode) { try { $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, - 'allow_self_signed' => true - ] + 'allow_self_signed' => true, + ], ]); $this->soapClient = new \SoapClient($this->soapUrl, [ @@ -73,10 +77,10 @@ private function initSoapClient(): void 'exceptions' => true, 'connection_timeout' => 30, 'stream_context' => $context, - 'cache_wsdl' => WSDL_CACHE_NONE + 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (\Throwable $e) { - Log::error('바로빌 SOAP 클라이언트 생성 실패: ' . $e->getMessage()); + Log::error('바로빌 SOAP 클라이언트 생성 실패: '.$e->getMessage()); } } } @@ -135,14 +139,14 @@ private function applyMemberServerMode(BarobillMember $member): void $baseUrl = $config->base_url ?: ($memberTestMode ? 'https://testws.baroservice.com' : 'https://ws.baroservice.com'); - $this->soapUrl = $baseUrl . '/TI.asmx?WSDL'; + $this->soapUrl = $baseUrl.'/TI.asmx?WSDL'; // SOAP 클라이언트 재초기화 $this->initSoapClient(); Log::info('[Etax] 서버 모드 적용', [ 'targetEnv' => $targetEnv, - 'certKey' => substr($this->certKey ?? '', 0, 10) . '...', + 'certKey' => substr($this->certKey ?? '', 0, 10).'...', 'corpNum' => $this->corpNum, 'soapUrl' => $this->soapUrl, ]); @@ -173,7 +177,7 @@ public function getInvoices(): JsonResponse $allInvoices = $data['invoices'] ?? []; // 본사(테넌트 1)가 아니면 해당 테넌트의 세금계산서만 필터링 - if (!$isHeadquarters && $tenantId) { + if (! $isHeadquarters && $tenantId) { $invoices = array_values(array_filter($allInvoices, function ($invoice) use ($tenantId) { return ($invoice['tenant_id'] ?? null) == $tenantId; })); @@ -204,12 +208,12 @@ public function issue(Request $request): JsonResponse $input = $request->all(); - $useRealAPI = $this->soapClient !== null && ($this->isTestMode || !empty($this->certKey)); + $useRealAPI = $this->soapClient !== null && ($this->isTestMode || ! empty($this->certKey)); $debugInfo = [ 'hasSoapClient' => $this->soapClient !== null, - 'hasCertKey' => !empty($this->certKey), - 'hasCorpNum' => !empty($this->corpNum), + 'hasCertKey' => ! empty($this->certKey), + 'hasCorpNum' => ! empty($this->corpNum), 'isTestMode' => $this->isTestMode, 'willUseRealAPI' => $useRealAPI, ]; @@ -218,7 +222,7 @@ public function issue(Request $request): JsonResponse $apiResult = $this->issueTaxInvoice($input); if ($apiResult['success']) { - $mgtKey = $input['issueKey'] ?? 'MGT' . date('YmdHis') . rand(1000, 9999); + $mgtKey = $input['issueKey'] ?? 'MGT'.date('YmdHis').rand(1000, 9999); $newInvoice = $this->createInvoiceRecord($input, $mgtKey, $apiResult['data'] ?? null); $this->saveInvoice($newInvoice); @@ -245,7 +249,7 @@ public function issue(Request $request): JsonResponse } } else { // 시뮬레이션 모드 - $issueKey = 'BARO-' . date('Y') . '-' . str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT); + $issueKey = 'BARO-'.date('Y').'-'.str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT); $newInvoice = $this->createInvoiceRecord($input, $issueKey, null); $this->saveInvoice($newInvoice); @@ -293,16 +297,16 @@ public function sendToNts(Request $request): JsonResponse } } - if (!$invoice) { + if (! $invoice) { return response()->json([ 'success' => false, 'error' => '세금계산서를 찾을 수 없습니다.', ], 404); } - $useRealAPI = $this->soapClient !== null && !empty($this->certKey); + $useRealAPI = $this->soapClient !== null && ! empty($this->certKey); - if ($useRealAPI && !empty($invoice['mgtKey'])) { + if ($useRealAPI && ! empty($invoice['mgtKey'])) { $result = $this->callBarobillSOAP('SendToNTS', [ 'CorpNum' => $this->corpNum, 'MgtKey' => $invoice['mgtKey'], @@ -310,7 +314,7 @@ public function sendToNts(Request $request): JsonResponse if ($result['success']) { $data['invoices'][$invoiceIndex]['status'] = 'sent'; - $data['invoices'][$invoiceIndex]['ntsReceiptNo'] = 'NTS-' . date('YmdHis'); + $data['invoices'][$invoiceIndex]['ntsReceiptNo'] = 'NTS-'.date('YmdHis'); $data['invoices'][$invoiceIndex]['sentAt'] = date('Y-m-d'); file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); @@ -327,7 +331,7 @@ public function sendToNts(Request $request): JsonResponse } else { // 시뮬레이션 $data['invoices'][$invoiceIndex]['status'] = 'sent'; - $data['invoices'][$invoiceIndex]['ntsReceiptNo'] = 'NTS-SIM-' . date('YmdHis'); + $data['invoices'][$invoiceIndex]['ntsReceiptNo'] = 'NTS-SIM-'.date('YmdHis'); $data['invoices'][$invoiceIndex]['sentAt'] = date('Y-m-d'); file_put_contents($dataFile, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); @@ -345,12 +349,12 @@ public function sendToNts(Request $request): JsonResponse public function getSupplier(): JsonResponse { $tenantId = session('selected_tenant_id'); - if (!$tenantId) { + if (! $tenantId) { return response()->json(['success' => false, 'error' => '테넌트가 선택되지 않았습니다.'], 400); } $member = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$member) { + if (! $member) { return response()->json(['success' => false, 'error' => '바로빌 회원사 정보가 없습니다.'], 404); } @@ -376,12 +380,12 @@ public function getSupplier(): JsonResponse public function updateSupplier(Request $request): JsonResponse { $tenantId = session('selected_tenant_id'); - if (!$tenantId) { + if (! $tenantId) { return response()->json(['success' => false, 'error' => '테넌트가 선택되지 않았습니다.'], 400); } $member = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$member) { + if (! $member) { return response()->json(['success' => false, 'error' => '바로빌 회원사 정보가 없습니다.'], 404); } @@ -424,7 +428,7 @@ public function delete(Request $request): JsonResponse $dataFile = storage_path('app/barobill/invoices_data.json'); - if (!file_exists($dataFile)) { + if (! file_exists($dataFile)) { return response()->json([ 'success' => false, 'error' => '데이터 파일이 없습니다.', @@ -434,7 +438,7 @@ public function delete(Request $request): JsonResponse $data = json_decode(file_get_contents($dataFile), true) ?? ['invoices' => []]; $originalCount = count($data['invoices']); - $data['invoices'] = array_values(array_filter($data['invoices'], fn($inv) => $inv['id'] !== $invoiceId)); + $data['invoices'] = array_values(array_filter($data['invoices'], fn ($inv) => $inv['id'] !== $invoiceId)); if (count($data['invoices']) === $originalCount) { return response()->json([ @@ -456,20 +460,20 @@ public function delete(Request $request): JsonResponse */ private function callBarobillSOAP(string $method, array $params = []): array { - if (!$this->soapClient) { + if (! $this->soapClient) { return [ 'success' => false, 'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다.', ]; } - if (!isset($params['CERTKEY'])) { + if (! isset($params['CERTKEY'])) { $params['CERTKEY'] = $this->certKey; } try { $result = $this->soapClient->$method($params); - $resultProperty = $method . 'Result'; + $resultProperty = $method.'Result'; if (isset($result->$resultProperty)) { $resultData = $result->$resultProperty; @@ -477,7 +481,7 @@ private function callBarobillSOAP(string $method, array $params = []): array if (is_numeric($resultData) && $resultData < 0) { return [ 'success' => false, - 'error' => '바로빌 API 오류 코드: ' . $resultData, + 'error' => '바로빌 API 오류 코드: '.$resultData, 'error_code' => $resultData, ]; } @@ -495,12 +499,12 @@ private function callBarobillSOAP(string $method, array $params = []): array } catch (\SoapFault $e) { return [ 'success' => false, - 'error' => 'SOAP 오류: ' . $e->getMessage(), + 'error' => 'SOAP 오류: '.$e->getMessage(), ]; } catch (\Throwable $e) { return [ 'success' => false, - 'error' => 'API 호출 오류: ' . $e->getMessage(), + 'error' => 'API 호출 오류: '.$e->getMessage(), ]; } } @@ -510,7 +514,7 @@ private function callBarobillSOAP(string $method, array $params = []): array */ private function issueTaxInvoice(array $invoiceData): array { - $mgtKey = $invoiceData['issueKey'] ?? 'MGT' . date('YmdHis') . rand(1000, 9999); + $mgtKey = $invoiceData['issueKey'] ?? 'MGT'.date('YmdHis').rand(1000, 9999); $supplyAmt = 0; $vat = 0; @@ -583,7 +587,7 @@ private function issueTaxInvoice(array $invoiceData): array $month = str_pad($item['month'] ?? '', 2, '0', STR_PAD_LEFT); $day = str_pad($item['day'] ?? '', 2, '0', STR_PAD_LEFT); $purchaseExpiry = ($month && $day && $month !== '00' && $day !== '00') - ? $year . $month . $day + ? $year.$month.$day : ''; $taxInvoice['TaxInvoiceTradeLineItems']['TaxInvoiceTradeLineItem'][] = [ @@ -613,7 +617,7 @@ private function issueTaxInvoice(array $invoiceData): array private function createInvoiceRecord(array $input, string $issueKey, $apiData): array { return [ - 'id' => 'inv_' . time() . '_' . rand(1000, 9999), + 'id' => 'inv_'.time().'_'.rand(1000, 9999), 'tenant_id' => session('selected_tenant_id'), // 테넌트별 필터링용 'issueKey' => $issueKey, 'mgtKey' => $issueKey, @@ -638,7 +642,7 @@ private function createInvoiceRecord(array $input, string $issueKey, $apiData): 'memo' => $input['memo'] ?? '', 'createdAt' => date('Y-m-d\TH:i:s'), 'sentAt' => date('Y-m-d'), - 'barobillInvoiceId' => is_numeric($apiData) ? (string)$apiData : '', + 'barobillInvoiceId' => is_numeric($apiData) ? (string) $apiData : '', ]; } @@ -648,11 +652,11 @@ private function createInvoiceRecord(array $input, string $issueKey, $apiData): private function saveInvoice(array $invoice): bool { $dataDir = storage_path('app/barobill'); - if (!is_dir($dataDir)) { + if (! is_dir($dataDir)) { mkdir($dataDir, 0755, true); } - $dataFile = $dataDir . '/invoices_data.json'; + $dataFile = $dataDir.'/invoices_data.json'; $existingData = ['invoices' => []]; if (file_exists($dataFile)) { diff --git a/app/Http/Controllers/Barobill/HometaxController.php b/app/Http/Controllers/Barobill/HometaxController.php index 139547a8..fdbdea3f 100644 --- a/app/Http/Controllers/Barobill/HometaxController.php +++ b/app/Http/Controllers/Barobill/HometaxController.php @@ -14,7 +14,6 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\View\View; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -32,9 +31,13 @@ class HometaxController extends Controller * 바로빌 설정 */ private ?string $certKey = null; + private ?string $corpNum = null; + private bool $isTestMode = false; + private string $baseUrl = ''; + private ?\SoapClient $soapClient = null; // 바로빌 파트너사 (본사) 테넌트 ID @@ -72,26 +75,26 @@ public function __construct() */ private function initSoapClient(): void { - if (!empty($this->certKey) || $this->isTestMode) { + if (! empty($this->certKey) || $this->isTestMode) { try { $context = stream_context_create([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, - 'allow_self_signed' => true - ] + 'allow_self_signed' => true, + ], ]); - $this->soapClient = new \SoapClient($this->baseUrl . '/TI.asmx?WSDL', [ + $this->soapClient = new \SoapClient($this->baseUrl.'/TI.asmx?WSDL', [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'stream_context' => $context, - 'cache_wsdl' => WSDL_CACHE_NONE + 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (\Throwable $e) { - Log::error('바로빌 홈택스 SOAP 클라이언트 생성 실패: ' . $e->getMessage()); + Log::error('바로빌 홈택스 SOAP 클라이언트 생성 실패: '.$e->getMessage()); } } } @@ -155,7 +158,7 @@ private function applyMemberServerMode(BarobillMember $member): void Log::info('[Hometax] 서버 모드 적용', [ 'targetEnv' => $targetEnv, - 'certKey' => substr($this->certKey ?? '', 0, 10) . '...', + 'certKey' => substr($this->certKey ?? '', 0, 10).'...', 'corpNum' => $this->corpNum, 'baseUrl' => $this->baseUrl, ]); @@ -177,19 +180,19 @@ public function sales(Request $request): JsonResponse try { $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month'))); $endDate = $request->input('endDate', date('Ymd')); - $page = (int)$request->input('page', 1); - $limit = (int)$request->input('limit', 50); - $taxType = (int)$request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세 - $dateType = (int)$request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자 + $page = (int) $request->input('page', 1); + $limit = (int) $request->input('limit', 50); + $taxType = (int) $request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세 + $dateType = (int) $request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자 // 현재 테넌트의 바로빌 회원 정보 조회 $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$barobillMember) { + if (! $barobillMember) { return response()->json([ 'success' => false, - 'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.' + 'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.', ]); } @@ -203,7 +206,7 @@ public function sales(Request $request): JsonResponse if (empty($userId)) { return response()->json([ 'success' => false, - 'error' => '바로빌 사용자 ID가 설정되지 않았습니다.' + 'error' => '바로빌 사용자 ID가 설정되지 않았습니다.', ]); } @@ -221,18 +224,19 @@ public function sales(Request $request): JsonResponse 'StartDate' => $startDate, 'EndDate' => $endDate, 'CountPerPage' => $limit, - 'CurrentPage' => $page + 'CurrentPage' => $page, ]); - if (!$result['success']) { + if (! $result['success']) { // 첫 번째 조회 실패 시 에러 반환 if (empty($allInvoices)) { return response()->json([ 'success' => false, 'error' => $result['error'], - 'error_code' => $result['error_code'] ?? null + 'error_code' => $result['error_code'] ?? null, ]); } + continue; // 이미 일부 데이터가 있으면 계속 진행 } @@ -240,19 +244,20 @@ public function sales(Request $request): JsonResponse $errorCode = $this->checkErrorCode($resultData); // 에러 코드 체크 (데이터 없음 외의 에러) - if ($errorCode && !in_array($errorCode, [-60005, -60001])) { + if ($errorCode && ! in_array($errorCode, [-60005, -60001])) { if (empty($allInvoices)) { return response()->json([ 'success' => false, 'error' => $this->getErrorMessage($errorCode), - 'error_code' => $errorCode + 'error_code' => $errorCode, ]); } + continue; } // 데이터가 있는 경우 파싱 - if (!$errorCode || !in_array($errorCode, [-60005, -60001])) { + if (! $errorCode || ! in_array($errorCode, [-60005, -60001])) { $parsed = $this->parseInvoices($resultData, 'sales'); $allInvoices = array_merge($allInvoices, $parsed['invoices']); $totalSummary['totalAmount'] += $parsed['summary']['totalAmount']; @@ -265,13 +270,13 @@ public function sales(Request $request): JsonResponse 'currentPage' => $resultData->CurrentPage ?? 1, 'countPerPage' => $resultData->CountPerPage ?? 50, 'maxPageNum' => $resultData->MaxPageNum ?? 1, - 'maxIndex' => $resultData->MaxIndex ?? 0 + 'maxIndex' => $resultData->MaxIndex ?? 0, ]; } } // 작성일 기준으로 정렬 (최신순) - usort($allInvoices, fn($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? '')); + usort($allInvoices, fn ($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? '')); // 마지막 매출 수집 시간 업데이트 $barobillMember->update(['last_sales_fetch_at' => now()]); @@ -282,14 +287,15 @@ public function sales(Request $request): JsonResponse 'invoices' => $allInvoices, 'pagination' => $lastPagination, 'summary' => $totalSummary, - 'lastFetchAt' => now()->format('Y-m-d H:i:s') - ] + 'lastFetchAt' => now()->format('Y-m-d H:i:s'), + ], ]); } catch (\Throwable $e) { - Log::error('홈택스 매출 조회 오류: ' . $e->getMessage()); + Log::error('홈택스 매출 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '서버 오류: ' . $e->getMessage() + 'error' => '서버 오류: '.$e->getMessage(), ]); } } @@ -307,19 +313,19 @@ public function purchases(Request $request): JsonResponse try { $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month'))); $endDate = $request->input('endDate', date('Ymd')); - $page = (int)$request->input('page', 1); - $limit = (int)$request->input('limit', 50); - $taxType = (int)$request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세 - $dateType = (int)$request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자 + $page = (int) $request->input('page', 1); + $limit = (int) $request->input('limit', 50); + $taxType = (int) $request->input('taxType', 0); // 0:전체, 1:과세+영세, 3:면세 + $dateType = (int) $request->input('dateType', 1); // 1:작성일자, 2:발급일자, 3:전송일자 // 현재 테넌트의 바로빌 회원 정보 조회 $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$barobillMember) { + if (! $barobillMember) { return response()->json([ 'success' => false, - 'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.' + 'error' => '바로빌 회원사 정보가 없습니다. 테넌트 설정을 확인해주세요.', ]); } @@ -333,7 +339,7 @@ public function purchases(Request $request): JsonResponse if (empty($userId)) { return response()->json([ 'success' => false, - 'error' => '바로빌 사용자 ID가 설정되지 않았습니다.' + 'error' => '바로빌 사용자 ID가 설정되지 않았습니다.', ]); } @@ -351,18 +357,19 @@ public function purchases(Request $request): JsonResponse 'StartDate' => $startDate, 'EndDate' => $endDate, 'CountPerPage' => $limit, - 'CurrentPage' => $page + 'CurrentPage' => $page, ]); - if (!$result['success']) { + if (! $result['success']) { // 첫 번째 조회 실패 시 에러 반환 if (empty($allInvoices)) { return response()->json([ 'success' => false, 'error' => $result['error'], - 'error_code' => $result['error_code'] ?? null + 'error_code' => $result['error_code'] ?? null, ]); } + continue; // 이미 일부 데이터가 있으면 계속 진행 } @@ -370,19 +377,20 @@ public function purchases(Request $request): JsonResponse $errorCode = $this->checkErrorCode($resultData); // 에러 코드 체크 (데이터 없음 외의 에러) - if ($errorCode && !in_array($errorCode, [-60005, -60001])) { + if ($errorCode && ! in_array($errorCode, [-60005, -60001])) { if (empty($allInvoices)) { return response()->json([ 'success' => false, 'error' => $this->getErrorMessage($errorCode), - 'error_code' => $errorCode + 'error_code' => $errorCode, ]); } + continue; } // 데이터가 있는 경우 파싱 - if (!$errorCode || !in_array($errorCode, [-60005, -60001])) { + if (! $errorCode || ! in_array($errorCode, [-60005, -60001])) { $parsed = $this->parseInvoices($resultData, 'purchase'); $allInvoices = array_merge($allInvoices, $parsed['invoices']); $totalSummary['totalAmount'] += $parsed['summary']['totalAmount']; @@ -395,13 +403,13 @@ public function purchases(Request $request): JsonResponse 'currentPage' => $resultData->CurrentPage ?? 1, 'countPerPage' => $resultData->CountPerPage ?? 50, 'maxPageNum' => $resultData->MaxPageNum ?? 1, - 'maxIndex' => $resultData->MaxIndex ?? 0 + 'maxIndex' => $resultData->MaxIndex ?? 0, ]; } } // 작성일 기준으로 정렬 (최신순) - usort($allInvoices, fn($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? '')); + usort($allInvoices, fn ($a, $b) => strcmp($b['writeDate'] ?? '', $a['writeDate'] ?? '')); // 마지막 매입 수집 시간 업데이트 $barobillMember->update(['last_purchases_fetch_at' => now()]); @@ -412,14 +420,15 @@ public function purchases(Request $request): JsonResponse 'invoices' => $allInvoices, 'pagination' => $lastPagination, 'summary' => $totalSummary, - 'lastFetchAt' => now()->format('Y-m-d H:i:s') - ] + 'lastFetchAt' => now()->format('Y-m-d H:i:s'), + ], ]); } catch (\Throwable $e) { - Log::error('홈택스 매입 조회 오류: ' . $e->getMessage()); + Log::error('홈택스 매입 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '서버 오류: ' . $e->getMessage() + 'error' => '서버 오류: '.$e->getMessage(), ]); } } @@ -435,24 +444,24 @@ public function getScrapRequestUrl(Request $request): JsonResponse $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$barobillMember) { + if (! $barobillMember) { return response()->json([ 'success' => false, - 'error' => '바로빌 회원사 정보가 없습니다.' + 'error' => '바로빌 회원사 정보가 없습니다.', ]); } $userId = $barobillMember->barobill_id ?? ''; $result = $this->callSoap('GetTaxInvoiceScrapRequestURL', [ - 'UserID' => $userId + 'UserID' => $userId, ]); - if (!$result['success']) { + if (! $result['success']) { return response()->json([ 'success' => false, 'error' => $result['error'], - 'error_code' => $result['error_code'] ?? null + 'error_code' => $result['error_code'] ?? null, ]); } @@ -461,7 +470,7 @@ public function getScrapRequestUrl(Request $request): JsonResponse if (is_string($url) && filter_var($url, FILTER_VALIDATE_URL)) { return response()->json([ 'success' => true, - 'data' => ['url' => $url] + 'data' => ['url' => $url], ]); } @@ -469,20 +478,21 @@ public function getScrapRequestUrl(Request $request): JsonResponse if (is_numeric($url) && $url < 0) { return response()->json([ 'success' => false, - 'error' => $this->getErrorMessage((int)$url), - 'error_code' => (int)$url + 'error' => $this->getErrorMessage((int) $url), + 'error_code' => (int) $url, ]); } return response()->json([ 'success' => true, - 'data' => ['url' => (string)$url] + 'data' => ['url' => (string) $url], ]); } catch (\Throwable $e) { - Log::error('홈택스 스크래핑 URL 조회 오류: ' . $e->getMessage()); + Log::error('홈택스 스크래핑 URL 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '서버 오류: ' . $e->getMessage() + 'error' => '서버 오류: '.$e->getMessage(), ]); } } @@ -498,24 +508,24 @@ public function refreshScrap(Request $request): JsonResponse $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$barobillMember) { + if (! $barobillMember) { return response()->json([ 'success' => false, - 'error' => '바로빌 회원사 정보가 없습니다.' + 'error' => '바로빌 회원사 정보가 없습니다.', ]); } $userId = $barobillMember->barobill_id ?? ''; $result = $this->callSoap('RefreshTaxInvoiceScrap', [ - 'UserID' => $userId + 'UserID' => $userId, ]); - if (!$result['success']) { + if (! $result['success']) { return response()->json([ 'success' => false, 'error' => $result['error'], - 'error_code' => $result['error_code'] ?? null + 'error_code' => $result['error_code'] ?? null, ]); } @@ -524,25 +534,27 @@ public function refreshScrap(Request $request): JsonResponse if ($code < 0) { return response()->json([ 'success' => false, - 'error' => $this->getErrorMessage((int)$code), - 'error_code' => (int)$code + 'error' => $this->getErrorMessage((int) $code), + 'error_code' => (int) $code, ]); } + return response()->json([ 'success' => true, - 'message' => '홈택스 데이터 수집이 요청되었습니다. 잠시 후 다시 조회해주세요.' + 'message' => '홈택스 데이터 수집이 요청되었습니다. 잠시 후 다시 조회해주세요.', ]); } return response()->json([ 'success' => true, - 'message' => '홈택스 데이터 수집이 요청되었습니다.' + 'message' => '홈택스 데이터 수집이 요청되었습니다.', ]); } catch (\Throwable $e) { - Log::error('홈택스 스크래핑 갱신 오류: ' . $e->getMessage()); + Log::error('홈택스 스크래핑 갱신 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '서버 오류: ' . $e->getMessage() + 'error' => '서버 오류: '.$e->getMessage(), ]); } } @@ -568,29 +580,29 @@ public function diagnose(Request $request): JsonResponse $diagnostics = [ 'config' => [ - 'certKey' => !empty($this->certKey) ? substr($this->certKey, 0, 8) . '...' : '미설정', + 'certKey' => ! empty($this->certKey) ? substr($this->certKey, 0, 8).'...' : '미설정', 'corpNum' => $this->corpNum ?? '미설정', // 파트너사 사업자번호 (API 인증용) 'isTestMode' => $this->isTestMode, - 'baseUrl' => $this->baseUrl + 'baseUrl' => $this->baseUrl, ], 'member' => [ 'userId' => $userId ?: '미설정', // 테넌트의 바로빌 ID (API 호출에 사용) 'bizNo' => $memberCorpNum ?: '미설정', // 테넌트 사업자번호 (참고용) - 'corpName' => $barobillMember?->corp_name ?? '미설정' + 'corpName' => $barobillMember?->corp_name ?? '미설정', ], - 'tests' => [] + 'tests' => [], ]; // 테스트 1: 홈택스 스크래핑 URL 조회 (서비스 활성화 확인용) $scrapUrlResult = $this->callSoap('GetTaxInvoiceScrapRequestURL', [ - 'UserID' => $userId + 'UserID' => $userId, ]); $diagnostics['tests']['scrapRequestUrl'] = [ 'method' => 'GetTaxInvoiceScrapRequestURL', 'success' => $scrapUrlResult['success'], 'result' => $scrapUrlResult['success'] ? (is_string($scrapUrlResult['data']) ? '성공 (URL 반환)' : $scrapUrlResult['data']) - : ($scrapUrlResult['error'] ?? '오류') + : ($scrapUrlResult['error'] ?? '오류'), ]; // 테스트 2: 매출 세금계산서 조회 (기간: 최근 1개월) @@ -602,7 +614,7 @@ public function diagnose(Request $request): JsonResponse 'StartDate' => date('Ymd', strtotime('-1 month')), 'EndDate' => date('Ymd'), 'CountPerPage' => 1, - 'CurrentPage' => 1 + 'CurrentPage' => 1, ]); $diagnostics['tests']['salesList'] = [ 'method' => 'GetPeriodTaxInvoiceSalesList', @@ -611,7 +623,7 @@ public function diagnose(Request $request): JsonResponse ? ($this->checkErrorCode($salesResult['data']) ? $this->getErrorMessage($this->checkErrorCode($salesResult['data'])) : '성공') - : ($salesResult['error'] ?? '오류') + : ($salesResult['error'] ?? '오류'), ]; // 테스트 3: 잔액 조회 (기본 연결 및 인증 확인용) @@ -621,20 +633,21 @@ public function diagnose(Request $request): JsonResponse 'success' => $balanceResult['success'], 'result' => $balanceResult['success'] ? (is_numeric($balanceResult['data']) && $balanceResult['data'] >= 0 - ? '성공 (잔액: ' . number_format($balanceResult['data']) . '원)' + ? '성공 (잔액: '.number_format($balanceResult['data']).'원)' : $balanceResult['data']) - : ($balanceResult['error'] ?? '오류') + : ($balanceResult['error'] ?? '오류'), ]; return response()->json([ 'success' => true, - 'data' => $diagnostics + 'data' => $diagnostics, ]); } catch (\Throwable $e) { - Log::error('홈택스 서비스 진단 오류: ' . $e->getMessage()); + Log::error('홈택스 서비스 진단 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '서버 오류: ' . $e->getMessage() + 'error' => '서버 오류: '.$e->getMessage(), ]); } } @@ -666,8 +679,8 @@ public function collectStatus(Request $request): JsonResponse 'purchaseLastCollectDate' => $purchasesLastFetch ? $purchasesLastFetch->format('Y-m-d H:i') : '', 'isCollecting' => false, 'collectStateText' => ($salesLastFetch || $purchasesLastFetch) ? '조회 완료' : '조회 전', - 'message' => '매출/매입 탭을 클릭하면 데이터가 조회되고 수집 시간이 기록됩니다.' - ] + 'message' => '매출/매입 탭을 클릭하면 데이터가 조회되고 수집 시간이 기록됩니다.', + ], ]); } @@ -714,8 +727,8 @@ private function parseInvoices($resultData, string $type = 'sales'): array $writeDate = $this->getProperty($item, 'IssueDT', ''); } $formattedDate = ''; - if (!empty($writeDate) && strlen($writeDate) >= 8) { - $formattedDate = substr($writeDate, 0, 4) . '-' . substr($writeDate, 4, 2) . '-' . substr($writeDate, 6, 2); + if (! empty($writeDate) && strlen($writeDate) >= 8) { + $formattedDate = substr($writeDate, 0, 4).'-'.substr($writeDate, 4, 2).'-'.substr($writeDate, 6, 2); } // 과세유형 (int: 1=과세, 2=영세, 3=면세) @@ -757,8 +770,8 @@ private function parseInvoices($resultData, string $type = 'sales'): array 'totalAmount' => $totalAmount, 'totalTax' => $totalTax, 'totalSum' => $totalAmount + $totalTax, - 'count' => count($invoices) - ] + 'count' => count($invoices), + ], ]; } @@ -768,8 +781,9 @@ private function parseInvoices($resultData, string $type = 'sales'): array private function checkErrorCode($data): ?int { if (isset($data->CurrentPage) && is_numeric($data->CurrentPage) && $data->CurrentPage < 0) { - return (int)$data->CurrentPage; + return (int) $data->CurrentPage; } + return null; } @@ -794,7 +808,8 @@ private function getErrorMessage(int $errorCode): string -60010 => '홈택스 로그인 실패 (-60010). 부서사용자 ID/비밀번호를 확인해주세요.', -60011 => '홈택스 데이터 수집 중입니다 (-60011). 잠시 후 다시 조회해주세요.', ]; - return $messages[$errorCode] ?? '바로빌 API 오류: ' . $errorCode; + + return $messages[$errorCode] ?? '바로빌 API 오류: '.$errorCode; } /** @@ -802,7 +817,7 @@ private function getErrorMessage(int $errorCode): string */ private function getCollectTypeCode(string $type): int { - return match($type) { + return match ($type) { 'sales' => 1, 'purchase' => 2, default => 0 // all @@ -814,8 +829,9 @@ private function getCollectTypeCode(string $type): int */ private function getTaxTypeName(mixed $code): string { - $code = (string)$code; - return match($code) { + $code = (string) $code; + + return match ($code) { '1', '01' => '과세', '2', '02' => '영세', '3', '03' => '면세', @@ -828,8 +844,9 @@ private function getTaxTypeName(mixed $code): string */ private function getIssueTypeName(mixed $code): string { - $code = (string)$code; - return match($code) { + $code = (string) $code; + + return match ($code) { '1', '01' => '정발행', '2', '02' => '역발행', '3', '03' => '위수탁', @@ -842,8 +859,9 @@ private function getIssueTypeName(mixed $code): string */ private function getPurposeTypeName(mixed $code): string { - $code = (string)$code; - return match($code) { + $code = (string) $code; + + return match ($code) { '1', '01' => '영수', '2', '02' => '청구', default => $code ?: '-' @@ -875,29 +893,29 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse if (empty($invoices)) { return response()->json([ 'success' => false, - 'error' => '저장할 데이터가 없습니다.' + 'error' => '저장할 데이터가 없습니다.', ]); } $typeName = $type === 'sales' ? '매출' : '매입'; - $filename = "홈택스_{$typeName}_" . date('Ymd_His') . ".csv"; + $filename = "홈택스_{$typeName}_".date('Ymd_His').'.csv'; return response()->streamDownload(function () use ($invoices, $type) { $handle = fopen('php://output', 'w'); // UTF-8 BOM - fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF)); // 헤더 if ($type === 'sales') { fputcsv($handle, [ '작성일', '국세청승인번호', '공급받는자 사업자번호', '공급받는자 상호', - '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구' + '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구', ]); } else { fputcsv($handle, [ '작성일', '국세청승인번호', '공급자 사업자번호', '공급자 상호', - '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구' + '공급가액', '세액', '합계', '과세유형', '발급유형', '영수/청구', ]); } @@ -914,7 +932,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse $inv['totalAmount'] ?? 0, $inv['taxTypeName'] ?? '', $inv['issueTypeName'] ?? '', - $inv['purposeTypeName'] ?? '' + $inv['purposeTypeName'] ?? '', ]); } else { fputcsv($handle, [ @@ -927,7 +945,7 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse $inv['totalAmount'] ?? 0, $inv['taxTypeName'] ?? '', $inv['issueTypeName'] ?? '', - $inv['purposeTypeName'] ?? '' + $inv['purposeTypeName'] ?? '', ]); } } @@ -935,13 +953,14 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse fclose($handle); }, $filename, [ 'Content-Type' => 'text/csv; charset=utf-8', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + 'Content-Disposition' => 'attachment; filename="'.$filename.'"', ]); } catch (\Throwable $e) { - Log::error('홈택스 엑셀 다운로드 오류: ' . $e->getMessage()); + Log::error('홈택스 엑셀 다운로드 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '다운로드 오류: ' . $e->getMessage() + 'error' => '다운로드 오류: '.$e->getMessage(), ]); } } @@ -951,32 +970,32 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse */ private function callSoap(string $method, array $params = []): array { - if (!$this->soapClient) { + if (! $this->soapClient) { return [ 'success' => false, - 'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다.' + 'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다.', ]; } - if (empty($this->certKey) && !$this->isTestMode) { + if (empty($this->certKey) && ! $this->isTestMode) { return [ 'success' => false, - 'error' => 'CERTKEY가 설정되지 않았습니다.' + 'error' => 'CERTKEY가 설정되지 않았습니다.', ]; } if (empty($this->corpNum)) { return [ 'success' => false, - 'error' => '사업자번호가 설정되지 않았습니다.' + 'error' => '사업자번호가 설정되지 않았습니다.', ]; } // CERTKEY와 CorpNum 자동 추가 - if (!isset($params['CERTKEY'])) { + if (! isset($params['CERTKEY'])) { $params['CERTKEY'] = $this->certKey ?? ''; } - if (!isset($params['CorpNum'])) { + if (! isset($params['CorpNum'])) { $params['CorpNum'] = $this->corpNum; } @@ -984,18 +1003,18 @@ private function callSoap(string $method, array $params = []): array // 날짜 파라미터 로깅 (디버깅용) $dateInfo = ''; if (isset($params['StartDate']) || isset($params['EndDate'])) { - $dateInfo = ", StartDate: " . ($params['StartDate'] ?? 'N/A') . ", EndDate: " . ($params['EndDate'] ?? 'N/A'); + $dateInfo = ', StartDate: '.($params['StartDate'] ?? 'N/A').', EndDate: '.($params['EndDate'] ?? 'N/A'); } - Log::info("바로빌 홈택스 API 호출 (SoapClient) - Method: {$method}, CorpNum: " . ($params['CorpNum'] ?? 'N/A') . ", UserID: " . ($params['UserID'] ?? 'N/A') . ", CERTKEY: " . substr($params['CERTKEY'] ?? '', 0, 10) . "..." . $dateInfo); + Log::info("바로빌 홈택스 API 호출 (SoapClient) - Method: {$method}, CorpNum: ".($params['CorpNum'] ?? 'N/A').', UserID: '.($params['UserID'] ?? 'N/A').', CERTKEY: '.substr($params['CERTKEY'] ?? '', 0, 10).'...'.$dateInfo); // SoapClient로 호출 $result = $this->soapClient->$method($params); - $resultProperty = $method . 'Result'; + $resultProperty = $method.'Result'; - if (!isset($result->$resultProperty)) { + if (! isset($result->$resultProperty)) { return [ 'success' => false, - 'error' => '응답 결과를 찾을 수 없습니다.' + 'error' => '응답 결과를 찾을 수 없습니다.', ]; } @@ -1003,17 +1022,18 @@ private function callSoap(string $method, array $params = []): array // 단순 숫자 응답인 경우 (에러 코드 또는 성공 코드) if (is_numeric($resultData)) { - $code = (int)$resultData; + $code = (int) $resultData; if ($code < 0) { return [ 'success' => false, 'error' => $this->getErrorMessage($code), - 'error_code' => $code + 'error_code' => $code, ]; } + return [ 'success' => true, - 'data' => $code + 'data' => $code, ]; } @@ -1021,27 +1041,29 @@ private function callSoap(string $method, array $params = []): array if (is_string($resultData)) { return [ 'success' => true, - 'data' => $resultData + 'data' => $resultData, ]; } // 객체 응답 (목록 조회 등) return [ 'success' => true, - 'data' => $resultData + 'data' => $resultData, ]; } catch (\SoapFault $e) { - Log::error('바로빌 SOAP 오류: ' . $e->getMessage()); + Log::error('바로빌 SOAP 오류: '.$e->getMessage()); + return [ 'success' => false, - 'error' => 'SOAP 오류: ' . $e->getMessage() + 'error' => 'SOAP 오류: '.$e->getMessage(), ]; } catch (\Throwable $e) { - Log::error('바로빌 API 호출 오류: ' . $e->getMessage()); + Log::error('바로빌 API 호출 오류: '.$e->getMessage()); + return [ 'success' => false, - 'error' => 'API 호출 오류: ' . $e->getMessage() + 'error' => 'API 호출 오류: '.$e->getMessage(), ]; } } @@ -1053,15 +1075,15 @@ private function buildSoapRequest(string $method, array $params): string { $paramsXml = ''; foreach ($params as $key => $value) { - $paramsXml .= "<{$key}>" . htmlspecialchars((string)$value, ENT_XML1, 'UTF-8') . ""; + $paramsXml .= "<{$key}>".htmlspecialchars((string) $value, ENT_XML1, 'UTF-8').""; } return ' - - ' . $paramsXml . ' - + + '.$paramsXml.' + '; } @@ -1077,7 +1099,7 @@ private function parseSoapResponse(string $xmlResponse, string $method): array if ($xml === false) { return [ 'success' => false, - 'error' => 'XML 파싱 실패' + 'error' => 'XML 파싱 실패', ]; } @@ -1095,28 +1117,30 @@ private function parseSoapResponse(string $xmlResponse, string $method): array if (empty($resultNodes)) { Log::warning("응답에서 {$method}Result를 찾을 수 없음"); + return [ 'success' => false, - 'error' => '응답 결과를 찾을 수 없습니다.' + 'error' => '응답 결과를 찾을 수 없습니다.', ]; } $resultNode = $resultNodes[0]; // 단순 숫자 응답인 경우 (에러 코드) - $textContent = trim((string)$resultNode); + $textContent = trim((string) $resultNode); if (is_numeric($textContent) && $resultNode->count() === 0) { - $code = (int)$textContent; + $code = (int) $textContent; if ($code < 0) { return [ 'success' => false, 'error' => $this->getErrorMessage($code), - 'error_code' => $code + 'error_code' => $code, ]; } + return [ 'success' => true, - 'data' => $code + 'data' => $code, ]; } @@ -1125,14 +1149,15 @@ private function parseSoapResponse(string $xmlResponse, string $method): array return [ 'success' => true, - 'data' => $resultData + 'data' => $resultData, ]; } catch (\Throwable $e) { - Log::error('SOAP 응답 파싱 오류: ' . $e->getMessage()); + Log::error('SOAP 응답 파싱 오류: '.$e->getMessage()); + return [ 'success' => false, - 'error' => 'XML 파싱 오류: ' . $e->getMessage() + 'error' => 'XML 파싱 오류: '.$e->getMessage(), ]; } } @@ -1142,11 +1167,11 @@ private function parseSoapResponse(string $xmlResponse, string $method): array */ private function xmlToObject(\SimpleXMLElement $xml): object { - $result = new \stdClass(); + $result = new \stdClass; // 속성 처리 foreach ($xml->attributes() as $attrName => $attrValue) { - $result->$attrName = (string)$attrValue; + $result->$attrName = (string) $attrValue; } // 자식 요소 처리 @@ -1160,20 +1185,20 @@ private function xmlToObject(\SimpleXMLElement $xml): object foreach ($children as $name => $child) { if ($childNames[$name] > 1) { // 여러 개의 동일 이름 요소 → 배열 - if (!isset($result->$name)) { + if (! isset($result->$name)) { $result->$name = []; } if ($child->count() > 0) { $result->{$name}[] = $this->xmlToObject($child); } else { - $result->{$name}[] = (string)$child; + $result->{$name}[] = (string) $child; } - } else if ($child->count() > 0) { + } elseif ($child->count() > 0) { // 자식이 있는 요소 → 재귀 호출 $result->$name = $this->xmlToObject($child); } else { // 텍스트 노드 - $result->$name = (string)$child; + $result->$name = (string) $child; } } @@ -1193,10 +1218,10 @@ public function localSales(Request $request, HometaxSyncService $syncService): J // YYYYMMDD 형식을 Y-m-d로 변환 if (strlen($startDate) === 8) { - $startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); + $startDate = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2); } if (strlen($endDate) === 8) { - $endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); + $endDate = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2); } $dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue'; @@ -1217,10 +1242,11 @@ public function localSales(Request $request, HometaxSyncService $syncService): J 'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'sales'), ]); } catch (\Throwable $e) { - Log::error('로컬 매출 조회 오류: ' . $e->getMessage()); + Log::error('로컬 매출 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '조회 오류: ' . $e->getMessage() + 'error' => '조회 오류: '.$e->getMessage(), ]); } } @@ -1238,10 +1264,10 @@ public function localPurchases(Request $request, HometaxSyncService $syncService // YYYYMMDD 형식을 Y-m-d로 변환 if (strlen($startDate) === 8) { - $startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); + $startDate = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2); } if (strlen($endDate) === 8) { - $endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); + $endDate = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2); } $dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue'; @@ -1262,10 +1288,11 @@ public function localPurchases(Request $request, HometaxSyncService $syncService 'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'purchase'), ]); } catch (\Throwable $e) { - Log::error('로컬 매입 조회 오류: ' . $e->getMessage()); + Log::error('로컬 매입 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '조회 오류: ' . $e->getMessage() + 'error' => '조회 오류: '.$e->getMessage(), ]); } } @@ -1280,7 +1307,7 @@ public function sync(Request $request, HometaxSyncService $syncService): JsonRes $type = $request->input('type', 'all'); // 'sales', 'purchase', 'all' $startDate = $request->input('startDate', date('Ymd', strtotime('-1 month'))); $endDate = $request->input('endDate', date('Ymd')); - $dateType = (int)$request->input('dateType', 1); + $dateType = (int) $request->input('dateType', 1); $results = []; @@ -1296,7 +1323,7 @@ public function sync(Request $request, HometaxSyncService $syncService): JsonRes $salesResponse = $this->sales($salesRequest); $salesData = json_decode($salesResponse->getContent(), true); - if ($salesData['success'] && !empty($salesData['data']['invoices'])) { + if ($salesData['success'] && ! empty($salesData['data']['invoices'])) { $results['sales'] = $syncService->syncInvoices( $salesData['data']['invoices'], $tenantId, @@ -1325,7 +1352,7 @@ public function sync(Request $request, HometaxSyncService $syncService): JsonRes $purchaseResponse = $this->purchases($purchaseRequest); $purchaseData = json_decode($purchaseResponse->getContent(), true); - if ($purchaseData['success'] && !empty($purchaseData['data']['invoices'])) { + if ($purchaseData['success'] && ! empty($purchaseData['data']['invoices'])) { $results['purchase'] = $syncService->syncInvoices( $purchaseData['data']['invoices'], $tenantId, @@ -1352,10 +1379,11 @@ public function sync(Request $request, HometaxSyncService $syncService): JsonRes 'data' => $results, ]); } catch (\Throwable $e) { - Log::error('홈택스 동기화 오류: ' . $e->getMessage()); + Log::error('홈택스 동기화 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '동기화 오류: ' . $e->getMessage() + 'error' => '동기화 오류: '.$e->getMessage(), ]); } } @@ -1371,7 +1399,7 @@ public function autoSync(Request $request, HometaxSyncService $syncService): Jso $type = $request->input('type', 'sales'); // 'sales' 또는 'purchase' $barobillMember = BarobillMember::where('tenant_id', $tenantId)->first(); - if (!$barobillMember) { + if (! $barobillMember) { return response()->json(['success' => true, 'skipped' => true, 'reason' => 'no_member']); } @@ -1411,7 +1439,7 @@ public function autoSync(Request $request, HometaxSyncService $syncService): Jso $syncResult = ['inserted' => 0, 'updated' => 0, 'total' => 0]; - if ($apiData['success'] && !empty($apiData['data']['invoices'])) { + if ($apiData['success'] && ! empty($apiData['data']['invoices'])) { $syncResult = $syncService->syncInvoices( $apiData['data']['invoices'], $tenantId, @@ -1428,7 +1456,8 @@ public function autoSync(Request $request, HometaxSyncService $syncService): Jso 'lastFetchAt' => now()->format('Y-m-d H:i:s'), ]); } catch (\Throwable $e) { - Log::error('홈택스 자동동기화 오류: ' . $e->getMessage()); + Log::error('홈택스 자동동기화 오류: '.$e->getMessage()); + // 자동동기화 실패는 치명적이지 않음 - 로컬 데이터는 정상 표시됨 return response()->json([ 'success' => true, @@ -1458,7 +1487,7 @@ public function updateMemo(Request $request, HometaxSyncService $syncService): J } catch (\Throwable $e) { return response()->json([ 'success' => false, - 'error' => '오류: ' . $e->getMessage() + 'error' => '오류: '.$e->getMessage(), ]); } } @@ -1480,7 +1509,7 @@ public function toggleChecked(Request $request, HometaxSyncService $syncService) } catch (\Throwable $e) { return response()->json([ 'success' => false, - 'error' => '오류: ' . $e->getMessage() + 'error' => '오류: '.$e->getMessage(), ]); } } @@ -1542,12 +1571,12 @@ public function manualStore(Request $request): JsonResponse $seq = 1; if ($lastNum) { $parts = explode('-', $lastNum); - $seq = (int)end($parts) + 1; + $seq = (int) end($parts) + 1; } $ntsConfirmNum = sprintf('MAN-%s-%03d', $dateStr, $seq); - $taxAmount = (float)($validated['tax_amount'] ?? 0); - $totalAmount = (float)$validated['supply_amount'] + $taxAmount; + $taxAmount = (float) ($validated['tax_amount'] ?? 0); + $totalAmount = (float) $validated['supply_amount'] + $taxAmount; $invoice = HometaxInvoice::create([ 'tenant_id' => $tenantId, @@ -1591,13 +1620,14 @@ public function manualStore(Request $request): JsonResponse } catch (\Illuminate\Validation\ValidationException $e) { return response()->json([ 'success' => false, - 'error' => '입력값 오류: ' . implode(', ', $e->validator->errors()->all()), + 'error' => '입력값 오류: '.implode(', ', $e->validator->errors()->all()), ], 422); } catch (\Throwable $e) { - Log::error('수동 세금계산서 저장 오류: ' . $e->getMessage()); + Log::error('수동 세금계산서 저장 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '저장 오류: ' . $e->getMessage(), + 'error' => '저장 오류: '.$e->getMessage(), ]); } } @@ -1614,7 +1644,7 @@ public function manualUpdate(Request $request, int $id): JsonResponse ->where('tenant_id', $tenantId) ->firstOrFail(); - if (!str_starts_with($invoice->nts_confirm_num, 'MAN-')) { + if (! str_starts_with($invoice->nts_confirm_num, 'MAN-')) { return response()->json([ 'success' => false, 'error' => '수동 입력 건만 수정할 수 있습니다.', @@ -1667,7 +1697,7 @@ public function manualUpdate(Request $request, int $id): JsonResponse $supply = $validated['supply_amount'] ?? $invoice->supply_amount; $tax = $validated['tax_amount'] ?? $invoice->tax_amount; - $validated['total_amount'] = (float)$supply + (float)$tax; + $validated['total_amount'] = (float) $supply + (float) $tax; $invoice->update($validated); @@ -1682,10 +1712,11 @@ public function manualUpdate(Request $request, int $id): JsonResponse 'error' => '해당 세금계산서를 찾을 수 없습니다.', ], 404); } catch (\Throwable $e) { - Log::error('수동 세금계산서 수정 오류: ' . $e->getMessage()); + Log::error('수동 세금계산서 수정 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '수정 오류: ' . $e->getMessage(), + 'error' => '수정 오류: '.$e->getMessage(), ]); } } @@ -1702,7 +1733,7 @@ public function manualDestroy(int $id): JsonResponse ->where('tenant_id', $tenantId) ->firstOrFail(); - if (!str_starts_with($invoice->nts_confirm_num, 'MAN-')) { + if (! str_starts_with($invoice->nts_confirm_num, 'MAN-')) { return response()->json([ 'success' => false, 'error' => '수동 입력 건만 삭제할 수 있습니다.', @@ -1721,10 +1752,11 @@ public function manualDestroy(int $id): JsonResponse 'error' => '해당 세금계산서를 찾을 수 없습니다.', ], 404); } catch (\Throwable $e) { - Log::error('수동 세금계산서 삭제 오류: ' . $e->getMessage()); + Log::error('수동 세금계산서 삭제 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '삭제 오류: ' . $e->getMessage(), + 'error' => '삭제 오류: '.$e->getMessage(), ]); } } @@ -1776,6 +1808,7 @@ public function createJournalEntry(Request $request): JsonResponse $errors = $e->errors(); $firstError = collect($errors)->flatten()->first() ?? '입력 데이터가 올바르지 않습니다.'; Log::error('분개 저장 검증 오류', ['errors' => $errors]); + return response()->json([ 'success' => false, 'error' => $firstError, @@ -1787,10 +1820,11 @@ public function createJournalEntry(Request $request): JsonResponse 'error' => '해당 세금계산서를 찾을 수 없습니다.', ], 404); } catch (\Throwable $e) { - Log::error('분개 저장 오류: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]); + Log::error('분개 저장 오류: '.$e->getMessage(), ['trace' => $e->getTraceAsString()]); + return response()->json([ 'success' => false, - 'error' => '분개 저장 오류: ' . $e->getMessage(), + 'error' => '분개 저장 오류: '.$e->getMessage(), ]); } } @@ -1804,11 +1838,11 @@ public function getJournals(Request $request): JsonResponse $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $invoiceId = $request->input('invoice_id'); - if (!$invoiceId) { + if (! $invoiceId) { return response()->json(['success' => false, 'error' => 'invoice_id는 필수입니다.'], 422); } - $journals = HometaxInvoiceJournal::getByInvoiceId($tenantId, (int)$invoiceId); + $journals = HometaxInvoiceJournal::getByInvoiceId($tenantId, (int) $invoiceId); return response()->json([ 'success' => true, @@ -1824,10 +1858,11 @@ public function getJournals(Request $request): JsonResponse }), ]); } catch (\Throwable $e) { - Log::error('분개 조회 오류: ' . $e->getMessage()); + Log::error('분개 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '분개 조회 오류: ' . $e->getMessage(), + 'error' => '분개 조회 오류: '.$e->getMessage(), ]); } } @@ -1841,21 +1876,22 @@ public function deleteJournals(Request $request): JsonResponse $tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID); $invoiceId = $request->input('invoice_id'); - if (!$invoiceId) { + if (! $invoiceId) { return response()->json(['success' => false, 'error' => 'invoice_id는 필수입니다.'], 422); } - $deleted = HometaxInvoiceJournal::deleteJournals($tenantId, (int)$invoiceId); + $deleted = HometaxInvoiceJournal::deleteJournals($tenantId, (int) $invoiceId); return response()->json([ 'success' => true, 'message' => "분개가 삭제되었습니다. ({$deleted}건)", ]); } catch (\Throwable $e) { - Log::error('분개 삭제 오류: ' . $e->getMessage()); + Log::error('분개 삭제 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '분개 삭제 오류: ' . $e->getMessage(), + 'error' => '분개 삭제 오류: '.$e->getMessage(), ]); } } @@ -1877,11 +1913,11 @@ public function cardTransactions(Request $request): JsonResponse ->orderByDesc('use_date') ->orderByDesc('use_time'); - if (!empty($search)) { + if (! empty($search)) { $query->where(function ($q) use ($search) { $q->where('merchant_name', 'like', "%{$search}%") - ->orWhere('merchant_biz_num', 'like', "%{$search}%") - ->orWhere('approval_num', 'like', "%{$search}%"); + ->orWhere('merchant_biz_num', 'like', "%{$search}%") + ->orWhere('approval_num', 'like', "%{$search}%"); }); } @@ -1893,10 +1929,10 @@ public function cardTransactions(Request $request): JsonResponse 'merchantName' => $t->merchant_name, 'merchantBizNum' => $t->merchant_biz_num, 'approvalNum' => $t->approval_num, - 'approvalAmount' => (float)$t->approval_amount, + 'approvalAmount' => (float) $t->approval_amount, 'approvalAmountFormatted' => number_format($t->approval_amount), - 'tax' => (float)($t->tax ?? 0), - 'supplyAmount' => (float)($t->modified_supply_amount ?: ($t->approval_amount - ($t->tax ?? 0))), + 'tax' => (float) ($t->tax ?? 0), + 'supplyAmount' => (float) ($t->modified_supply_amount ?: ($t->approval_amount - ($t->tax ?? 0))), 'cardNum' => $t->card_num ? substr($t->card_num, -4) : '', 'cardCompanyName' => $t->card_company_name ?? '', ]; @@ -1907,10 +1943,11 @@ public function cardTransactions(Request $request): JsonResponse 'data' => $transactions, ]); } catch (\Throwable $e) { - Log::error('카드내역 조회 오류: ' . $e->getMessage()); + Log::error('카드내역 조회 오류: '.$e->getMessage()); + return response()->json([ 'success' => false, - 'error' => '조회 오류: ' . $e->getMessage(), + 'error' => '조회 오류: '.$e->getMessage(), ]); } } diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index d21cc359..14a8a786 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -208,7 +208,7 @@ public function index(Request $request): View|Response private function flattenTree($categories): \Illuminate\Support\Collection { $result = collect(); - $byParent = $categories->groupBy(fn($c) => $c->parent_id ?? 0); + $byParent = $categories->groupBy(fn ($c) => $c->parent_id ?? 0); $this->addChildrenRecursive($result, $byParent, 0, 0); diff --git a/app/Http/Controllers/CategorySyncController.php b/app/Http/Controllers/CategorySyncController.php index 301b16b9..63f2cc2c 100644 --- a/app/Http/Controllers/CategorySyncController.php +++ b/app/Http/Controllers/CategorySyncController.php @@ -22,6 +22,7 @@ class CategorySyncController extends Controller protected function getTenantId(): int { $tenantId = session('selected_tenant_id'); + return ($tenantId && $tenantId !== 'all') ? (int) $tenantId : 1; } @@ -155,6 +156,7 @@ public function import(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -188,6 +190,7 @@ public function import(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -218,7 +221,7 @@ public function import(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => "{$imported}개 카테고리가 동기화되었습니다." . ($skipped > 0 ? " ({$skipped}개 스킵)" : ''), + 'message' => "{$imported}개 카테고리가 동기화되었습니다.".($skipped > 0 ? " ({$skipped}개 스킵)" : ''), 'imported' => $imported, 'skipped' => $skipped, ]); @@ -247,6 +250,7 @@ public function push(Request $request): JsonResponse $localCategories = $this->getCategoryList($validated['type']); $selectedCategories = array_filter($localCategories, function ($cat) use ($validated) { $key = $this->makeCategoryKey($cat); + return in_array($key, $validated['category_keys']); }); @@ -259,7 +263,7 @@ public function push(Request $request): JsonResponse $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->post(rtrim($env['url'], '/') . '/category-sync/import', [ + ])->post(rtrim($env['url'], '/').'/category-sync/import', [ 'categories' => array_values($selectedCategories), ]); @@ -276,7 +280,7 @@ public function push(Request $request): JsonResponse 'error' => $response->json('error', '원격 서버 오류'), ], $response->status()); } catch (\Exception $e) { - return response()->json(['error' => '연결 실패: ' . $e->getMessage()], 500); + return response()->json(['error' => '연결 실패: '.$e->getMessage()], 500); } } @@ -309,6 +313,7 @@ public function pull(Request $request): JsonResponse // 선택된 카테고리만 필터링 $selectedCategories = array_filter($remoteCategories, function ($cat) use ($validated) { $key = $this->makeCategoryKey($cat); + return in_array($key, $validated['category_keys']); }); @@ -328,6 +333,7 @@ public function pull(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -359,6 +365,7 @@ public function pull(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -388,7 +395,7 @@ public function pull(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => "{$imported}개 카테고리가 동기화되었습니다." . ($skipped > 0 ? " ({$skipped}개 스킵)" : ''), + 'message' => "{$imported}개 카테고리가 동기화되었습니다.".($skipped > 0 ? " ({$skipped}개 스킵)" : ''), 'imported' => $imported, 'skipped' => $skipped, ]); @@ -396,7 +403,8 @@ public function pull(Request $request): JsonResponse /** * 카테고리 목록 조회 - * @param string $type 'global', 'tenant', or 'all' + * + * @param string $type 'global', 'tenant', or 'all' */ private function getCategoryList(string $type = 'all'): array { @@ -472,12 +480,12 @@ private function fetchRemoteCategories(array $env, string $type = 'all'): array $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($env['url'], '/') . '/category-sync/export', [ + ])->timeout(10)->get(rtrim($env['url'], '/').'/category-sync/export', [ 'type' => $type, ]); if (! $response->successful()) { - throw new \Exception('API 오류: HTTP ' . $response->status()); + throw new \Exception('API 오류: HTTP '.$response->status()); } $data = $response->json(); @@ -496,6 +504,7 @@ private function fetchRemoteCategories(array $env, string $type = 'all'): array private function makeCategoryKey(array $cat): string { $typePart = $cat['is_global'] ? 'global' : "tenant:{$cat['tenant_id']}"; + return "{$typePart}:{$cat['code_group']}:{$cat['code']}"; } @@ -504,8 +513,8 @@ private function makeCategoryKey(array $cat): string */ private function calculateDiff(array $localCategories, array $remoteCategories): array { - $localKeys = array_map(fn($c) => $this->makeCategoryKey($c), $localCategories); - $remoteKeys = array_map(fn($c) => $this->makeCategoryKey($c), $remoteCategories); + $localKeys = array_map(fn ($c) => $this->makeCategoryKey($c), $localCategories); + $remoteKeys = array_map(fn ($c) => $this->makeCategoryKey($c), $remoteCategories); return [ 'local_only' => array_values(array_diff($localKeys, $remoteKeys)), diff --git a/app/Http/Controllers/CommonCodeController.php b/app/Http/Controllers/CommonCodeController.php index bcc388a5..147d333b 100644 --- a/app/Http/Controllers/CommonCodeController.php +++ b/app/Http/Controllers/CommonCodeController.php @@ -10,7 +10,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class CommonCodeController extends Controller @@ -272,6 +271,7 @@ public function update(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '테넌트를 먼저 선택해주세요.'], 400); } + return redirect()->back()->with('error', '테넌트를 먼저 선택해주세요.'); } @@ -282,6 +282,7 @@ public function update(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '코드를 찾을 수 없습니다.'], 404); } + return redirect()->back()->with('error', '코드를 찾을 수 없습니다.'); } @@ -294,6 +295,7 @@ public function update(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '글로벌 코드는 본사만 수정할 수 있습니다.'], 403); } + return redirect()->back()->with('error', '글로벌 코드는 본사만 수정할 수 있습니다.'); } @@ -302,6 +304,7 @@ public function update(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '다른 테넌트의 코드는 수정할 수 없습니다.'], 403); } + return redirect()->back()->with('error', '다른 테넌트의 코드는 수정할 수 없습니다.'); } } @@ -393,6 +396,7 @@ public function bulkPromoteToGlobal(Request $request): RedirectResponse|JsonResp if ($request->ajax()) { return response()->json(['error' => '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'], 403); } + return redirect()->back()->with('error', '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'); } @@ -429,6 +433,7 @@ public function bulkPromoteToGlobal(Request $request): RedirectResponse|JsonResp if ($exists) { $skippedCount++; + continue; } @@ -447,6 +452,7 @@ public function bulkPromoteToGlobal(Request $request): RedirectResponse|JsonResp DB::commit(); } catch (\Exception $e) { DB::rollBack(); + return redirect()->back()->with('error', '복사 중 오류가 발생했습니다.'); } @@ -471,6 +477,7 @@ public function copy(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '테넌트를 먼저 선택해주세요.'], 400); } + return redirect()->back()->with('error', '테넌트를 먼저 선택해주세요.'); } @@ -479,6 +486,7 @@ public function copy(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '글로벌 코드를 찾을 수 없습니다.'], 404); } + return redirect()->back()->with('error', '글로벌 코드를 찾을 수 없습니다.'); } @@ -493,6 +501,7 @@ public function copy(Request $request, int $id): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '이미 복사된 코드가 있습니다.'], 400); } + return redirect()->back()->with('error', '이미 복사된 코드가 있습니다.'); } @@ -527,6 +536,7 @@ public function bulkCopy(Request $request): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '테넌트를 먼저 선택해주세요.'], 400); } + return redirect()->back()->with('error', '테넌트를 먼저 선택해주세요.'); } @@ -538,6 +548,7 @@ public function bulkCopy(Request $request): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '복사할 코드를 선택해주세요.'], 400); } + return redirect()->back()->with('error', '복사할 코드를 선택해주세요.'); } } else { @@ -570,6 +581,7 @@ public function bulkCopy(Request $request): RedirectResponse|JsonResponse if ($exists) { $skippedCount++; + continue; } @@ -593,6 +605,7 @@ public function bulkCopy(Request $request): RedirectResponse|JsonResponse if ($request->ajax()) { return response()->json(['error' => '복사 중 오류가 발생했습니다.'], 500); } + return redirect()->back()->with('error', '복사 중 오류가 발생했습니다.'); } @@ -624,6 +637,7 @@ public function promoteToGlobal(Request $request, int $id): RedirectResponse|Jso if ($request->ajax()) { return response()->json(['error' => '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'], 403); } + return redirect()->back()->with('error', '본사 또는 슈퍼관리자만 글로벌로 복사할 수 있습니다.'); } @@ -632,6 +646,7 @@ public function promoteToGlobal(Request $request, int $id): RedirectResponse|Jso if ($request->ajax()) { return response()->json(['error' => '테넌트 코드를 찾을 수 없습니다.'], 404); } + return redirect()->back()->with('error', '테넌트 코드를 찾을 수 없습니다.'); } @@ -646,6 +661,7 @@ public function promoteToGlobal(Request $request, int $id): RedirectResponse|Jso if ($request->ajax()) { return response()->json(['error' => '이미 동일한 글로벌 코드가 존재합니다.'], 400); } + return redirect()->back()->with('error', '이미 동일한 글로벌 코드가 존재합니다.'); } @@ -680,6 +696,7 @@ public function destroy(Request $request, int $id): RedirectResponse|JsonRespons if ($request->ajax()) { return response()->json(['error' => '테넌트를 먼저 선택해주세요.'], 400); } + return redirect()->back()->with('error', '테넌트를 먼저 선택해주세요.'); } @@ -690,6 +707,7 @@ public function destroy(Request $request, int $id): RedirectResponse|JsonRespons if ($request->ajax()) { return response()->json(['error' => '코드를 찾을 수 없습니다.'], 404); } + return redirect()->back()->with('error', '코드를 찾을 수 없습니다.'); } @@ -702,6 +720,7 @@ public function destroy(Request $request, int $id): RedirectResponse|JsonRespons if ($request->ajax()) { return response()->json(['error' => '글로벌 코드는 본사만 삭제할 수 있습니다.'], 403); } + return redirect()->back()->with('error', '글로벌 코드는 본사만 삭제할 수 있습니다.'); } @@ -710,6 +729,7 @@ public function destroy(Request $request, int $id): RedirectResponse|JsonRespons if ($request->ajax()) { return response()->json(['error' => '다른 테넌트의 코드는 삭제할 수 없습니다.'], 403); } + return redirect()->back()->with('error', '다른 테넌트의 코드는 삭제할 수 없습니다.'); } } diff --git a/app/Http/Controllers/CommonCodeSyncController.php b/app/Http/Controllers/CommonCodeSyncController.php index 455cc453..45d63a1f 100644 --- a/app/Http/Controllers/CommonCodeSyncController.php +++ b/app/Http/Controllers/CommonCodeSyncController.php @@ -21,6 +21,7 @@ class CommonCodeSyncController extends Controller protected function getTenantId(): int { $tenantId = session('selected_tenant_id'); + return ($tenantId && $tenantId !== 'all') ? (int) $tenantId : 1; } @@ -153,6 +154,7 @@ public function import(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -170,7 +172,7 @@ public function import(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => "{$imported}개 코드가 동기화되었습니다." . ($skipped > 0 ? " ({$skipped}개 스킵)" : ''), + 'message' => "{$imported}개 코드가 동기화되었습니다.".($skipped > 0 ? " ({$skipped}개 스킵)" : ''), 'imported' => $imported, 'skipped' => $skipped, ]); @@ -199,6 +201,7 @@ public function push(Request $request): JsonResponse $localCodes = $this->getCodeList($validated['type']); $selectedCodes = array_filter($localCodes, function ($code) use ($validated) { $key = $this->makeCodeKey($code); + return in_array($key, $validated['code_keys']); }); @@ -211,7 +214,7 @@ public function push(Request $request): JsonResponse $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->post(rtrim($env['url'], '/') . '/common-code-sync/import', [ + ])->post(rtrim($env['url'], '/').'/common-code-sync/import', [ 'codes' => array_values($selectedCodes), ]); @@ -228,7 +231,7 @@ public function push(Request $request): JsonResponse 'error' => $response->json('error', '원격 서버 오류'), ], $response->status()); } catch (\Exception $e) { - return response()->json(['error' => '연결 실패: ' . $e->getMessage()], 500); + return response()->json(['error' => '연결 실패: '.$e->getMessage()], 500); } } @@ -261,6 +264,7 @@ public function pull(Request $request): JsonResponse // 선택된 코드만 필터링 $selectedCodes = array_filter($remoteCodes, function ($code) use ($validated) { $key = $this->makeCodeKey($code); + return in_array($key, $validated['code_keys']); }); @@ -282,6 +286,7 @@ public function pull(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } @@ -299,7 +304,7 @@ public function pull(Request $request): JsonResponse return response()->json([ 'success' => true, - 'message' => "{$imported}개 코드가 동기화되었습니다." . ($skipped > 0 ? " ({$skipped}개 스킵)" : ''), + 'message' => "{$imported}개 코드가 동기화되었습니다.".($skipped > 0 ? " ({$skipped}개 스킵)" : ''), 'imported' => $imported, 'skipped' => $skipped, ]); @@ -307,7 +312,8 @@ public function pull(Request $request): JsonResponse /** * 코드 목록 조회 - * @param string $type 'global', 'tenant', or 'all' + * + * @param string $type 'global', 'tenant', or 'all' */ private function getCodeList(string $type = 'all'): array { @@ -369,12 +375,12 @@ private function fetchRemoteCodes(array $env, string $type = 'all'): array $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($env['url'], '/') . '/common-code-sync/export', [ + ])->timeout(10)->get(rtrim($env['url'], '/').'/common-code-sync/export', [ 'type' => $type, ]); if (! $response->successful()) { - throw new \Exception('API 오류: HTTP ' . $response->status()); + throw new \Exception('API 오류: HTTP '.$response->status()); } $data = $response->json(); @@ -393,6 +399,7 @@ private function fetchRemoteCodes(array $env, string $type = 'all'): array private function makeCodeKey(array $code): string { $tenantPart = $code['tenant_id'] ?? 'global'; + return "{$tenantPart}:{$code['code_group']}:{$code['code']}"; } @@ -401,8 +408,8 @@ private function makeCodeKey(array $code): string */ private function calculateDiff(array $localCodes, array $remoteCodes): array { - $localKeys = array_map(fn($c) => $this->makeCodeKey($c), $localCodes); - $remoteKeys = array_map(fn($c) => $this->makeCodeKey($c), $remoteCodes); + $localKeys = array_map(fn ($c) => $this->makeCodeKey($c), $localCodes); + $remoteKeys = array_map(fn ($c) => $this->makeCodeKey($c), $remoteCodes); return [ 'local_only' => array_values(array_diff($localKeys, $remoteKeys)), diff --git a/app/Http/Controllers/Credit/CreditController.php b/app/Http/Controllers/Credit/CreditController.php index eae3efd7..f3081acc 100644 --- a/app/Http/Controllers/Credit/CreditController.php +++ b/app/Http/Controllers/Credit/CreditController.php @@ -26,7 +26,7 @@ public function inquiry(Request $request): View|Response return response('', 200)->header('HX-Redirect', route('credit.inquiry.index')); } - $service = new CooconService(); + $service = new CooconService; $hasConfig = $service->hasConfig(); // 검색 조건 @@ -42,10 +42,10 @@ public function inquiry(Request $request): View|Response // 기간 검색 if ($request->filled('start_date')) { - $query->where('inquired_at', '>=', $request->input('start_date') . ' 00:00:00'); + $query->where('inquired_at', '>=', $request->input('start_date').' 00:00:00'); } if ($request->filled('end_date')) { - $query->where('inquired_at', '<=', $request->input('end_date') . ' 23:59:59'); + $query->where('inquired_at', '<=', $request->input('end_date').' 23:59:59'); } // 이슈 있는 것만 @@ -79,9 +79,9 @@ public function search(Request $request): JsonResponse $companyKey = preg_replace('/[^0-9]/', '', $request->input('company_key')); - $cooconService = new CooconService(); + $cooconService = new CooconService; - if (!$cooconService->hasConfig()) { + if (! $cooconService->hasConfig()) { return response()->json([ 'success' => false, 'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.', @@ -92,7 +92,7 @@ public function search(Request $request): JsonResponse $apiResult = $cooconService->getAllCreditInfo($companyKey); // 국세청 사업자등록 상태 조회 - $ntsService = new NtsBusinessService(); + $ntsService = new NtsBusinessService; $ntsResult = $ntsService->getBusinessStatus($companyKey); // DB에 저장 (tenant_id는 세션에서 가져옴) @@ -354,14 +354,14 @@ public function toggleConfig(int $id): JsonResponse { $config = CooconConfig::findOrFail($id); - if (!$config->is_active) { + if (! $config->is_active) { // 같은 환경에서 활성화된 설정이 이미 있으면 비활성화 CooconConfig::where('environment', $config->environment) ->where('is_active', true) ->update(['is_active' => false]); } - $config->update(['is_active' => !$config->is_active]); + $config->update(['is_active' => ! $config->is_active]); return response()->json([ 'success' => true, @@ -380,9 +380,9 @@ public function testConnection(Request $request): JsonResponse ]); $companyKey = $request->input('company_key'); - $service = new CooconService(); + $service = new CooconService; - if (!$service->hasConfig()) { + if (! $service->hasConfig()) { return response()->json([ 'success' => false, 'error' => '쿠콘 API 설정이 없습니다.', diff --git a/app/Http/Controllers/Credit/CreditUsageController.php b/app/Http/Controllers/Credit/CreditUsageController.php index 9c4fed19..33cf09c7 100644 --- a/app/Http/Controllers/Credit/CreditUsageController.php +++ b/app/Http/Controllers/Credit/CreditUsageController.php @@ -44,8 +44,8 @@ public function index(Request $request): View|Response $startDate = "{$year}-01-01 00:00:00"; $endDate = "{$year}-12-31 23:59:59"; } elseif ($viewType === 'custom') { - $startDate = $request->input('start_date', date('Y-m-01')) . ' 00:00:00'; - $endDate = $request->input('end_date', date('Y-m-t')) . ' 23:59:59'; + $startDate = $request->input('start_date', date('Y-m-01')).' 00:00:00'; + $endDate = $request->input('end_date', date('Y-m-t')).' 23:59:59'; } else { $startDate = "{$year}-{$month}-01 00:00:00"; $endDate = date('Y-m-t 23:59:59', strtotime($startDate)); @@ -116,7 +116,7 @@ private function getAllTenantsUsage(string $startDate, string $endDate, string $ $tenantId = $row->tenant_id; $month = $row->month; - if (!isset($monthlyData[$tenantId])) { + if (! isset($monthlyData[$tenantId])) { $monthlyData[$tenantId] = []; } $monthlyData[$tenantId][$month] = $row->total_count; @@ -173,7 +173,7 @@ private function getAllTenantsUsage(string $startDate, string $endDate, string $ } // 정렬: 조회 건수 내림차순 - usort($details, fn($a, $b) => $b['count'] - $a['count']); + usort($details, fn ($a, $b) => $b['count'] - $a['count']); return [ 'total_count' => $totalCount, @@ -228,7 +228,7 @@ private function getSingleTenantUsage(int $tenantId, string $startDate, string $ $existingMonths = collect($details)->pluck('month')->toArray(); for ($m = 1; $m <= 12; $m++) { $monthKey = sprintf('%s-%02d', $year, $m); - if (!in_array($monthKey, $existingMonths)) { + if (! in_array($monthKey, $existingMonths)) { $details[] = [ 'tenant_id' => $tenantId, 'tenant_name' => $tenant?->company_name ?? '(삭제됨)', @@ -241,7 +241,7 @@ private function getSingleTenantUsage(int $tenantId, string $startDate, string $ } } // 월 순서로 정렬 - usort($details, fn($a, $b) => strcmp($a['month'], $b['month'])); + usort($details, fn ($a, $b) => strcmp($a['month'], $b['month'])); } return [ @@ -257,6 +257,7 @@ private function getSingleTenantUsage(int $tenantId, string $startDate, string $ private function calculateFee(int $count): int { $paidCount = max(0, $count - self::FREE_MONTHLY_QUOTA); + return $paidCount * self::ADDITIONAL_FEE_PER_INQUIRY; } } diff --git a/app/Http/Controllers/DashboardCalendarController.php b/app/Http/Controllers/DashboardCalendarController.php index 69785492..2308f8c6 100644 --- a/app/Http/Controllers/DashboardCalendarController.php +++ b/app/Http/Controllers/DashboardCalendarController.php @@ -7,10 +7,9 @@ use App\Models\System\Schedule; use App\Services\GoogleCloudStorageService; use Carbon\Carbon; +use Illuminate\Contracts\View\View; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Contracts\View\View; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -174,7 +173,7 @@ public function uploadFiles(Request $request, int $scheduleId, GoogleCloudStorag foreach ($request->file('files') as $file) { $originalName = $file->getClientOriginalName(); - $storedName = Str::random(40) . '.' . $file->getClientOriginalExtension(); + $storedName = Str::random(40).'.'.$file->getClientOriginalExtension(); $storagePath = "schedules/{$tenantId}/{$schedule->id}/{$storedName}"; // 로컬(tenant 디스크) 저장 @@ -215,7 +214,7 @@ public function uploadFiles(Request $request, int $scheduleId, GoogleCloudStorag return response()->json([ 'success' => true, - 'message' => count($uploaded) . '개 파일이 업로드되었습니다.', + 'message' => count($uploaded).'개 파일이 업로드되었습니다.', 'files' => $uploaded, ]); } @@ -274,9 +273,16 @@ public function downloadFile(int $scheduleId, int $fileId) */ private function determineFileType(string $mimeType): string { - if (str_starts_with($mimeType, 'image/')) return 'image'; - if (str_contains($mimeType, 'spreadsheet') || str_contains($mimeType, 'excel')) return 'excel'; - if (str_contains($mimeType, 'zip') || str_contains($mimeType, 'rar') || str_contains($mimeType, 'archive')) return 'archive'; + if (str_starts_with($mimeType, 'image/')) { + return 'image'; + } + if (str_contains($mimeType, 'spreadsheet') || str_contains($mimeType, 'excel')) { + return 'excel'; + } + if (str_contains($mimeType, 'zip') || str_contains($mimeType, 'rar') || str_contains($mimeType, 'archive')) { + return 'archive'; + } + return 'document'; } diff --git a/app/Http/Controllers/DevTools/ApiExplorerController.php b/app/Http/Controllers/DevTools/ApiExplorerController.php index 23baf30f..8a96841f 100644 --- a/app/Http/Controllers/DevTools/ApiExplorerController.php +++ b/app/Http/Controllers/DevTools/ApiExplorerController.php @@ -461,13 +461,13 @@ public function users(): JsonResponse // 세션에서 직접 테넌트 ID 조회 (관리자가 선택한 테넌트) $selectedTenantId = session('selected_tenant_id'); - if (!$selectedTenantId) { + if (! $selectedTenantId) { // 테넌트가 선택되지 않은 경우 로그인 사용자의 기본 테넌트 사용 $currentTenant = auth()->user()->tenants() ->where('is_default', true) ->first() ?? auth()->user()->tenants()->first(); - if (!$currentTenant) { + if (! $currentTenant) { return response()->json([]); } @@ -477,7 +477,7 @@ public function users(): JsonResponse // Tenant 모델에서 직접 조회 (사용자의 테넌트 관계와 무관하게) $tenant = \App\Models\Tenants\Tenant::find($selectedTenantId); - if (!$tenant) { + if (! $tenant) { return response()->json([]); } @@ -510,7 +510,7 @@ public function issueToken(Request $request): JsonResponse $user = \App\Models\User::find($validated['user_id']); - if (!$user) { + if (! $user) { return response()->json(['error' => '사용자를 찾을 수 없습니다.'], 404); } diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 92cca884..6ab267cf 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -248,6 +248,7 @@ public function show(int $id): View ->filter(fn ($lot) => $lot->total_qty < 0) ->map(function ($lot) { $lot->total_qty = abs($lot->total_qty); + return $lot; }) ->values(); diff --git a/app/Http/Controllers/DocumentTemplateController.php b/app/Http/Controllers/DocumentTemplateController.php index a2b40eec..4c7156d0 100644 --- a/app/Http/Controllers/DocumentTemplateController.php +++ b/app/Http/Controllers/DocumentTemplateController.php @@ -332,7 +332,7 @@ private function resolveDisplayText(?string $sourceTable, int $linkableId, ?arra $title = is_object($record) ? ($record->$titleField ?? '') : ($record->$titleField ?? ''); $subtitle = $subtitleField ? (is_object($record) ? ($record->$subtitleField ?? '') : ($record->$subtitleField ?? '')) : ''; - return $title . ($subtitle ? " ({$subtitle})" : ''); + return $title.($subtitle ? " ({$subtitle})" : ''); } catch (\Exception $e) { return "ID: {$linkableId}"; } diff --git a/app/Http/Controllers/ESign/EsignApiController.php b/app/Http/Controllers/ESign/EsignApiController.php index a3478bff..2675681b 100644 --- a/app/Http/Controllers/ESign/EsignApiController.php +++ b/app/Http/Controllers/ESign/EsignApiController.php @@ -1059,7 +1059,7 @@ private function sendAlimtalk( // 등록된 버튼 URL을 그대로 사용 (동적 URL 사용 시 템플릿 불일치 오류) $buttons = ! empty($templateButtons) ? $templateButtons : [ ['Name' => '계약서 확인하기', 'ButtonType' => 'WL', - 'Url1' => 'https://mng.codebridge-x.com', 'Url2' => 'https://mng.codebridge-x.com'], + 'Url1' => 'https://mng.codebridge-x.com', 'Url2' => 'https://mng.codebridge-x.com'], ]; $receiverNum = preg_replace('/[^0-9]/', '', $signer->phone); diff --git a/app/Http/Controllers/Finance/CardTransactionController.php b/app/Http/Controllers/Finance/CardTransactionController.php index a548a056..5f5e3126 100644 --- a/app/Http/Controllers/Finance/CardTransactionController.php +++ b/app/Http/Controllers/Finance/CardTransactionController.php @@ -19,8 +19,8 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('merchant', 'like', "%{$search}%") - ->orWhere('memo', 'like', "%{$search}%") - ->orWhere('approval_no', 'like', "%{$search}%"); + ->orWhere('memo', 'like', "%{$search}%") + ->orWhere('approval_no', 'like', "%{$search}%"); }); } @@ -72,7 +72,7 @@ public function index(Request $request): JsonResponse 'total' => $transactions->count(), 'totalAmount' => $transactions->sum('amount'), 'approvedAmount' => $transactions->where('status', 'approved')->sum('amount'), - 'cancelledAmount' => $transactions->where('status', 'cancelled')->sum(fn($t) => abs($t['amount'])), + 'cancelledAmount' => $transactions->where('status', 'cancelled')->sum(fn ($t) => abs($t['amount'])), ]; // 카드 목록 diff --git a/app/Http/Controllers/Finance/ConsultingFeeController.php b/app/Http/Controllers/Finance/ConsultingFeeController.php index 82de601f..8ced65cb 100644 --- a/app/Http/Controllers/Finance/ConsultingFeeController.php +++ b/app/Http/Controllers/Finance/ConsultingFeeController.php @@ -17,14 +17,16 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('consultant', 'like', "%{$search}%") - ->orWhere('customer', 'like', "%{$search}%"); + ->orWhere('customer', 'like', "%{$search}%"); }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } - $fees = $query->orderBy('date', 'desc')->get()->map(fn($item) => [ + $fees = $query->orderBy('date', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'date' => $item->date?->format('Y-m-d'), 'consultant' => $item->consultant, 'customer' => $item->customer, 'service' => $item->service, 'hours' => $item->hours, @@ -82,6 +84,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); ConsultingFee::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '컨설팅비가 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/CorporateCardController.php b/app/Http/Controllers/Finance/CorporateCardController.php index c13e22b1..a7402d62 100644 --- a/app/Http/Controllers/Finance/CorporateCardController.php +++ b/app/Http/Controllers/Finance/CorporateCardController.php @@ -227,7 +227,7 @@ public function summary(): JsonResponse $adjustedDate = $this->getAdjustedPaymentDate($tenantId, $nextMonth->year, $nextMonth->month, $paymentDay); } - $isAdjusted = !$originalDate->isSameDay($adjustedDate); + $isAdjusted = ! $originalDate->isSameDay($adjustedDate); // 청구기간: 결제일 기준 전월 1일 ~ 결제일 $billingStart = $adjustedDate->copy()->subMonth()->startOfMonth(); @@ -283,7 +283,7 @@ public function updatePrepayment(Request $request): JsonResponse $tenantId = session('selected_tenant_id', 1); $yearMonth = $this->getBillingYearMonth($tenantId); - $items = collect($request->input('items', []))->filter(fn($item) => ($item['amount'] ?? 0) > 0)->values()->toArray(); + $items = collect($request->input('items', []))->filter(fn ($item) => ($item['amount'] ?? 0) > 0)->values()->toArray(); $amount = collect($items)->sum('amount'); $prepayment = CorporateCardPrepayment::updateOrCreate( @@ -328,6 +328,7 @@ private function getBillingYearMonth(int $tenantId): string private function createPaymentDate(int $year, int $month, int $day): Carbon { $maxDay = Carbon::create($year, $month)->daysInMonth; + return Carbon::create($year, $month, min($day, $maxDay)); } @@ -367,7 +368,7 @@ private function getAdjustedPaymentDate(int $tenantId, int $year, int $month, in private function calculateBillingUsage(int $tenantId, string $startDate, string $endDate, array $cardNumbers): array { // 카드번호 정규화 (하이픈 제거) + 원본↔정규화 매핑 - $normalizedNums = array_map(fn($num) => str_replace('-', '', $num), $cardNumbers); + $normalizedNums = array_map(fn ($num) => str_replace('-', '', $num), $cardNumbers); if (empty($normalizedNums)) { return ['total' => 0, 'perCard' => []]; diff --git a/app/Http/Controllers/Finance/CorporateVehicleController.php b/app/Http/Controllers/Finance/CorporateVehicleController.php index 7a457ba7..60aa7ae5 100644 --- a/app/Http/Controllers/Finance/CorporateVehicleController.php +++ b/app/Http/Controllers/Finance/CorporateVehicleController.php @@ -45,8 +45,8 @@ public function list(Request $request): JsonResponse $search = $request->search; $query->where(function ($q) use ($search) { $q->where('plate_number', 'like', "%{$search}%") - ->orWhere('model', 'like', "%{$search}%") - ->orWhere('driver', 'like', "%{$search}%"); + ->orWhere('model', 'like', "%{$search}%") + ->orWhere('driver', 'like', "%{$search}%"); }); } diff --git a/app/Http/Controllers/Finance/CustomerController.php b/app/Http/Controllers/Finance/CustomerController.php index 02ce0dbc..d716ef40 100644 --- a/app/Http/Controllers/Finance/CustomerController.php +++ b/app/Http/Controllers/Finance/CustomerController.php @@ -19,18 +19,22 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") - ->orWhere('ceo', 'like', "%{$search}%") - ->orWhere('manager', 'like', "%{$search}%"); + ->orWhere('ceo', 'like', "%{$search}%") + ->orWhere('manager', 'like', "%{$search}%"); }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } if ($grade = $request->input('grade')) { - if ($grade !== 'all') $query->where('grade', $grade); + if ($grade !== 'all') { + $query->where('grade', $grade); + } } - $customers = $query->orderBy('created_at', 'desc')->get()->map(fn($item) => [ + $customers = $query->orderBy('created_at', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'name' => $item->name, 'bizNo' => $item->biz_no, 'ceo' => $item->ceo, 'industry' => $item->industry, 'grade' => $item->grade, 'contact' => $item->contact, 'email' => $item->email, 'address' => $item->address, @@ -90,6 +94,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); Customer::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '고객사가 삭제되었습니다.']); } @@ -130,13 +135,14 @@ function ($attribute, $value, $fail) { return response()->json(['ok' => false, 'message' => $e->getMessage()], 500); } catch (\Throwable $e) { Log::error('고객사 OCR 예상치 못한 오류', ['error' => $e->getMessage()]); + return response()->json(['ok' => false, 'message' => 'OCR 처리 중 오류가 발생했습니다.'], 500); } } private function matchIndustry(string $bizType, string $bizItem): string { - $text = $bizType . ' ' . $bizItem; + $text = $bizType.' '.$bizItem; $keywords = [ 'IT/소프트웨어' => ['소프트웨어', 'IT', '정보통신', '전산', '컴퓨터', '인터넷', '데이터', '프로그램'], @@ -162,8 +168,13 @@ private function buildCustomerMemo(array $raw): string $parts = []; $bizType = trim($raw['biz_type'] ?? ''); $bizItem = trim($raw['biz_item'] ?? ''); - if ($bizType) $parts[] = "[업태] {$bizType}"; - if ($bizItem) $parts[] = "[종목] {$bizItem}"; + if ($bizType) { + $parts[] = "[업태] {$bizType}"; + } + if ($bizItem) { + $parts[] = "[종목] {$bizItem}"; + } + return implode(' / ', $parts); } } diff --git a/app/Http/Controllers/Finance/CustomerSettlementController.php b/app/Http/Controllers/Finance/CustomerSettlementController.php index eca0e8dc..aa889ba1 100644 --- a/app/Http/Controllers/Finance/CustomerSettlementController.php +++ b/app/Http/Controllers/Finance/CustomerSettlementController.php @@ -20,10 +20,12 @@ public function index(Request $request): JsonResponse }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } - $settlements = $query->orderBy('created_at', 'desc')->get()->map(fn($item) => [ + $settlements = $query->orderBy('created_at', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'period' => $item->period, 'customer' => $item->customer, 'totalSales' => $item->total_sales, 'commission' => $item->commission, 'expense' => $item->expense, @@ -85,6 +87,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); CustomerSettlement::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '정산이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/DailyFundController.php b/app/Http/Controllers/Finance/DailyFundController.php index 53ce76eb..d76e02c0 100644 --- a/app/Http/Controllers/Finance/DailyFundController.php +++ b/app/Http/Controllers/Finance/DailyFundController.php @@ -3,13 +3,12 @@ namespace App\Http\Controllers\Finance; use App\Http\Controllers\Controller; -use App\Models\Finance\DailyFundTransaction; -use App\Models\Finance\DailyFundMemo; use App\Models\Barobill\BankTransaction as BarobillBankTransaction; use App\Models\Barobill\BankTransactionOverride; +use App\Models\Finance\DailyFundMemo; +use App\Models\Finance\DailyFundTransaction; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; class DailyFundController extends Controller { @@ -179,14 +178,14 @@ public function periodReport(Request $request): JsonResponse ->whereBetween('trans_date', [$startDateYmd, $endDateYmd]) ->orderBy('id', 'desc') // 최신 ID 우선 (올바른 잔액) ->get() - ->unique(fn($tx) => $tx->unique_key) + ->unique(fn ($tx) => $tx->unique_key) ->sortByDesc('trans_date') - ->sortByDesc(fn($tx) => $tx->trans_date . $tx->trans_time) + ->sortByDesc(fn ($tx) => $tx->trans_date.$tx->trans_time) ->values(); // 오버라이드 데이터 병합 (수정된 적요/내용) if ($transactions->isNotEmpty()) { - $uniqueKeys = $transactions->map(fn($t) => $t->unique_key)->toArray(); + $uniqueKeys = $transactions->map(fn ($t) => $t->unique_key)->toArray(); $overrides = BankTransactionOverride::getByUniqueKeys($tenantId, $uniqueKeys); $transactions = $transactions->map(function ($tx) use ($overrides) { @@ -199,6 +198,7 @@ public function periodReport(Request $request): JsonResponse $tx->cast = $override->modified_cast; } } + return $tx; }); } @@ -211,7 +211,7 @@ public function periodReport(Request $request): JsonResponse $date = $tx->trans_date; $accountNum = $tx->bank_account_num; - if (!isset($dailyData[$date])) { + if (! isset($dailyData[$date])) { $dailyData[$date] = [ 'date' => $date, 'dateFormatted' => $this->formatDateKorean($date), @@ -224,7 +224,7 @@ public function periodReport(Request $request): JsonResponse } // 계좌별 데이터 집계 - if (!isset($dailyData[$date]['accounts'][$accountNum])) { + if (! isset($dailyData[$date]['accounts'][$accountNum])) { $dailyData[$date]['accounts'][$accountNum] = [ 'bankName' => $tx->bank_name, 'accountNum' => $accountNum, diff --git a/app/Http/Controllers/Finance/ExpenseController.php b/app/Http/Controllers/Finance/ExpenseController.php index 1977ce8b..6d4230f4 100644 --- a/app/Http/Controllers/Finance/ExpenseController.php +++ b/app/Http/Controllers/Finance/ExpenseController.php @@ -17,17 +17,21 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('vendor', 'like', "%{$search}%") - ->orWhere('description', 'like', "%{$search}%"); + ->orWhere('description', 'like', "%{$search}%"); }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } if ($category = $request->input('category')) { - if ($category !== 'all') $query->where('category', $category); + if ($category !== 'all') { + $query->where('category', $category); + } } - $expenses = $query->orderBy('date', 'desc')->get()->map(fn($item) => [ + $expenses = $query->orderBy('date', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'date' => $item->date?->format('Y-m-d'), 'vendor' => $item->vendor, 'description' => $item->description, 'category' => $item->category, 'amount' => $item->amount, @@ -87,6 +91,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); Expense::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '지출이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/FinanceDashboardController.php b/app/Http/Controllers/Finance/FinanceDashboardController.php index d2649848..f4588351 100644 --- a/app/Http/Controllers/Finance/FinanceDashboardController.php +++ b/app/Http/Controllers/Finance/FinanceDashboardController.php @@ -7,7 +7,6 @@ use App\Models\Barobill\BankTransactionOverride; use App\Models\Barobill\CardTransaction as BarobillCardTransaction; use App\Models\Finance\BankAccount; -use App\Models\Finance\FundSchedule; use App\Services\BankAccountService; use App\Services\FundScheduleService; use Illuminate\Contracts\View\View; @@ -53,7 +52,7 @@ public function index(): View // 오버라이드 데이터 병합 (수정된 적요/내용) if ($recentTransactions->isNotEmpty()) { - $uniqueKeys = $recentTransactions->map(fn($t) => $t->unique_key)->toArray(); + $uniqueKeys = $recentTransactions->map(fn ($t) => $t->unique_key)->toArray(); $overrides = BankTransactionOverride::getByUniqueKeys($tenantId, $uniqueKeys); $recentTransactions = $recentTransactions->map(function ($transaction) use ($overrides) { @@ -71,6 +70,7 @@ public function index(): View } else { $transaction->is_overridden = false; } + return $transaction; }); } diff --git a/app/Http/Controllers/Finance/IncomeController.php b/app/Http/Controllers/Finance/IncomeController.php index 551f7f06..b5a67dc1 100644 --- a/app/Http/Controllers/Finance/IncomeController.php +++ b/app/Http/Controllers/Finance/IncomeController.php @@ -17,13 +17,21 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('customer', 'like', "%{$search}%") - ->orWhere('description', 'like', "%{$search}%"); + ->orWhere('description', 'like', "%{$search}%"); }); } - if ($status = $request->input('status')) { if ($status !== 'all') $query->where('status', $status); } - if ($category = $request->input('category')) { if ($category !== 'all') $query->where('category', $category); } + if ($status = $request->input('status')) { + if ($status !== 'all') { + $query->where('status', $status); + } + } + if ($category = $request->input('category')) { + if ($category !== 'all') { + $query->where('category', $category); + } + } - $incomes = $query->orderBy('date', 'desc')->get()->map(fn($item) => [ + $incomes = $query->orderBy('date', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'date' => $item->date?->format('Y-m-d'), 'customer' => $item->customer, 'description' => $item->description, 'category' => $item->category, 'amount' => $item->amount, @@ -80,6 +88,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); Income::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '수입이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/PurchaseController.php b/app/Http/Controllers/Finance/PurchaseController.php index 308a9162..465d58aa 100644 --- a/app/Http/Controllers/Finance/PurchaseController.php +++ b/app/Http/Controllers/Finance/PurchaseController.php @@ -17,17 +17,21 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('vendor', 'like', "%{$search}%") - ->orWhere('item', 'like', "%{$search}%"); + ->orWhere('item', 'like', "%{$search}%"); }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } if ($category = $request->input('category')) { - if ($category !== 'all') $query->where('category', $category); + if ($category !== 'all') { + $query->where('category', $category); + } } - $purchases = $query->orderBy('date', 'desc')->get()->map(fn($item) => [ + $purchases = $query->orderBy('date', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'date' => $item->date?->format('Y-m-d'), 'vendor' => $item->vendor, 'item' => $item->item, 'category' => $item->category, 'amount' => $item->amount, @@ -85,6 +89,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); Purchase::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '매입이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/RefundController.php b/app/Http/Controllers/Finance/RefundController.php index 83938748..85600fed 100644 --- a/app/Http/Controllers/Finance/RefundController.php +++ b/app/Http/Controllers/Finance/RefundController.php @@ -18,7 +18,7 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('customer_name', 'like', "%{$search}%") - ->orWhere('product_name', 'like', "%{$search}%"); + ->orWhere('product_name', 'like', "%{$search}%"); }); } diff --git a/app/Http/Controllers/Finance/SalesCommissionController.php b/app/Http/Controllers/Finance/SalesCommissionController.php index 3f915077..aeb9a532 100644 --- a/app/Http/Controllers/Finance/SalesCommissionController.php +++ b/app/Http/Controllers/Finance/SalesCommissionController.php @@ -27,7 +27,7 @@ public function __construct( public function index(Request $request): View|Response { // HTMX 요청 시 전체 페이지로 리다이렉트 (JavaScript 필요) - if ($request->header('HX-Request') && !$request->header('HX-Boosted')) { + if ($request->header('HX-Request') && ! $request->header('HX-Boosted')) { return response('', 200)->header('HX-Redirect', route('finance.sales-commissions.index')); } @@ -77,7 +77,7 @@ public function show(int $id): JsonResponse { $commission = $this->service->getCommissionById($id); - if (!$commission) { + if (! $commission) { return response()->json([ 'success' => false, 'message' => '정산 정보를 찾을 수 없습니다.', @@ -377,9 +377,9 @@ public function export(Request $request) // 전체 데이터 조회 (페이지네이션 없이) $commissions = SalesCommission::query() ->with(['tenant', 'partner.user', 'manager']) - ->when(!empty($filters['status']), fn($q) => $q->where('status', $filters['status'])) - ->when(!empty($filters['payment_type']), fn($q) => $q->where('payment_type', $filters['payment_type'])) - ->when(!empty($filters['partner_id']), fn($q) => $q->where('partner_id', $filters['partner_id'])) + ->when(! empty($filters['status']), fn ($q) => $q->where('status', $filters['status'])) + ->when(! empty($filters['payment_type']), fn ($q) => $q->where('payment_type', $filters['payment_type'])) + ->when(! empty($filters['partner_id']), fn ($q) => $q->where('partner_id', $filters['partner_id'])) ->forScheduledMonth($year, $month) ->orderBy('scheduled_payment_date') ->get(); @@ -402,7 +402,7 @@ public function export(Request $request) fputcsv($file, [ '번호', '테넌트', '입금구분', '입금액', '입금일', '기준액', '영업파트너', '파트너수당', '매니저', '매니저수당', - '지급예정일', '상태', '실제지급일' + '지급예정일', '상태', '실제지급일', ]); // 데이터 diff --git a/app/Http/Controllers/Finance/SalesRecordController.php b/app/Http/Controllers/Finance/SalesRecordController.php index 4f00b582..2b293841 100644 --- a/app/Http/Controllers/Finance/SalesRecordController.php +++ b/app/Http/Controllers/Finance/SalesRecordController.php @@ -17,17 +17,21 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('customer', 'like', "%{$search}%") - ->orWhere('project', 'like', "%{$search}%"); + ->orWhere('project', 'like', "%{$search}%"); }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } if ($type = $request->input('type')) { - if ($type !== 'all') $query->where('type', $type); + if ($type !== 'all') { + $query->where('type', $type); + } } - $records = $query->orderBy('date', 'desc')->get()->map(fn($item) => [ + $records = $query->orderBy('date', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'date' => $item->date?->format('Y-m-d'), 'customer' => $item->customer, 'project' => $item->project, 'type' => $item->type, 'taxType' => $item->tax_type ?? 'taxable', @@ -93,6 +97,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); SalesRecord::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '매출이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/SubscriptionController.php b/app/Http/Controllers/Finance/SubscriptionController.php index cbb5ed21..862af3b2 100644 --- a/app/Http/Controllers/Finance/SubscriptionController.php +++ b/app/Http/Controllers/Finance/SubscriptionController.php @@ -20,10 +20,12 @@ public function index(Request $request): JsonResponse }); } if ($status = $request->input('status')) { - if ($status !== 'all') $query->where('status', $status); + if ($status !== 'all') { + $query->where('status', $status); + } } - $subscriptions = $query->orderBy('created_at', 'desc')->get()->map(fn($item) => [ + $subscriptions = $query->orderBy('created_at', 'desc')->get()->map(fn ($item) => [ 'id' => $item->id, 'customer' => $item->customer, 'plan' => $item->plan, 'monthlyFee' => $item->monthly_fee, 'billingCycle' => $item->billing_cycle, @@ -87,6 +89,7 @@ public function destroy(int $id): JsonResponse { $tenantId = session('selected_tenant_id', 1); Subscription::forTenant($tenantId)->findOrFail($id)->delete(); + return response()->json(['success' => true, 'message' => '구독이 삭제되었습니다.']); } } diff --git a/app/Http/Controllers/Finance/TradingPartnerController.php b/app/Http/Controllers/Finance/TradingPartnerController.php index bc62ce27..69313e6d 100644 --- a/app/Http/Controllers/Finance/TradingPartnerController.php +++ b/app/Http/Controllers/Finance/TradingPartnerController.php @@ -19,8 +19,8 @@ public function index(Request $request): JsonResponse if ($search = $request->input('search')) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") - ->orWhere('ceo', 'like', "%{$search}%") - ->orWhere('manager', 'like', "%{$search}%"); + ->orWhere('ceo', 'like', "%{$search}%") + ->orWhere('manager', 'like', "%{$search}%"); }); } diff --git a/app/Http/Controllers/Finance/VatRecordController.php b/app/Http/Controllers/Finance/VatRecordController.php index 8a3d32bd..321c3ae5 100644 --- a/app/Http/Controllers/Finance/VatRecordController.php +++ b/app/Http/Controllers/Finance/VatRecordController.php @@ -3,11 +3,11 @@ namespace App\Http\Controllers\Finance; use App\Http\Controllers\Controller; -use App\Models\Finance\VatRecord; use App\Models\Barobill\CardTransaction as BarobillCardTransaction; -use App\Models\Barobill\CardTransactionSplit; use App\Models\Barobill\CardTransactionHide; +use App\Models\Barobill\CardTransactionSplit; use App\Models\Barobill\HometaxInvoice; +use App\Models\Finance\VatRecord; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -43,7 +43,7 @@ public function index(Request $request): JsonResponse $hometaxSalesRecords = $hometaxSales->map(function ($inv) use ($period, $taxTypeMap) { return [ - 'id' => 'hometax_' . $inv->id, + 'id' => 'hometax_'.$inv->id, 'period' => $period, 'type' => 'sales', 'taxType' => $taxTypeMap[$inv->tax_type] ?? 'taxable', @@ -69,7 +69,7 @@ public function index(Request $request): JsonResponse $hometaxPurchaseRecords = $hometaxPurchases->map(function ($inv) use ($period, $taxTypeMap) { return [ - 'id' => 'hometax_' . $inv->id, + 'id' => 'hometax_'.$inv->id, 'period' => $period, 'type' => 'purchase', 'taxType' => $taxTypeMap[$inv->tax_type] ?? 'taxable', @@ -104,7 +104,7 @@ public function index(Request $request): JsonResponse foreach ($splitsByKey as $fullKey => $splits) { $parts = explode('|', $fullKey); if (count($parts) >= 3) { - $partialKey = $parts[0] . '|' . $parts[1] . '|' . $parts[2]; + $partialKey = $parts[0].'|'.$parts[1].'|'.$parts[2]; $splitsByPartialKey[$partialKey] = $splits; } } @@ -117,8 +117,8 @@ public function index(Request $request): JsonResponse // 분개 매칭: 정확한 키 → 부분키(금액 제외) 순으로 시도 $splits = $splitsByKey[$card->unique_key] ?? null; - if (!$splits) { - $cardPartialKey = $card->card_num . '|' . $card->use_dt . '|' . $card->approval_num; + if (! $splits) { + $cardPartialKey = $card->card_num.'|'.$card->use_dt.'|'.$card->approval_num; $splits = $splitsByPartialKey[$cardPartialKey] ?? null; } @@ -127,7 +127,7 @@ public function index(Request $request): JsonResponse foreach ($splits as $split) { if ($split->deduction_type === 'deductible') { $cardRecords->push([ - 'id' => 'card_split_' . $split->id, + 'id' => 'card_split_'.$split->id, 'period' => $period, 'type' => 'purchase', 'taxType' => 'taxable', @@ -153,7 +153,7 @@ public function index(Request $request): JsonResponse $effectiveTax = $card->modified_tax ?? $card->tax; $cardRecords->push([ - 'id' => 'card_' . $card->id, + 'id' => 'card_'.$card->id, 'period' => $period, 'type' => 'purchase', 'taxType' => 'taxable', @@ -269,13 +269,13 @@ public function index(Request $request): JsonResponse // 확정(C) 기간이면 대응하는 예정(P)의 netVat를 계산 if ($period && str_ends_with($period, 'C')) { try { - $prelimPeriod = substr($period, 0, -1) . 'P'; + $prelimPeriod = substr($period, 0, -1).'P'; $stats['preliminaryVat'] = $this->calculatePeriodNetVat($tenantId, $prelimPeriod); } catch (\Throwable $e) { \Log::error('예정 세액 계산 실패', [ 'message' => $e->getMessage(), - 'file' => $e->getFile() . ':' . $e->getLine(), - 'trace' => array_slice(array_map(fn($t) => ($t['file'] ?? '') . ':' . ($t['line'] ?? '') . ' ' . ($t['function'] ?? ''), $e->getTrace()), 0, 5), + 'file' => $e->getFile().':'.$e->getLine(), + 'trace' => array_slice(array_map(fn ($t) => ($t['file'] ?? '').':'.($t['line'] ?? '').' '.($t['function'] ?? ''), $e->getTrace()), 0, 5), 'tenant_id' => $tenantId, 'period' => $prelimPeriod ?? null, ]); @@ -385,7 +385,7 @@ public function destroy(int $id): JsonResponse private function calculatePeriodNetVat(int $tenantId, string $period): int { [$startDate, $endDate] = $this->periodToDateRange($period); - if (!$startDate || !$endDate) { + if (! $startDate || ! $endDate) { return 0; } @@ -422,7 +422,7 @@ private function calculatePeriodNetVat(int $tenantId, string $period): int foreach ($splitsByKey as $fullKey => $splits) { $parts = explode('|', $fullKey); if (count($parts) >= 3) { - $partialKey = $parts[0] . '|' . $parts[1] . '|' . $parts[2]; + $partialKey = $parts[0].'|'.$parts[1].'|'.$parts[2]; $splitsByPartialKey[$partialKey] = $splits; } } @@ -433,8 +433,8 @@ private function calculatePeriodNetVat(int $tenantId, string $period): int } $splits = $splitsByKey[$card->unique_key] ?? null; - if (!$splits) { - $cardPartialKey = $card->card_num . '|' . $card->use_dt . '|' . $card->approval_num; + if (! $splits) { + $cardPartialKey = $card->card_num.'|'.$card->use_dt.'|'.$card->approval_num; $splits = $splitsByPartialKey[$cardPartialKey] ?? null; } diff --git a/app/Http/Controllers/Finance/VehicleLogController.php b/app/Http/Controllers/Finance/VehicleLogController.php index 9c568b5e..dea385f9 100644 --- a/app/Http/Controllers/Finance/VehicleLogController.php +++ b/app/Http/Controllers/Finance/VehicleLogController.php @@ -71,10 +71,10 @@ public function list(Request $request): JsonResponse $search = $request->search; $query->where(function ($q) use ($search) { $q->where('driver_name', 'like', "%{$search}%") - ->orWhere('department', 'like', "%{$search}%") - ->orWhere('departure_name', 'like', "%{$search}%") - ->orWhere('arrival_name', 'like', "%{$search}%") - ->orWhere('note', 'like', "%{$search}%"); + ->orWhere('department', 'like', "%{$search}%") + ->orWhere('departure_name', 'like', "%{$search}%") + ->orWhere('arrival_name', 'like', "%{$search}%") + ->orWhere('note', 'like', "%{$search}%"); }); } @@ -233,9 +233,9 @@ public function summary(Request $request): JsonResponse COUNT(*) as count, SUM(distance_km) as total_distance ') - ->groupBy('trip_type') - ->get() - ->keyBy('trip_type'); + ->groupBy('trip_type') + ->get() + ->keyBy('trip_type'); $tripTypes = VehicleLog::getTripTypes(); $result = []; diff --git a/app/Http/Controllers/Finance/VehicleMaintenanceController.php b/app/Http/Controllers/Finance/VehicleMaintenanceController.php index 12eb8802..c4c6ad38 100644 --- a/app/Http/Controllers/Finance/VehicleMaintenanceController.php +++ b/app/Http/Controllers/Finance/VehicleMaintenanceController.php @@ -71,8 +71,8 @@ public function list(Request $request): JsonResponse $search = $request->search; $query->where(function ($q) use ($search) { $q->where('description', 'like', "%{$search}%") - ->orWhere('vendor', 'like', "%{$search}%") - ->orWhere('memo', 'like', "%{$search}%"); + ->orWhere('vendor', 'like', "%{$search}%") + ->orWhere('memo', 'like', "%{$search}%"); }); } diff --git a/app/Http/Controllers/Juil/ConstructionSitePhotoController.php b/app/Http/Controllers/Juil/ConstructionSitePhotoController.php index c6cb5f25..8063a176 100644 --- a/app/Http/Controllers/Juil/ConstructionSitePhotoController.php +++ b/app/Http/Controllers/Juil/ConstructionSitePhotoController.php @@ -43,7 +43,7 @@ public function show(int $id): JsonResponse { $photo = ConstructionSitePhoto::with(['user', 'rows'])->find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -77,7 +77,7 @@ public function uploadPhoto(Request $request, int $id, int $rowId): JsonResponse { $photo = ConstructionSitePhoto::find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -88,7 +88,7 @@ public function uploadPhoto(Request $request, int $id, int $rowId): JsonResponse ->where('construction_site_photo_id', $id) ->first(); - if (!$row) { + if (! $row) { return response()->json([ 'success' => false, 'message' => '사진 행을 찾을 수 없습니다.', @@ -102,7 +102,7 @@ public function uploadPhoto(Request $request, int $id, int $rowId): JsonResponse $result = $this->service->uploadPhoto($row, $request->file('photo'), $validated['type']); - if (!$result) { + if (! $result) { return response()->json([ 'success' => false, 'message' => '사진 업로드에 실패했습니다.', @@ -120,7 +120,7 @@ public function update(Request $request, int $id): JsonResponse { $photo = ConstructionSitePhoto::find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -146,7 +146,7 @@ public function destroy(int $id): JsonResponse { $photo = ConstructionSitePhoto::with('rows')->find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -165,7 +165,7 @@ public function deletePhoto(int $id, int $rowId, string $type): JsonResponse { $photo = ConstructionSitePhoto::find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -176,14 +176,14 @@ public function deletePhoto(int $id, int $rowId, string $type): JsonResponse ->where('construction_site_photo_id', $id) ->first(); - if (!$row) { + if (! $row) { return response()->json([ 'success' => false, 'message' => '사진 행을 찾을 수 없습니다.', ], 404); } - if (!in_array($type, ['before', 'during', 'after'])) { + if (! in_array($type, ['before', 'during', 'after'])) { return response()->json([ 'success' => false, 'message' => '올바르지 않은 사진 유형입니다.', @@ -203,7 +203,7 @@ public function downloadPhoto(Request $request, int $id, int $rowId, string $typ { $photo = ConstructionSitePhoto::find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -214,23 +214,23 @@ public function downloadPhoto(Request $request, int $id, int $rowId, string $typ ->where('construction_site_photo_id', $id) ->first(); - if (!$row) { + if (! $row) { return response()->json([ 'success' => false, 'message' => '사진 행을 찾을 수 없습니다.', ], 404); } - if (!in_array($type, ['before', 'during', 'after'])) { + if (! in_array($type, ['before', 'during', 'after'])) { return response()->json([ 'success' => false, 'message' => '올바르지 않은 사진 유형입니다.', ], 422); } - $path = $row->{$type . '_photo_path'}; + $path = $row->{$type.'_photo_path'}; - if (!$path) { + if (! $path) { return response()->json([ 'success' => false, 'message' => '파일을 찾을 수 없습니다.', @@ -240,7 +240,7 @@ public function downloadPhoto(Request $request, int $id, int $rowId, string $typ $googleCloudService = app(GoogleCloudService::class); $content = $googleCloudService->downloadFromStorage($path); - if (!$content) { + if (! $content) { return response()->json([ 'success' => false, 'message' => '파일 다운로드에 실패했습니다.', @@ -248,7 +248,7 @@ public function downloadPhoto(Request $request, int $id, int $rowId, string $typ } $extension = pathinfo($path, PATHINFO_EXTENSION) ?: 'jpg'; - $mimeType = 'image/' . ($extension === 'jpg' ? 'jpeg' : $extension); + $mimeType = 'image/'.($extension === 'jpg' ? 'jpeg' : $extension); $typeLabel = match ($type) { 'before' => '작업전', @@ -273,7 +273,7 @@ public function addRow(int $id): JsonResponse { $photo = ConstructionSitePhoto::find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -293,7 +293,7 @@ public function deleteRow(int $id, int $rowId): JsonResponse { $photo = ConstructionSitePhoto::with('rows')->find($id); - if (!$photo) { + if (! $photo) { return response()->json([ 'success' => false, 'message' => '사진대지를 찾을 수 없습니다.', @@ -309,7 +309,7 @@ public function deleteRow(int $id, int $rowId): JsonResponse $row = $photo->rows->firstWhere('id', $rowId); - if (!$row) { + if (! $row) { return response()->json([ 'success' => false, 'message' => '사진 행을 찾을 수 없습니다.', diff --git a/app/Http/Controllers/Juil/MeetingMinuteController.php b/app/Http/Controllers/Juil/MeetingMinuteController.php index 309b9313..7f2a693c 100644 --- a/app/Http/Controllers/Juil/MeetingMinuteController.php +++ b/app/Http/Controllers/Juil/MeetingMinuteController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers\Juil; -use App\Helpers\AiTokenHelper; use App\Http\Controllers\Controller; use App\Models\Juil\MeetingMinute; use App\Services\GoogleCloudService; @@ -172,7 +171,7 @@ public function saveSegments(Request $request, int $id): JsonResponse return response()->json([ 'success' => false, - 'message' => '세그먼트 저장 중 오류가 발생했습니다: ' . $e->getMessage(), + 'message' => '세그먼트 저장 중 오류가 발생했습니다: '.$e->getMessage(), ], 500); } } @@ -310,7 +309,7 @@ public function downloadAudio(Request $request, int $id): Response|JsonResponse } $extension = pathinfo($meeting->audio_file_path, PATHINFO_EXTENSION) ?: 'webm'; - $mimeType = 'audio/' . $extension; + $mimeType = 'audio/'.$extension; $safeTitle = preg_replace('/[\/\\\\:*?"<>|]/', '_', $meeting->title); $filename = "{$safeTitle}.{$extension}"; diff --git a/app/Http/Controllers/Lab/StrategyController.php b/app/Http/Controllers/Lab/StrategyController.php index 94785cc5..95e9a4b8 100644 --- a/app/Http/Controllers/Lab/StrategyController.php +++ b/app/Http/Controllers/Lab/StrategyController.php @@ -22,6 +22,7 @@ private function handlePresentationPage(Request $request, string $routeName): ?R if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route($routeName)); } + return null; } @@ -33,6 +34,7 @@ public function labor(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.labor')); } + return view('lab.strategy.labor'); } @@ -44,6 +46,7 @@ public function chatbot(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.chatbot')); } + return view('lab.strategy.chatbot'); } @@ -55,6 +58,7 @@ public function knowledgeSearch(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.knowledge-search')); } + return view('lab.strategy.knowledge-search'); } @@ -66,6 +70,7 @@ public function chatbotCompare(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.chatbot-compare')); } + return view('lab.strategy.chatbot-compare'); } @@ -77,6 +82,7 @@ public function ragStartups(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.rag-startups')); } + return view('lab.strategy.rag-startups'); } @@ -88,6 +94,7 @@ public function douzone(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.douzone')); } + return view('lab.strategy.douzone'); } @@ -99,6 +106,7 @@ public function confluenceVsNotion(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.confluence-vs-notion')); } + return view('lab.strategy.confluence-vs-notion'); } @@ -110,6 +118,7 @@ public function salesStrategy(Request $request): View|Response if ($request->header('HX-Request')) { return response('', 200)->header('HX-Redirect', route('lab.strategy.sales-strategy')); } + return view('lab.strategy.sales-strategy'); } } diff --git a/app/Http/Controllers/MenuSyncController.php b/app/Http/Controllers/MenuSyncController.php index 389fa1ea..3745e366 100644 --- a/app/Http/Controllers/MenuSyncController.php +++ b/app/Http/Controllers/MenuSyncController.php @@ -21,6 +21,7 @@ class MenuSyncController extends Controller protected function getTenantId(): int { $tenantId = session('selected_tenant_id'); + return ($tenantId && $tenantId !== 'all') ? (int) $tenantId : 1; } @@ -229,7 +230,7 @@ public function push(Request $request): JsonResponse $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->post(rtrim($env['url'], '/') . '/menu-sync/import', [ + ])->post(rtrim($env['url'], '/').'/menu-sync/import', [ 'menus' => $menuData, ]); @@ -244,7 +245,7 @@ public function push(Request $request): JsonResponse 'error' => $response->json('error', '원격 서버 오류'), ], $response->status()); } catch (\Exception $e) { - return response()->json(['error' => '연결 실패: ' . $e->getMessage()], 500); + return response()->json(['error' => '연결 실패: '.$e->getMessage()], 500); } } @@ -307,10 +308,11 @@ public function testConnection(Request $request): JsonResponse $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $validated['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($validated['url'], '/') . '/menu-sync/export'); + ])->timeout(10)->get(rtrim($validated['url'], '/').'/menu-sync/export'); if ($response->successful()) { $data = $response->json(); + return response()->json([ 'success' => true, 'message' => '연결 성공', @@ -321,12 +323,12 @@ public function testConnection(Request $request): JsonResponse return response()->json([ 'success' => false, - 'message' => 'API 오류: ' . $response->status(), + 'message' => 'API 오류: '.$response->status(), ]); } catch (\Exception $e) { return response()->json([ 'success' => false, - 'message' => '연결 실패: ' . $e->getMessage(), + 'message' => '연결 실패: '.$e->getMessage(), ]); } } @@ -394,12 +396,12 @@ private function fetchRemoteMenus(array $env): array $response = Http::withHeaders([ 'X-Menu-Sync-Key' => $env['api_key'], 'Accept' => 'application/json', - ])->timeout(10)->get(rtrim($env['url'], '/') . '/menu-sync/export', [ + ])->timeout(10)->get(rtrim($env['url'], '/').'/menu-sync/export', [ 'tenant_id' => $this->getTenantId(), // 현재 선택된 테넌트 전달 ]); if (! $response->successful()) { - throw new \Exception('API 오류: HTTP ' . $response->status()); + throw new \Exception('API 오류: HTTP '.$response->status()); } $data = $response->json(); @@ -440,6 +442,7 @@ private function flattenMenuNames(array $menus, string $prefix = ''): array $names = array_merge($names, $this->flattenMenuNames($menu['children'], $fullName)); } } + return $names; } diff --git a/app/Http/Controllers/Sales/AdminProspectController.php b/app/Http/Controllers/Sales/AdminProspectController.php index c13bb327..a0671c80 100644 --- a/app/Http/Controllers/Sales/AdminProspectController.php +++ b/app/Http/Controllers/Sales/AdminProspectController.php @@ -25,7 +25,7 @@ class AdminProspectController extends Controller */ private function checkAdminAccess(): void { - if (!auth()->user()->isAdmin() && !auth()->user()->isSuperAdmin()) { + if (! auth()->user()->isAdmin() && ! auth()->user()->isSuperAdmin()) { abort(403, '관리자만 접근할 수 있습니다.'); } } @@ -45,7 +45,7 @@ private function getPaymentTypeForField(string $field): string */ private function loadMergedCommission(?SalesTenantManagement $management): ?object { - if (!$management) { + if (! $management) { return null; } @@ -58,12 +58,12 @@ private function loadMergedCommission(?SalesTenantManagement $management): ?obje $balance = $commissions->firstWhere('payment_type', SalesCommission::PAYMENT_BALANCE); // balance 레코드가 없으면 기존 단일 레코드 그대로 반환 (하위호환) - if (!$balance) { + if (! $balance) { return $deposit ?? $commissions->first(); } // 1차 필드는 deposit, 2차 필드는 balance에서 가져옴 - $merged = new \stdClass(); + $merged = new \stdClass; $merged->first_payment_at = $deposit?->first_payment_at; $merged->first_partner_paid_at = $deposit?->first_partner_paid_at; $merged->second_payment_at = $balance->second_payment_at; @@ -118,7 +118,7 @@ public function modalShow(int $id): View // 파트너 타입 $partnerType = $management->salesPartner?->partner_type; - if (!$partnerType && $prospect->registered_by) { + if (! $partnerType && $prospect->registered_by) { $partnerType = SalesPartner::where('user_id', $prospect->registered_by)->value('partner_type'); } $partnerType = $partnerType ?? 'individual'; @@ -161,7 +161,7 @@ private function getIndexData(Request $request): array $query = TenantProspect::with(['registeredBy', 'tenant']); // 검색 - if (!empty($filters['search'])) { + if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('company_name', 'like', "%{$search}%") @@ -174,7 +174,7 @@ private function getIndexData(Request $request): array // 상태 필터 $isProgressCompleteFilter = ($filters['status'] === 'progress_complete'); $isHandoverFilter = ($filters['status'] === 'handover'); - if (!empty($filters['status']) && !$isProgressCompleteFilter && !$isHandoverFilter) { + if (! empty($filters['status']) && ! $isProgressCompleteFilter && ! $isHandoverFilter) { $query->where('status', $filters['status']); } @@ -185,7 +185,7 @@ private function getIndexData(Request $request): array } // 영업파트너 필터 - if (!empty($filters['registered_by'])) { + if (! empty($filters['registered_by'])) { $query->where('registered_by', $filters['registered_by']); } @@ -215,7 +215,7 @@ private function getIndexData(Request $request): array // 파트너 타입: management → registered_by 순으로 조회 $partnerType = $management?->salesPartner?->partner_type; - if (!$partnerType && $prospect->registered_by) { + if (! $partnerType && $prospect->registered_by) { $partnerType = SalesPartner::where('user_id', $prospect->registered_by)->value('partner_type'); } $prospect->partner_type = $partnerType ?? 'individual'; @@ -264,7 +264,7 @@ private function getIndexData(Request $request): array // 파트너 타입: management → registered_by 순으로 조회 $partnerType = $management?->salesPartner?->partner_type; - if (!$partnerType && $prospect->registered_by) { + if (! $partnerType && $prospect->registered_by) { $partnerType = SalesPartner::where('user_id', $prospect->registered_by)->value('partner_type'); } $prospect->partner_type = $partnerType ?? 'individual'; @@ -314,7 +314,7 @@ public function updateHqStatus(int $id, Request $request) $this->checkAdminAccess(); $request->validate([ - 'hq_status' => 'required|in:' . implode(',', array_keys(SalesTenantManagement::$hqStatusLabels)), + 'hq_status' => 'required|in:'.implode(',', array_keys(SalesTenantManagement::$hqStatusLabels)), ]); $prospect = TenantProspect::findOrFail($id); @@ -410,7 +410,7 @@ public function updateCommissionDate(int $id, Request $request) // 파트너 resolve → 요율 결정 $partner = $management->salesPartner; - if (!$partner && $prospect->registered_by) { + if (! $partner && $prospect->registered_by) { $partner = SalesPartner::where('user_id', $prospect->registered_by)->first(); } $isGroup = $partner?->isGroup() ?? false; @@ -557,7 +557,7 @@ public function updateReferrerCommission(int $id, Request $request) // 단체 파트너는 수동 수정 불가 $partner = $management->salesPartner; - if (!$partner && $prospect->registered_by) { + if (! $partner && $prospect->registered_by) { $partner = SalesPartner::where('user_id', $prospect->registered_by)->first(); } if ($partner && $partner->isGroup()) { @@ -619,7 +619,7 @@ public function clearCommissionDate(int $id, Request $request) $prospect = TenantProspect::findOrFail($id); $management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first(); - if (!$management) { + if (! $management) { return response()->json(['success' => false, 'message' => '관리 정보가 없습니다.']); } @@ -632,7 +632,7 @@ public function clearCommissionDate(int $id, Request $request) ->where('payment_type', $paymentType) ->first(); - if (!$commission) { + if (! $commission) { return response()->json(['success' => false, 'message' => '수당 정보가 없습니다.']); } $updateData = [$field => null]; diff --git a/app/Http/Controllers/Sales/ConsultationController.php b/app/Http/Controllers/Sales/ConsultationController.php index 40265ddb..d2f3308d 100644 --- a/app/Http/Controllers/Sales/ConsultationController.php +++ b/app/Http/Controllers/Sales/ConsultationController.php @@ -114,7 +114,7 @@ public function uploadAudio(Request $request, GoogleCloudStorageService $gcs): J // 파일 저장 $file = $request->file('audio'); - $fileName = 'audio_' . now()->format('Ymd_His') . '_' . uniqid() . '.' . $file->getClientOriginalExtension(); + $fileName = 'audio_'.now()->format('Ymd_His').'_'.uniqid().'.'.$file->getClientOriginalExtension(); $localPath = $file->storeAs("tenant/consultations/{$tenantId}", $fileName, 'local'); $fileSize = $file->getSize(); @@ -154,7 +154,7 @@ public function uploadAudio(Request $request, GoogleCloudStorageService $gcs): J 'formatted_duration' => $consultation->formatted_duration, 'created_by_name' => $consultation->creator->name, 'created_at' => $consultation->created_at->format('Y-m-d H:i'), - 'has_gcs' => !empty($gcsUri), + 'has_gcs' => ! empty($gcsUri), ], ]); } @@ -178,7 +178,7 @@ public function uploadFile(Request $request): JsonResponse // 파일 저장 $file = $request->file('file'); $originalName = $file->getClientOriginalName(); - $fileName = now()->format('Ymd_His') . '_' . uniqid() . '_' . $originalName; + $fileName = now()->format('Ymd_His').'_'.uniqid().'_'.$originalName; $path = $file->storeAs("tenant/attachments/{$tenantId}", $fileName, 'local'); // DB에 저장 @@ -229,7 +229,7 @@ public function downloadAudio(int $consultationId, GoogleCloudStorageService $gc // GCS에 저장된 경우 서명된 URL로 리다이렉트 if ($consultation->gcs_uri) { - $objectName = str_replace('gs://' . $gcs->getBucketName() . '/', '', $consultation->gcs_uri); + $objectName = str_replace('gs://'.$gcs->getBucketName().'/', '', $consultation->gcs_uri); $signedUrl = $gcs->getSignedUrl($objectName, 60); if ($signedUrl) { @@ -240,7 +240,7 @@ public function downloadAudio(int $consultationId, GoogleCloudStorageService $gc // 로컬 파일 다운로드 $localPath = Storage::disk('local')->path($consultation->file_path); - if (!file_exists($localPath)) { + if (! file_exists($localPath)) { abort(404, '파일을 찾을 수 없습니다.'); } @@ -250,11 +250,11 @@ public function downloadAudio(int $consultationId, GoogleCloudStorageService $gc 'wav' => 'audio/wav', 'mp3' => 'audio/mpeg', 'ogg' => 'audio/ogg', - 'm4a' => 'audio/mp4' + 'm4a' => 'audio/mp4', ]; $contentType = $mimeTypes[$extension] ?? 'audio/webm'; - $downloadFileName = '상담녹음_' . $consultation->created_at->format('Ymd_His') . '.' . $extension; + $downloadFileName = '상담녹음_'.$consultation->created_at->format('Ymd_His').'.'.$extension; return response()->download($localPath, $downloadFileName, [ 'Content-Type' => $contentType, @@ -274,7 +274,7 @@ public function downloadFile(int $consultationId): BinaryFileResponse $localPath = Storage::disk('local')->path($consultation->file_path); - if (!file_exists($localPath)) { + if (! file_exists($localPath)) { abort(404, '파일을 찾을 수 없습니다.'); } @@ -359,7 +359,7 @@ public function prospectUploadAudio(Request $request, GoogleCloudStorageService $duration = $request->input('duration'); $file = $request->file('audio'); - $fileName = 'audio_' . now()->format('Ymd_His') . '_' . uniqid() . '.' . $file->getClientOriginalExtension(); + $fileName = 'audio_'.now()->format('Ymd_His').'_'.uniqid().'.'.$file->getClientOriginalExtension(); $localPath = $file->storeAs("prospect/consultations/{$prospectId}", $fileName, 'local'); $fileSize = $file->getSize(); @@ -397,7 +397,7 @@ public function prospectUploadAudio(Request $request, GoogleCloudStorageService 'formatted_duration' => $consultation->formatted_duration, 'created_by_name' => $consultation->creator->name, 'created_at' => $consultation->created_at->format('Y-m-d H:i'), - 'has_gcs' => !empty($gcsUri), + 'has_gcs' => ! empty($gcsUri), ], ]); } @@ -420,7 +420,7 @@ public function prospectUploadFile(Request $request): JsonResponse $file = $request->file('file'); $originalName = $file->getClientOriginalName(); - $fileName = now()->format('Ymd_His') . '_' . uniqid() . '_' . $originalName; + $fileName = now()->format('Ymd_His').'_'.uniqid().'_'.$originalName; $path = $file->storeAs("prospect/attachments/{$prospectId}", $fileName, 'local'); $consultation = SalesConsultation::createFileByProspect( diff --git a/app/Http/Controllers/Sales/SalesContractController.php b/app/Http/Controllers/Sales/SalesContractController.php index 0bb1b661..62248dd4 100644 --- a/app/Http/Controllers/Sales/SalesContractController.php +++ b/app/Http/Controllers/Sales/SalesContractController.php @@ -89,7 +89,7 @@ public function saveProducts(Request $request): JsonResponse } catch (\Exception $e) { return response()->json([ 'success' => false, - 'message' => '저장 중 오류가 발생했습니다: ' . $e->getMessage(), + 'message' => '저장 중 오류가 발생했습니다: '.$e->getMessage(), ], 500); } } diff --git a/app/Http/Controllers/Sales/SalesDashboardController.php b/app/Http/Controllers/Sales/SalesDashboardController.php index 9b640923..fbcb41f7 100644 --- a/app/Http/Controllers/Sales/SalesDashboardController.php +++ b/app/Http/Controllers/Sales/SalesDashboardController.php @@ -141,7 +141,7 @@ private function getDashboardData(Request $request): array ], ]; - if (!$isGroupPartner) { + if (! $isGroupPartner) { $commissionByRole[] = [ 'name' => '관리자', 'rate' => null, // 1개월 구독료 (퍼센트가 아닌 고정 금액) @@ -181,7 +181,7 @@ private function getDashboardData(Request $request): array // 수당 계산: 개발비 × 요율% (개인 20%, 단체 30%) - 1차/2차 분할은 calculateExpectedCommissionSummary에서 처리 $handoverPartnerRate = $isGroupPartner ? 0.30 : 0.20; - $handoverPartnerCommission = (int)($handoverTotalRegFee * $handoverPartnerRate); + $handoverPartnerCommission = (int) ($handoverTotalRegFee * $handoverPartnerRate); // 내가 매니저로 지정된 인계 완료 건의 수당 계산 $managedHandoverManagements = SalesTenantManagement::where('manager_user_id', $currentUserId) @@ -190,7 +190,7 @@ private function getDashboardData(Request $request): array $managedHandoverManagementIds = $managedHandoverManagements->pluck('id')->toArray(); // 매니저 수당: 1개월 구독료 (퍼센트가 아닌 고정 금액) - $handoverManagerCommission = (int)SalesContractProduct::whereIn('management_id', $managedHandoverManagementIds) + $handoverManagerCommission = (int) SalesContractProduct::whereIn('management_id', $managedHandoverManagementIds) ->sum('subscription_fee'); // 기존 수당에 인계 완료 수당 추가 @@ -202,7 +202,7 @@ private function getDashboardData(Request $request): array // 역할별 수당 업데이트 (실제 지급된 수당 기준) // 참고: 예상 수당은 나중에 $totalExpectedCommission으로 별도 계산됨 $commissionByRole[0]['amount'] = $partnerCommissionTotal; - if (!$isGroupPartner) { + if (! $isGroupPartner) { $commissionByRole[1]['amount'] = $managerCommissionTotal; } @@ -278,7 +278,7 @@ private function getDashboardData(Request $request): array ->toArray(); $devInProgressRegFee = SalesContractProduct::whereIn('management_id', $devInProgressManagementIds) ->sum('registration_fee'); - $expectedFromDevInProgress = (int)($devInProgressRegFee * $handoverPartnerRate); // 개발비 × 요율 (개인 20%, 단체 30%) + $expectedFromDevInProgress = (int) ($devInProgressRegFee * $handoverPartnerRate); // 개발비 × 요율 (개인 20%, 단체 30%) // 2) 인계 완료 중 지급 미완료 건 $handoverUnpaidRegFee = SalesContractProduct::whereIn('management_id', $handoverManagementIds) @@ -288,7 +288,7 @@ private function getDashboardData(Request $request): array ->whereIn('management_id', $handoverManagementIds) ->where('status', SalesCommission::STATUS_PAID) ->sum('partner_commission'); - $expectedFromHandover = (int)($handoverUnpaidRegFee * $handoverPartnerRate) - $paidCommissionFromHandover; + $expectedFromHandover = (int) ($handoverUnpaidRegFee * $handoverPartnerRate) - $paidCommissionFromHandover; $expectedFromHandover = max(0, $expectedFromHandover); // 총 예상 수당 (지급 완료 제외) @@ -383,7 +383,7 @@ public function assignManager(int $tenantId, Request $request): JsonResponse } else { // 특정 매니저 지정 $manager = User::find($managerId); - if (!$manager) { + if (! $manager) { return response()->json([ 'success' => false, 'message' => '매니저를 찾을 수 없습니다.', @@ -428,7 +428,7 @@ public function assignProspectManager(int $prospectId, Request $request): JsonRe } else { // 특정 매니저 지정 $manager = User::find($managerId); - if (!$manager) { + if (! $manager) { return response()->json([ 'success' => false, 'message' => '매니저를 찾을 수 없습니다.', @@ -581,7 +581,7 @@ private function getManagerOnlyProspects(int $currentUserId): array $prospect = $management->tenantProspect; // 내가 등록한 건은 제외 (순수하게 매니저로만 참여한 건만) - if (!$prospect || $prospect->registered_by === $currentUserId) { + if (! $prospect || $prospect->registered_by === $currentUserId) { continue; } @@ -615,7 +615,7 @@ private function getManagerOnlyProspects(int $currentUserId): array foreach ($tenantManagements as $management) { $tenant = $management->tenant; - if (!$tenant) { + if (! $tenant) { continue; } @@ -676,12 +676,12 @@ private function calculatePartnerSummaryStats(array $partnerIds, int $currentUse // 개인 파트너 예상 수당: 1개월 구독료 $individualProspectIds = TenantProspect::whereIn('registered_by', $individualPartnerIds)->pluck('id')->toArray(); $individualMgmtIds = SalesTenantManagement::whereIn('tenant_prospect_id', $individualProspectIds)->pluck('id')->toArray(); - $individualExpected = (int)SalesContractProduct::whereIn('management_id', $individualMgmtIds)->sum('subscription_fee'); + $individualExpected = (int) SalesContractProduct::whereIn('management_id', $individualMgmtIds)->sum('subscription_fee'); // 단체 파트너 예상 수당: 개발비 × 3% $groupProspectIds = TenantProspect::whereIn('registered_by', $groupPartnerUserIds)->pluck('id')->toArray(); $groupMgmtIds = SalesTenantManagement::whereIn('tenant_prospect_id', $groupProspectIds)->pluck('id')->toArray(); - $groupExpected = (int)(SalesContractProduct::whereIn('management_id', $groupMgmtIds)->sum('registration_fee') * 0.03); + $groupExpected = (int) (SalesContractProduct::whereIn('management_id', $groupMgmtIds)->sum('registration_fee') * 0.03); $expectedFromFee = $individualExpected + $groupExpected; @@ -727,16 +727,16 @@ private function calculatePartnerSummaryStats(array $partnerIds, int $currentUse 'expected_commission' => $expectedCommission, 'paid_commission' => $paidManagerCommission, 'first_commission' => [ - 'total' => (int)$halfExpected, - 'pending' => (int)$halfPending, - 'scheduled' => (int)$halfScheduled, - 'paid' => (int)$halfPaid, + 'total' => (int) $halfExpected, + 'pending' => (int) $halfPending, + 'scheduled' => (int) $halfScheduled, + 'paid' => (int) $halfPaid, ], 'second_commission' => [ - 'total' => (int)$halfExpected, - 'pending' => (int)$halfPending, - 'scheduled' => (int)$halfScheduled, - 'paid' => (int)$halfPaid, + 'total' => (int) $halfExpected, + 'pending' => (int) $halfPending, + 'scheduled' => (int) $halfScheduled, + 'paid' => (int) $halfPaid, ], ]; } @@ -782,8 +782,8 @@ private function getPartnerActivitiesDetail($recruitedPartners, int $currentUser // 단체 파트너: 협업지원금(개발비 × 3%), 개인 파트너: 관리자 수당(1개월 구독료) $isRecruitedGroupPartner = $salesPartner && $salesPartner->isGroup(); $expectedCommission = $isRecruitedGroupPartner - ? (int)($totalRegistrationFee * 0.03) - : (int)$totalSubscriptionFee; + ? (int) ($totalRegistrationFee * 0.03) + : (int) $totalSubscriptionFee; // 최종 수당 (확정 + 예상 중 큰 값) $managerCommission = max($confirmedCommission, $expectedCommission); @@ -864,7 +864,7 @@ private function getPartnerActivitiesDetail($recruitedPartners, int $currentUser // 역할 정보 $roles = $partner->userRoles->pluck('role.name')->filter()->toArray(); - $roleLabel = !empty($roles) ? implode(', ', $roles) : '영업'; + $roleLabel = ! empty($roles) ? implode(', ', $roles) : '영업'; $activities[] = [ 'partner' => $partner, @@ -912,7 +912,7 @@ private function getCommissionData(): array */ private function getAllManagerUsers() { - return User::whereHas('userRoles.role', fn($q) => $q->where('name', 'manager')) + return User::whereHas('userRoles.role', fn ($q) => $q->where('name', 'manager')) ->where('is_active', true) ->where('id', '!=', auth()->id()) // 본인 제외 ->get(['id', 'name', 'email']); @@ -930,7 +930,7 @@ public function searchManagers(Request $request): JsonResponse // 디버깅: SQL 쿼리 로깅 \DB::enableQueryLog(); - $managers = User::whereHas('userRoles.role', fn($q) => $q->where('name', 'manager')) + $managers = User::whereHas('userRoles.role', fn ($q) => $q->where('name', 'manager')) ->where('is_active', true) ->where('id', '!=', $authId) ->when($query, function ($q) use ($query) { @@ -1012,24 +1012,24 @@ private function calculateExpectedCommissionSummary($commissions, int $totalExpe return [ 'scheduled_this_month' => $commissions ->where('status', SalesCommission::STATUS_APPROVED) - ->filter(fn($c) => $c->scheduled_payment_date?->format('Y-m') === $thisMonth) + ->filter(fn ($c) => $c->scheduled_payment_date?->format('Y-m') === $thisMonth) ->sum('partner_commission'), 'total_received' => $paidCommission, 'pending_amount' => $pendingAmount, 'contracts_this_month' => $commissions - ->filter(fn($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) + ->filter(fn ($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) ->count(), 'first_commission' => [ - 'total' => (int)$halfExpected, - 'pending' => (int)$halfPending, - 'scheduled' => (int)$halfScheduled, - 'paid' => (int)$halfPaid, + 'total' => (int) $halfExpected, + 'pending' => (int) $halfPending, + 'scheduled' => (int) $halfScheduled, + 'paid' => (int) $halfPaid, ], 'second_commission' => [ - 'total' => (int)$halfExpected, - 'pending' => (int)$halfPending, - 'scheduled' => (int)$halfScheduled, - 'paid' => (int)$halfPaid, + 'total' => (int) $halfExpected, + 'pending' => (int) $halfPending, + 'scheduled' => (int) $halfScheduled, + 'paid' => (int) $halfPaid, ], 'total_commission' => $totalExpectedCommission, ]; @@ -1053,7 +1053,7 @@ private function calculateCommissionSummaryFromCollection($commissions): array return [ 'scheduled_this_month' => $commissions ->where('status', SalesCommission::STATUS_APPROVED) - ->filter(fn($c) => $c->scheduled_payment_date?->format('Y-m') === $thisMonth) + ->filter(fn ($c) => $c->scheduled_payment_date?->format('Y-m') === $thisMonth) ->sum('partner_commission'), 'total_received' => $commissions ->where('status', SalesCommission::STATUS_PAID) @@ -1062,7 +1062,7 @@ private function calculateCommissionSummaryFromCollection($commissions): array ->where('status', SalesCommission::STATUS_PENDING) ->sum('partner_commission'), 'contracts_this_month' => $commissions - ->filter(fn($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) + ->filter(fn ($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) ->count(), 'first_commission' => $firstCommission, 'second_commission' => $secondCommission, diff --git a/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php b/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php index ed2c8e05..6de3c98a 100644 --- a/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php +++ b/app/Http/Controllers/Sales/SalesDevelopmentApprovalController.php @@ -24,7 +24,7 @@ public function __construct( public function index(Request $request): View|Response { // 권한 체크: admin 역할만 접근 가능 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -62,7 +62,7 @@ public function index(Request $request): View|Response public function approve(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -97,7 +97,7 @@ public function approve(Request $request, int $id) public function reject(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -136,7 +136,7 @@ public function reject(Request $request, int $id) public function updateStatus(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -176,7 +176,7 @@ public function updateStatus(Request $request, int $id) public function revertToPending(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -211,7 +211,7 @@ public function revertToPending(Request $request, int $id) public function detail(int $id): View { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } diff --git a/app/Http/Controllers/Sales/SalesManagerController.php b/app/Http/Controllers/Sales/SalesManagerController.php index c75b62f3..7a23b03e 100644 --- a/app/Http/Controllers/Sales/SalesManagerController.php +++ b/app/Http/Controllers/Sales/SalesManagerController.php @@ -199,7 +199,7 @@ public function update(Request $request, int $id) $validated = $request->validate([ 'name' => 'required|string|max:100', - 'email' => 'required|email|max:255|unique:users,email,' . $id, + 'email' => 'required|email|max:255|unique:users,email,'.$id, 'phone' => 'nullable|string|max:20', 'password' => 'nullable|string|min:4|confirmed', 'role_ids' => 'required|array|min:1', @@ -242,7 +242,7 @@ public function update(Request $request, int $id) public function destroy(int $id) { // 권한 체크: admin 역할만 삭제 가능 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '삭제 권한이 없습니다.'); } @@ -298,6 +298,7 @@ public function delegateRole(Request $request, int $id) $this->service->delegateRole($fromUser, $toUser, $validated['role_name']); $roleLabel = '상담매니저'; + return redirect()->back() ->with('success', "{$roleLabel} 역할이 {$toUser->name}님에게 위임되었습니다."); } catch (\InvalidArgumentException $e) { @@ -319,6 +320,7 @@ public function assignRole(Request $request, int $id) $this->service->assignRole($partner, $validated['role_name']); $roleLabels = ['sales' => '영업파트너', 'manager' => '상담매니저']; + return redirect()->back() ->with('success', "{$roleLabels[$validated['role_name']]} 역할이 부여되었습니다."); } @@ -336,6 +338,7 @@ public function removeRole(Request $request, int $id) $this->service->removeRole($partner, $validated['role_name']); $roleLabels = ['sales' => '영업파트너', 'manager' => '상담매니저']; + return redirect()->back() ->with('success', "{$roleLabels[$validated['role_name']]} 역할이 제거되었습니다."); } @@ -371,7 +374,7 @@ public function deleteDocument(int $id, int $documentId) public function approvals(Request $request): View|Response { // 권한 체크: admin 역할만 접근 가능 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -404,7 +407,7 @@ public function approvals(Request $request): View|Response public function approveFromList(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } @@ -428,7 +431,7 @@ public function approveFromList(Request $request, int $id) public function rejectFromList(Request $request, int $id) { // 권한 체크 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { abort(403, '접근 권한이 없습니다.'); } diff --git a/app/Http/Controllers/Sales/SalesProductController.php b/app/Http/Controllers/Sales/SalesProductController.php index be331146..a6f5fe0b 100644 --- a/app/Http/Controllers/Sales/SalesProductController.php +++ b/app/Http/Controllers/Sales/SalesProductController.php @@ -27,7 +27,7 @@ public function index(Request $request): View|Response $categories = SalesProductCategory::active() ->ordered() - ->with(['products' => fn($q) => $q->ordered()]) + ->with(['products' => fn ($q) => $q->ordered()]) ->get(); $currentCategoryCode = $request->input('category', $categories->first()?->code); @@ -43,7 +43,7 @@ public function productList(Request $request): View { $categoryCode = $request->input('category'); $category = SalesProductCategory::where('code', $categoryCode) - ->with(['products' => fn($q) => $q->ordered()]) + ->with(['products' => fn ($q) => $q->ordered()]) ->first(); return view('sales.products.partials.product-list', compact('category')); @@ -144,7 +144,7 @@ public function destroy(int $id): JsonResponse public function toggleActive(int $id): JsonResponse { $product = SalesProduct::findOrFail($id); - $product->update(['is_active' => !$product->is_active]); + $product->update(['is_active' => ! $product->is_active]); return response()->json([ 'success' => true, @@ -268,7 +268,7 @@ public function getProductsApi(): JsonResponse { $categories = SalesProductCategory::active() ->ordered() - ->with(['products' => fn($q) => $q->active()->ordered()]) + ->with(['products' => fn ($q) => $q->active()->ordered()]) ->get(); return response()->json([ diff --git a/app/Http/Controllers/Sales/SalesProspectController.php b/app/Http/Controllers/Sales/SalesProspectController.php index b9f85555..3dd68380 100644 --- a/app/Http/Controllers/Sales/SalesProspectController.php +++ b/app/Http/Controllers/Sales/SalesProspectController.php @@ -99,19 +99,19 @@ public function store(Request $request) ]); // 명함 이미지 저장 (Base64) - if (!empty($validated['business_card_image_data'])) { + if (! empty($validated['business_card_image_data'])) { $validated['business_card_image'] = $this->saveBase64Image($validated['business_card_image_data'], 'business-cards'); } unset($validated['business_card_image_data']); // 신분증 이미지 저장 (Base64) - if (!empty($validated['id_card_image_data'])) { + if (! empty($validated['id_card_image_data'])) { $validated['id_card_image'] = $this->saveBase64Image($validated['id_card_image_data'], 'id-cards'); } unset($validated['id_card_image_data']); // 통장사본 이미지 저장 (Base64) - if (!empty($validated['bankbook_image_data'])) { + if (! empty($validated['bankbook_image_data'])) { $validated['bankbook_image'] = $this->saveBase64Image($validated['bankbook_image_data'], 'bankbooks'); } unset($validated['bankbook_image_data']); @@ -132,7 +132,7 @@ public function show(int $id): View 'salesManager', 'products', 'scenarios', - 'consultations.manager' + 'consultations.manager', ])->findOrFail($id); $managers = SalesManager::active() @@ -250,7 +250,7 @@ private function saveBase64Image(string $base64Data, string $folder): ?string return null; } - $filename = $folder . '/' . date('Ymd') . '_' . uniqid() . '.' . $extension; + $filename = $folder.'/'.date('Ymd').'_'.uniqid().'.'.$extension; Storage::disk('public')->put($filename, $imageData); return $filename; diff --git a/app/Http/Controllers/Sales/SalesScenarioController.php b/app/Http/Controllers/Sales/SalesScenarioController.php index d2acd2bd..b4328b26 100644 --- a/app/Http/Controllers/Sales/SalesScenarioController.php +++ b/app/Http/Controllers/Sales/SalesScenarioController.php @@ -7,9 +7,9 @@ use App\Models\Sales\SalesTenantManagement; use App\Models\Sales\TenantProspect; use App\Models\Tenants\Tenant; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Illuminate\Http\JsonResponse; use Illuminate\View\View; /** diff --git a/app/Http/Controllers/Sales/TenantProspectController.php b/app/Http/Controllers/Sales/TenantProspectController.php index 1a707b45..5831995c 100644 --- a/app/Http/Controllers/Sales/TenantProspectController.php +++ b/app/Http/Controllers/Sales/TenantProspectController.php @@ -71,7 +71,7 @@ public function store(Request $request) // 등록 가능 여부 확인 $checkResult = $this->service->canRegister($validated['business_number']); - if (!$checkResult['can_register']) { + if (! $checkResult['can_register']) { return redirect()->back() ->withInput() ->with('error', $checkResult['reason']); @@ -172,7 +172,7 @@ public function destroy(int $id) } // 관리자만 삭제 가능 - if (!auth()->user()->isAdmin()) { + if (! auth()->user()->isAdmin()) { return redirect()->route('sales.prospects.index') ->with('error', '삭제 권한이 없습니다. 본사 운영팀에 문의하세요.'); } @@ -257,7 +257,7 @@ public function deleteAttachment(Request $request, int $id) $type = $request->get('type'); $allowedTypes = ['business_card', 'id_card', 'bankbook']; - if (!in_array($type, $allowedTypes)) { + if (! in_array($type, $allowedTypes)) { return response()->json(['success' => false, 'message' => '잘못된 요청입니다.'], 400); } diff --git a/app/Http/Controllers/System/AiVoiceRecordingController.php b/app/Http/Controllers/System/AiVoiceRecordingController.php index 2eafcd16..7434cccf 100644 --- a/app/Http/Controllers/System/AiVoiceRecordingController.php +++ b/app/Http/Controllers/System/AiVoiceRecordingController.php @@ -9,7 +9,6 @@ use App\Services\GoogleCloudService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Str; use Illuminate\View\View; use Symfony\Component\HttpFoundation\Response; diff --git a/app/Http/Controllers/System/HolidayController.php b/app/Http/Controllers/System/HolidayController.php index 2d88475d..2307bf34 100644 --- a/app/Http/Controllers/System/HolidayController.php +++ b/app/Http/Controllers/System/HolidayController.php @@ -158,6 +158,7 @@ public function bulkStore(Request $request): JsonResponse if ($exists) { $skipped++; + continue; } diff --git a/app/Http/Controllers/System/SystemAlertController.php b/app/Http/Controllers/System/SystemAlertController.php index e0e2de78..cffdf8f7 100644 --- a/app/Http/Controllers/System/SystemAlertController.php +++ b/app/Http/Controllers/System/SystemAlertController.php @@ -38,10 +38,10 @@ public function index(Request $request): View // 날짜 범위 필터 if ($request->filled('date_from')) { - $query->where('created_at', '>=', $request->date_from . ' 00:00:00'); + $query->where('created_at', '>=', $request->date_from.' 00:00:00'); } if ($request->filled('date_to')) { - $query->where('created_at', '<=', $request->date_to . ' 23:59:59'); + $query->where('created_at', '<=', $request->date_to.' 23:59:59'); } $alerts = $query->paginate(50)->withQueryString(); @@ -95,4 +95,4 @@ public function markAllAsRead(): Response return response('', 200, ['HX-Refresh' => 'true']); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/TenantSettingController.php b/app/Http/Controllers/TenantSettingController.php index 1d342363..ecf362a3 100644 --- a/app/Http/Controllers/TenantSettingController.php +++ b/app/Http/Controllers/TenantSettingController.php @@ -124,4 +124,4 @@ public function store(Request $request): RedirectResponse return redirect()->route('tenant-settings.index') ->with('success', '설정이 저장되었습니다.'); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Video/TutorialVideoController.php b/app/Http/Controllers/Video/TutorialVideoController.php index 253af8d8..71dc0130 100644 --- a/app/Http/Controllers/Video/TutorialVideoController.php +++ b/app/Http/Controllers/Video/TutorialVideoController.php @@ -43,7 +43,7 @@ public function upload(Request $request): JsonResponse 'screenshots.*' => 'required|image|max:10240', // 최대 10MB ]); - $uploadDir = storage_path('app/tutorial_uploads/' . auth()->id() . '/' . time()); + $uploadDir = storage_path('app/tutorial_uploads/'.auth()->id().'/'.time()); if (! is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } @@ -52,7 +52,7 @@ public function upload(Request $request): JsonResponse foreach ($request->file('screenshots') as $i => $file) { $filename = sprintf('screenshot_%02d.%s', $i + 1, $file->getClientOriginalExtension()); $file->move($uploadDir, $filename); - $paths[] = $uploadDir . '/' . $filename; + $paths[] = $uploadDir.'/'.$filename; } return response()->json([ @@ -79,7 +79,7 @@ public function analyze(Request $request): JsonResponse if (! file_exists($path)) { return response()->json([ 'success' => false, - 'message' => '업로드된 파일을 찾을 수 없습니다: ' . basename($path), + 'message' => '업로드된 파일을 찾을 수 없습니다: '.basename($path), ], 400); } } @@ -93,7 +93,7 @@ public function analyze(Request $request): JsonResponse return response()->json([ 'success' => false, - 'message' => 'AI 분석 중 오류가 발생했습니다: ' . $e->getMessage(), + 'message' => 'AI 분석 중 오류가 발생했습니다: '.$e->getMessage(), ], 500); } @@ -188,7 +188,7 @@ public function download(int $id): BinaryFileResponse|RedirectResponse|JsonRespo return response()->json(['message' => '영상 파일을 찾을 수 없습니다.'], 404); } - $filename = 'tutorial_' . ($tutorial->title ? preg_replace('/[^a-zA-Z0-9가-힣_\-.]/', '_', $tutorial->title) : $tutorial->id) . '.mp4'; + $filename = 'tutorial_'.($tutorial->title ? preg_replace('/[^a-zA-Z0-9가-힣_\-.]/', '_', $tutorial->title) : $tutorial->id).'.mp4'; return response()->download($tutorial->output_path, $filename, [ 'Content-Type' => 'video/mp4', diff --git a/app/Http/Controllers/Video/Veo3Controller.php b/app/Http/Controllers/Video/Veo3Controller.php index f206241c..fb2566c7 100644 --- a/app/Http/Controllers/Video/Veo3Controller.php +++ b/app/Http/Controllers/Video/Veo3Controller.php @@ -355,12 +355,12 @@ private function buildYoutubeText(VideoGeneration $video, array $scenario, array // 해시태그 생성 $hashtags = ['#shorts', '#쇼츠']; if ($keyword) { - $hashtags[] = '#' . str_replace(' ', '', $keyword); + $hashtags[] = '#'.str_replace(' ', '', $keyword); } // 시나리오에서 추가 태그 추출 $bgmMood = $scenario['bgm_mood'] ?? ''; if ($bgmMood) { - $hashtags[] = '#' . $bgmMood; + $hashtags[] = '#'.$bgmMood; } $hashtags = array_merge($hashtags, ['#건강', '#건강정보', '#헬스']); @@ -373,7 +373,7 @@ private function buildYoutubeText(VideoGeneration $video, array $scenario, array foreach ($scenes as $scene) { $narration = $scene['narration'] ?? ''; if ($narration && ($scene['scene_type'] ?? '') !== 'HOOK') { - $descLines[] = '- ' . mb_substr($narration, 0, 60); + $descLines[] = '- '.mb_substr($narration, 0, 60); } } diff --git a/app/Http/Middleware/AutoLoginViaRemember.php b/app/Http/Middleware/AutoLoginViaRemember.php index f3ea54c1..d00884bd 100644 --- a/app/Http/Middleware/AutoLoginViaRemember.php +++ b/app/Http/Middleware/AutoLoginViaRemember.php @@ -30,16 +30,18 @@ public function handle(Request $request, Closure $next): Response $user = Auth::user(); // HQ 테넌트 소속 확인 - if (!$user->belongsToHQ()) { + if (! $user->belongsToHQ()) { Auth::logout(); Log::info('[AutoLoginViaRemember] Non-HQ user rejected', ['user_id' => $user->id]); + return $next($request); } // 활성 상태 확인 - if (!$user->is_active) { + if (! $user->is_active) { Auth::logout(); Log::info('[AutoLoginViaRemember] Inactive user rejected', ['user_id' => $user->id]); + return $next($request); } @@ -79,4 +81,4 @@ private function refreshApiToken(int $userId, int $tenantId): void ]); } } -} \ No newline at end of file +} diff --git a/app/Jobs/VideoGenerationJob.php b/app/Jobs/VideoGenerationJob.php index fd64089f..54ee5a95 100644 --- a/app/Jobs/VideoGenerationJob.php +++ b/app/Jobs/VideoGenerationJob.php @@ -3,12 +3,12 @@ namespace App\Jobs; use App\Models\VideoGeneration; +use App\Services\GoogleCloudStorageService; use App\Services\Video\BgmService; use App\Services\Video\GeminiScriptService; use App\Services\Video\TtsService; use App\Services\Video\VeoVideoService; use App\Services\Video\VideoAssemblyService; -use App\Services\GoogleCloudStorageService; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -114,7 +114,7 @@ public function handle( $video->updateProgress( VideoGeneration::STATUS_GENERATING_CLIPS, 20 + (int) (($sceneNum / count($scenes)) * 10), - "영상 클립 생성 요청 중 ({$sceneNum}/" . count($scenes) . ')' + "영상 클립 생성 요청 중 ({$sceneNum}/".count($scenes).')' ); $result = $veo->generateClip($prompt, $duration); @@ -137,7 +137,7 @@ public function handle( $video->updateProgress( VideoGeneration::STATUS_GENERATING_CLIPS, 30 + (int) (($sceneNum / count($scenes)) * 40), - "영상 클립 생성 대기 중 ({$sceneNum}/" . count($scenes) . ')' + "영상 클립 생성 대기 중 ({$sceneNum}/".count($scenes).')' ); $result = $veo->waitAndSave( @@ -189,7 +189,7 @@ public function handle( // 성공한 클립이 절반 미만이면 전체 실패 if (count($clipPaths) < ceil(count($scenes) / 2)) { - $video->markFailed('영상 클립 생성 실패 (성공: ' . count($clipPaths) . '/' . count($scenes) . ')'); + $video->markFailed('영상 클립 생성 실패 (성공: '.count($clipPaths).'/'.count($scenes).')'); return; } @@ -310,7 +310,7 @@ public function handle( // === 완료 === $stepMsg = empty($skippedScenes) ? '완료' - : '완료 (장면 ' . implode(',', $skippedScenes) . ' 건너뜀)'; + : '완료 (장면 '.implode(',', $skippedScenes).' 건너뜀)'; $video->update([ 'status' => VideoGeneration::STATUS_COMPLETED, @@ -344,7 +344,7 @@ public function handle( public function failed(\Throwable $exception): void { $video = VideoGeneration::withoutGlobalScopes()->find($this->videoGenerationId); - $video?->markFailed('Job 실패: ' . $exception->getMessage()); + $video?->markFailed('Job 실패: '.$exception->getMessage()); } /** @@ -356,6 +356,6 @@ private function makeSafePrompt(string $prompt): string $prompt = preg_replace('/\b(woman|man|girl|boy|person|people|her|his|she|he)\b/i', 'subject', $prompt); // 안전 키워드 추가 - return 'Safe for all audiences. Professional stock footage style. ' . $prompt; + return 'Safe for all audiences. Professional stock footage style. '.$prompt; } } diff --git a/app/Mail/EsignCompletedMail.php b/app/Mail/EsignCompletedMail.php index 7cd992df..b650592b 100644 --- a/app/Mail/EsignCompletedMail.php +++ b/app/Mail/EsignCompletedMail.php @@ -6,8 +6,8 @@ use App\Models\ESign\EsignSigner; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; @@ -32,7 +32,7 @@ public function envelope(): Envelope public function content(): Content { - $downloadUrl = config('app.url') . '/esign/sign/' . $this->signer->access_token . '/api/document'; + $downloadUrl = config('app.url').'/esign/sign/'.$this->signer->access_token.'/api/document'; return new Content( html: 'emails.esign.completed', diff --git a/app/Mail/EsignOtpMail.php b/app/Mail/EsignOtpMail.php index eaf00768..f272a447 100644 --- a/app/Mail/EsignOtpMail.php +++ b/app/Mail/EsignOtpMail.php @@ -4,8 +4,8 @@ use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; diff --git a/app/Mail/EsignRequestMail.php b/app/Mail/EsignRequestMail.php index e4554531..dc74db1c 100644 --- a/app/Mail/EsignRequestMail.php +++ b/app/Mail/EsignRequestMail.php @@ -6,8 +6,8 @@ use App\Models\ESign\EsignSigner; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; -use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; @@ -33,7 +33,7 @@ public function envelope(): Envelope public function content(): Content { - $signUrl = config('app.url') . '/esign/sign/' . $this->signer->access_token; + $signUrl = config('app.url').'/esign/sign/'.$this->signer->access_token; return new Content( html: 'emails.esign.request', diff --git a/app/Models/Barobill/AccountCode.php b/app/Models/Barobill/AccountCode.php index 7c295872..47cb6843 100644 --- a/app/Models/Barobill/AccountCode.php +++ b/app/Models/Barobill/AccountCode.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 계정과목 모델 diff --git a/app/Models/Barobill/BankSyncStatus.php b/app/Models/Barobill/BankSyncStatus.php index 9d32ac83..33b1dab0 100644 --- a/app/Models/Barobill/BankSyncStatus.php +++ b/app/Models/Barobill/BankSyncStatus.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 바로빌 계좌 거래 동기화 상태 모델 diff --git a/app/Models/Barobill/BankTransactionSplit.php b/app/Models/Barobill/BankTransactionSplit.php index 509d68a1..0b6ac1cb 100644 --- a/app/Models/Barobill/BankTransactionSplit.php +++ b/app/Models/Barobill/BankTransactionSplit.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 계좌 입출금 거래 분개 모델 @@ -62,7 +62,7 @@ public static function getByDateRange(int $tenantId, string $startDate, string $ $grouped = []; foreach ($splits as $split) { $key = $split->original_unique_key; - if (!isset($grouped[$key])) { + if (! isset($grouped[$key])) { $grouped[$key] = []; } $grouped[$key][] = $split; diff --git a/app/Models/Barobill/BarobillConfig.php b/app/Models/Barobill/BarobillConfig.php index b63104fd..5e5cf723 100644 --- a/app/Models/Barobill/BarobillConfig.php +++ b/app/Models/Barobill/BarobillConfig.php @@ -102,6 +102,6 @@ public function getMaskedCertKeyAttribute(): string return $this->cert_key; } - return substr($this->cert_key, 0, 8) . str_repeat('*', 8) . '...'; + return substr($this->cert_key, 0, 8).str_repeat('*', 8).'...'; } } diff --git a/app/Models/Barobill/BarobillMember.php b/app/Models/Barobill/BarobillMember.php index 01d79f2e..1875ee90 100644 --- a/app/Models/Barobill/BarobillMember.php +++ b/app/Models/Barobill/BarobillMember.php @@ -60,8 +60,9 @@ public function getFormattedBizNoAttribute(): string { $bizNo = preg_replace('/[^0-9]/', '', $this->biz_no); if (strlen($bizNo) === 10) { - return substr($bizNo, 0, 3) . '-' . substr($bizNo, 3, 2) . '-' . substr($bizNo, 5); + return substr($bizNo, 0, 3).'-'.substr($bizNo, 3, 2).'-'.substr($bizNo, 5); } + return $this->biz_no; } diff --git a/app/Models/Barobill/BarobillPricingPolicy.php b/app/Models/Barobill/BarobillPricingPolicy.php index 88164d33..602fdf46 100644 --- a/app/Models/Barobill/BarobillPricingPolicy.php +++ b/app/Models/Barobill/BarobillPricingPolicy.php @@ -50,7 +50,9 @@ class BarobillPricingPolicy extends Model * 서비스 유형 상수 */ public const TYPE_CARD = 'card'; + public const TYPE_TAX_INVOICE = 'tax_invoice'; + public const TYPE_BANK_ACCOUNT = 'bank_account'; /** @@ -104,7 +106,7 @@ public static function getAllActive(): \Illuminate\Database\Eloquent\Collection /** * 추가 과금액 계산 * - * @param int $usageCount 사용량 + * @param int $usageCount 사용량 * @return array ['free_count' => int, 'billable_count' => int, 'billable_amount' => int] */ public function calculateBilling(int $usageCount): array diff --git a/app/Models/Barobill/BarobillSetting.php b/app/Models/Barobill/BarobillSetting.php index 445b4352..20c1f4cd 100644 --- a/app/Models/Barobill/BarobillSetting.php +++ b/app/Models/Barobill/BarobillSetting.php @@ -60,10 +60,19 @@ public function tenant(): BelongsTo public function getActiveServicesAttribute(): array { $services = []; - if ($this->use_tax_invoice) $services[] = 'tax_invoice'; - if ($this->use_bank_account) $services[] = 'bank_account'; - if ($this->use_card_usage) $services[] = 'card_usage'; - if ($this->use_hometax) $services[] = 'hometax'; + if ($this->use_tax_invoice) { + $services[] = 'tax_invoice'; + } + if ($this->use_bank_account) { + $services[] = 'bank_account'; + } + if ($this->use_card_usage) { + $services[] = 'card_usage'; + } + if ($this->use_hometax) { + $services[] = 'hometax'; + } + return $services; } } diff --git a/app/Models/Barobill/BarobillSubscription.php b/app/Models/Barobill/BarobillSubscription.php index a44ad136..f4ca21e6 100644 --- a/app/Models/Barobill/BarobillSubscription.php +++ b/app/Models/Barobill/BarobillSubscription.php @@ -74,12 +74,13 @@ public function getServiceTypeLabelAttribute(): string */ public function getStatusLabelAttribute(): string { - if (!$this->is_active) { + if (! $this->is_active) { return '비활성'; } if ($this->ended_at && $this->ended_at->isPast()) { return '종료'; } + return '구독중'; } @@ -88,12 +89,13 @@ public function getStatusLabelAttribute(): string */ public function getStatusColorAttribute(): string { - if (!$this->is_active) { + if (! $this->is_active) { return 'bg-gray-100 text-gray-800'; } if ($this->ended_at && $this->ended_at->isPast()) { return 'bg-red-100 text-red-800'; } + return 'bg-green-100 text-green-800'; } @@ -103,10 +105,10 @@ public function getStatusColorAttribute(): string public function scopeActive($query) { return $query->where('is_active', true) - ->where(function ($q) { - $q->whereNull('ended_at') - ->orWhere('ended_at', '>=', now()->toDateString()); - }); + ->where(function ($q) { + $q->whereNull('ended_at') + ->orWhere('ended_at', '>=', now()->toDateString()); + }); } /** diff --git a/app/Models/Barobill/CardTransaction.php b/app/Models/Barobill/CardTransaction.php index 2b07e94d..fc7534f8 100644 --- a/app/Models/Barobill/CardTransaction.php +++ b/app/Models/Barobill/CardTransaction.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 바로빌 카드 사용내역 모델 @@ -103,6 +103,6 @@ public static function getByDateRange(int $tenantId, string $startDate, string $ $query->where('card_num', $cardNum); } - return $query->get()->keyBy(fn($item) => $item->unique_key); + return $query->get()->keyBy(fn ($item) => $item->unique_key); } } diff --git a/app/Models/Barobill/CardTransactionSplit.php b/app/Models/Barobill/CardTransactionSplit.php index 0e4654a2..e7e731cc 100644 --- a/app/Models/Barobill/CardTransactionSplit.php +++ b/app/Models/Barobill/CardTransactionSplit.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 카드 거래 분개 모델 @@ -67,7 +67,7 @@ public static function getByDateRange(int $tenantId, string $startDate, string $ $grouped = []; foreach ($splits as $split) { $key = $split->original_unique_key; - if (!isset($grouped[$key])) { + if (! isset($grouped[$key])) { $grouped[$key] = []; } $grouped[$key][] = $split; diff --git a/app/Models/Barobill/HometaxInvoice.php b/app/Models/Barobill/HometaxInvoice.php index fa26c861..e6c59426 100644 --- a/app/Models/Barobill/HometaxInvoice.php +++ b/app/Models/Barobill/HometaxInvoice.php @@ -99,15 +99,19 @@ class HometaxInvoice extends Model // 과세유형 상수 public const TAX_TYPE_TAXABLE = 1; // 과세 + public const TAX_TYPE_ZERO_RATE = 2; // 영세 + public const TAX_TYPE_EXEMPT = 3; // 면세 // 영수/청구 상수 public const PURPOSE_TYPE_RECEIPT = 1; // 영수 + public const PURPOSE_TYPE_CLAIM = 2; // 청구 // 발급유형 상수 public const ISSUE_TYPE_NORMAL = 1; // 정발행 + public const ISSUE_TYPE_REVERSE = 2; // 역발행 /** @@ -147,7 +151,7 @@ public function scopePurchase($query) */ public function scopePeriod($query, string $startDate, string $endDate, string $dateType = 'write') { - $column = match($dateType) { + $column = match ($dateType) { 'issue' => 'issue_date', 'send' => 'send_date', default => 'write_date', @@ -169,12 +173,12 @@ public function scopeSearchCorp($query, string $keyword, string $invoiceType = ' if ($invoiceType === 'sales') { return $query->where(function ($q) use ($keyword) { $q->where('invoicee_corp_name', 'like', "%{$keyword}%") - ->orWhere('invoicee_corp_num', 'like', "%{$keyword}%"); + ->orWhere('invoicee_corp_num', 'like', "%{$keyword}%"); }); } else { return $query->where(function ($q) use ($keyword) { $q->where('invoicer_corp_name', 'like', "%{$keyword}%") - ->orWhere('invoicer_corp_num', 'like', "%{$keyword}%"); + ->orWhere('invoicer_corp_num', 'like', "%{$keyword}%"); }); } } @@ -184,7 +188,7 @@ public function scopeSearchCorp($query, string $keyword, string $invoiceType = ' */ public function getTaxTypeNameAttribute(): string { - return match($this->tax_type) { + return match ($this->tax_type) { self::TAX_TYPE_TAXABLE => '과세', self::TAX_TYPE_ZERO_RATE => '영세', self::TAX_TYPE_EXEMPT => '면세', @@ -197,7 +201,7 @@ public function getTaxTypeNameAttribute(): string */ public function getPurposeTypeNameAttribute(): string { - return match($this->purpose_type) { + return match ($this->purpose_type) { self::PURPOSE_TYPE_RECEIPT => '영수', self::PURPOSE_TYPE_CLAIM => '청구', default => '-', @@ -209,7 +213,7 @@ public function getPurposeTypeNameAttribute(): string */ public function getIssueTypeNameAttribute(): string { - return match($this->issue_type) { + return match ($this->issue_type) { self::ISSUE_TYPE_NORMAL => '정발급', self::ISSUE_TYPE_REVERSE => '역발급', default => '-', @@ -247,17 +251,17 @@ public static function fromApiData(array $apiData, int $tenantId, string $invoic { // 작성일자 파싱 $writeDate = null; - if (!empty($apiData['writeDate']) && strlen($apiData['writeDate']) >= 8) { - $writeDate = substr($apiData['writeDate'], 0, 4) . '-' . - substr($apiData['writeDate'], 4, 2) . '-' . + if (! empty($apiData['writeDate']) && strlen($apiData['writeDate']) >= 8) { + $writeDate = substr($apiData['writeDate'], 0, 4).'-'. + substr($apiData['writeDate'], 4, 2).'-'. substr($apiData['writeDate'], 6, 2); } // 발급일자 파싱 $issueDate = null; - if (!empty($apiData['issueDT']) && strlen($apiData['issueDT']) >= 8) { - $issueDate = substr($apiData['issueDT'], 0, 4) . '-' . - substr($apiData['issueDT'], 4, 2) . '-' . + if (! empty($apiData['issueDT']) && strlen($apiData['issueDT']) >= 8) { + $issueDate = substr($apiData['issueDT'], 0, 4).'-'. + substr($apiData['issueDT'], 4, 2).'-'. substr($apiData['issueDT'], 6, 2); } @@ -273,11 +277,11 @@ public static function fromApiData(array $apiData, int $tenantId, string $invoic 'invoicee_corp_num' => $apiData['invoiceeCorpNum'] ?? '', 'invoicee_corp_name' => $apiData['invoiceeCorpName'] ?? '', 'invoicee_ceo_name' => $apiData['invoiceeCEOName'] ?? null, - 'supply_amount' => (int)($apiData['supplyAmount'] ?? 0), - 'tax_amount' => (int)($apiData['taxAmount'] ?? 0), - 'total_amount' => (int)($apiData['totalAmount'] ?? 0), - 'tax_type' => (int)($apiData['taxType'] ?? 1), - 'purpose_type' => (int)($apiData['purposeType'] ?? 1), + 'supply_amount' => (int) ($apiData['supplyAmount'] ?? 0), + 'tax_amount' => (int) ($apiData['taxAmount'] ?? 0), + 'total_amount' => (int) ($apiData['totalAmount'] ?? 0), + 'tax_type' => (int) ($apiData['taxType'] ?? 1), + 'purpose_type' => (int) ($apiData['purposeType'] ?? 1), 'issue_type' => 1, // 기본값: 정발행 'item_name' => $apiData['itemName'] ?? null, 'remark' => $apiData['remark'] ?? null, diff --git a/app/Models/Barobill/HometaxInvoiceJournal.php b/app/Models/Barobill/HometaxInvoiceJournal.php index 478d1fdd..035f43d6 100644 --- a/app/Models/Barobill/HometaxInvoiceJournal.php +++ b/app/Models/Barobill/HometaxInvoiceJournal.php @@ -2,9 +2,9 @@ namespace App\Models\Barobill; +use App\Models\Tenants\Tenant; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Tenants\Tenant; /** * 홈택스 세금계산서 분개 모델 @@ -72,15 +72,15 @@ public static function saveJournals(int $tenantId, int $invoiceId, array $invoic 'dc_type' => $line['dc_type'], 'account_code' => $line['account_code'], 'account_name' => $line['account_name'], - 'debit_amount' => (int)($line['debit_amount'] ?? 0), - 'credit_amount' => (int)($line['credit_amount'] ?? 0), + 'debit_amount' => (int) ($line['debit_amount'] ?? 0), + 'credit_amount' => (int) ($line['credit_amount'] ?? 0), 'description' => $line['description'] ?? '', 'sort_order' => $index, 'invoice_type' => $invoiceData['invoice_type'] ?? '', 'write_date' => $invoiceData['write_date'] ?? null, - 'supply_amount' => (int)($invoiceData['supply_amount'] ?? 0), - 'tax_amount' => (int)($invoiceData['tax_amount'] ?? 0), - 'total_amount' => (int)($invoiceData['total_amount'] ?? 0), + 'supply_amount' => (int) ($invoiceData['supply_amount'] ?? 0), + 'tax_amount' => (int) ($invoiceData['tax_amount'] ?? 0), + 'total_amount' => (int) ($invoiceData['total_amount'] ?? 0), 'trading_partner_name' => $invoiceData['trading_partner_name'] ?? '', ]); } diff --git a/app/Models/Boards/BoardComment.php b/app/Models/Boards/BoardComment.php index 9fa0ce96..74b19a00 100644 --- a/app/Models/Boards/BoardComment.php +++ b/app/Models/Boards/BoardComment.php @@ -68,4 +68,4 @@ public function replies(): HasMany { return $this->children(); } -} \ No newline at end of file +} diff --git a/app/Models/Category.php b/app/Models/Category.php index 52edcead..be610d2d 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -79,4 +79,4 @@ public function scopeActive($query) { return $query->where('is_active', true); } -} \ No newline at end of file +} diff --git a/app/Models/Coocon/CooconConfig.php b/app/Models/Coocon/CooconConfig.php index e62c607e..43129bbc 100644 --- a/app/Models/Coocon/CooconConfig.php +++ b/app/Models/Coocon/CooconConfig.php @@ -102,6 +102,6 @@ public function getMaskedApiKeyAttribute(): string return $this->api_key; } - return substr($this->api_key, 0, 8) . str_repeat('*', 8) . '...'; + return substr($this->api_key, 0, 8).str_repeat('*', 8).'...'; } } diff --git a/app/Models/Credit/CreditInquiry.php b/app/Models/Credit/CreditInquiry.php index 374d6011..b9757285 100644 --- a/app/Models/Credit/CreditInquiry.php +++ b/app/Models/Credit/CreditInquiry.php @@ -87,7 +87,7 @@ protected static function boot() */ public static function generateInquiryKey(): string { - return date('Ymd') . Str::upper(Str::random(24)); + return date('Ymd').Str::upper(Str::random(24)); } /** @@ -126,8 +126,9 @@ public function getFormattedCompanyKeyAttribute(): string { $key = $this->company_key; if (strlen($key) === 10) { - return substr($key, 0, 3) . '-' . substr($key, 3, 2) . '-' . substr($key, 5); + return substr($key, 0, 3).'-'.substr($key, 3, 2).'-'.substr($key, 5); } + return $key; } @@ -172,11 +173,11 @@ public function getNtsStatusLabelAttribute(): string /** * API 응답으로부터 모델 생성 * - * @param string $companyKey 사업자번호 - * @param array $apiResult 쿠콘 API 결과 - * @param array|null $ntsResult 국세청 API 결과 - * @param int|null $userId 조회자 ID - * @param int|null $tenantId 테넌트 ID + * @param string $companyKey 사업자번호 + * @param array $apiResult 쿠콘 API 결과 + * @param array|null $ntsResult 국세청 API 결과 + * @param int|null $userId 조회자 ID + * @param int|null $tenantId 테넌트 ID */ public static function createFromApiResponse( string $companyKey, @@ -199,10 +200,10 @@ public static function createFromApiResponse( $businessType = $companyInfoData['bizcnd'] ?? $companyInfoData['indutyNm'] ?? null; $businessItem = $companyInfoData['bizitm'] ?? $companyInfoData['indutyDetailNm'] ?? null; $establishmentDate = null; - if (!empty($companyInfoData['estbDt']) || !empty($companyInfoData['estbDate'])) { + if (! empty($companyInfoData['estbDt']) || ! empty($companyInfoData['estbDate'])) { $estbDt = $companyInfoData['estbDt'] ?? $companyInfoData['estbDate']; if (strlen($estbDt) === 8) { - $establishmentDate = substr($estbDt, 0, 4) . '-' . substr($estbDt, 4, 2) . '-' . substr($estbDt, 6, 2); + $establishmentDate = substr($estbDt, 0, 4).'-'.substr($estbDt, 4, 2).'-'.substr($estbDt, 6, 2); } } @@ -216,8 +217,8 @@ public static function createFromApiResponse( $ntsStatus = $ntsData['b_stt'] ?? null; $ntsStatusCode = $ntsData['b_stt_cd'] ?? null; $ntsTaxType = $ntsData['tax_type'] ?? null; - if (!empty($ntsData['end_dt']) && strlen($ntsData['end_dt']) === 8) { - $ntsClosureDate = substr($ntsData['end_dt'], 0, 4) . '-' . substr($ntsData['end_dt'], 4, 2) . '-' . substr($ntsData['end_dt'], 6, 2); + if (! empty($ntsData['end_dt']) && strlen($ntsData['end_dt']) === 8) { + $ntsClosureDate = substr($ntsData['end_dt'], 0, 4).'-'.substr($ntsData['end_dt'], 4, 2).'-'.substr($ntsData['end_dt'], 6, 2); } } @@ -230,7 +231,7 @@ public static function createFromApiResponse( if (isset($apiResult[$key]['success']) && $apiResult[$key]['success']) { $successCount++; } else { - $errors[] = $key . ': ' . ($apiResult[$key]['error'] ?? 'Unknown error'); + $errors[] = $key.': '.($apiResult[$key]['error'] ?? 'Unknown error'); } } diff --git a/app/Models/DocumentTemplate.php b/app/Models/DocumentTemplate.php index a4e517ac..56645b60 100644 --- a/app/Models/DocumentTemplate.php +++ b/app/Models/DocumentTemplate.php @@ -10,7 +10,7 @@ class DocumentTemplate extends Model { - use HasFactory, BelongsToTenant, SoftDeletes; + use BelongsToTenant, HasFactory, SoftDeletes; protected $fillable = [ 'tenant_id', diff --git a/app/Models/DocumentTemplateApprovalLine.php b/app/Models/DocumentTemplateApprovalLine.php index 0984f0e8..f9eb9209 100644 --- a/app/Models/DocumentTemplateApprovalLine.php +++ b/app/Models/DocumentTemplateApprovalLine.php @@ -27,4 +27,4 @@ public function template(): BelongsTo { return $this->belongsTo(DocumentTemplate::class, 'template_id'); } -} \ No newline at end of file +} diff --git a/app/Models/DocumentTemplateBasicField.php b/app/Models/DocumentTemplateBasicField.php index 4ea87e98..9a67184e 100644 --- a/app/Models/DocumentTemplateBasicField.php +++ b/app/Models/DocumentTemplateBasicField.php @@ -27,4 +27,4 @@ public function template(): BelongsTo { return $this->belongsTo(DocumentTemplate::class, 'template_id'); } -} \ No newline at end of file +} diff --git a/app/Models/DocumentTemplateColumn.php b/app/Models/DocumentTemplateColumn.php index ab1a0c67..5cc2a327 100644 --- a/app/Models/DocumentTemplateColumn.php +++ b/app/Models/DocumentTemplateColumn.php @@ -29,4 +29,4 @@ public function template(): BelongsTo { return $this->belongsTo(DocumentTemplate::class, 'template_id'); } -} \ No newline at end of file +} diff --git a/app/Models/DocumentTemplateSection.php b/app/Models/DocumentTemplateSection.php index b808c774..f2ae8db5 100644 --- a/app/Models/DocumentTemplateSection.php +++ b/app/Models/DocumentTemplateSection.php @@ -32,4 +32,4 @@ public function items(): HasMany return $this->hasMany(DocumentTemplateSectionItem::class, 'section_id') ->orderBy('sort_order'); } -} \ No newline at end of file +} diff --git a/app/Models/ESign/EsignContract.php b/app/Models/ESign/EsignContract.php index b524b7c5..e4611cc7 100644 --- a/app/Models/ESign/EsignContract.php +++ b/app/Models/ESign/EsignContract.php @@ -3,7 +3,6 @@ namespace App\Models\ESign; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; diff --git a/app/Models/ESign/EsignFieldTemplate.php b/app/Models/ESign/EsignFieldTemplate.php index 293c8e33..b2246479 100644 --- a/app/Models/ESign/EsignFieldTemplate.php +++ b/app/Models/ESign/EsignFieldTemplate.php @@ -3,8 +3,8 @@ namespace App\Models\ESign; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class EsignFieldTemplate extends Model { diff --git a/app/Models/Finance/BankAccount.php b/app/Models/Finance/BankAccount.php index e0932e2f..61c05824 100644 --- a/app/Models/Finance/BankAccount.php +++ b/app/Models/Finance/BankAccount.php @@ -86,14 +86,14 @@ public function getFormattedBalanceAttribute(): string $amount = abs($this->balance); if ($amount >= 100000000) { - return number_format($amount / 100000000, 1) . '억원'; + return number_format($amount / 100000000, 1).'억원'; } elseif ($amount >= 10000000) { - return number_format($amount / 10000000, 0) . '천만원'; + return number_format($amount / 10000000, 0).'천만원'; } elseif ($amount >= 10000) { - return number_format($amount / 10000, 0) . '만원'; + return number_format($amount / 10000, 0).'만원'; } - return number_format($amount) . '원'; + return number_format($amount).'원'; } /** @@ -106,7 +106,7 @@ public function getMaskedAccountNumberAttribute(): string return $number; } - return substr($number, 0, 3) . '-***-' . substr($number, -4); + return substr($number, 0, 3).'-***-'.substr($number, -4); } // ============================================================ diff --git a/app/Models/Finance/BankTransaction.php b/app/Models/Finance/BankTransaction.php index 625433e7..7d4915ad 100644 --- a/app/Models/Finance/BankTransaction.php +++ b/app/Models/Finance/BankTransaction.php @@ -18,7 +18,9 @@ class BankTransaction extends Model // 거래 유형 상수 public const TYPE_DEPOSIT = 'deposit'; // 입금 + public const TYPE_WITHDRAWAL = 'withdrawal'; // 출금 + public const TYPE_TRANSFER = 'transfer'; // 이체 protected $fillable = [ @@ -106,7 +108,8 @@ public function getIsWithdrawalAttribute(): bool public function getFormattedAmountAttribute(): string { $prefix = $this->is_deposit ? '+' : '-'; - return $prefix . number_format(abs($this->amount)) . '원'; + + return $prefix.number_format(abs($this->amount)).'원'; } /** @@ -114,7 +117,7 @@ public function getFormattedAmountAttribute(): string */ public function getFormattedBalanceAfterAttribute(): string { - return number_format($this->balance_after) . '원'; + return number_format($this->balance_after).'원'; } /** diff --git a/app/Models/Finance/ConsultingFee.php b/app/Models/Finance/ConsultingFee.php index 6a9c5203..93ece669 100644 --- a/app/Models/Finance/ConsultingFee.php +++ b/app/Models/Finance/ConsultingFee.php @@ -1,4 +1,5 @@ amount); if ($amount >= 100000000) { - return number_format($amount / 100000000, 1) . '억원'; + return number_format($amount / 100000000, 1).'억원'; } elseif ($amount >= 10000000) { - return number_format($amount / 10000000, 1) . '천만원'; + return number_format($amount / 10000000, 1).'천만원'; } elseif ($amount >= 10000) { - return number_format($amount / 10000, 0) . '만원'; + return number_format($amount / 10000, 0).'만원'; } - return number_format($amount) . '원'; + return number_format($amount).'원'; } /** diff --git a/app/Models/Finance/Income.php b/app/Models/Finance/Income.php index 367816c9..dc39051a 100644 --- a/app/Models/Finance/Income.php +++ b/app/Models/Finance/Income.php @@ -1,4 +1,5 @@ name; } -} \ No newline at end of file +} diff --git a/app/Models/Items/Item.php b/app/Models/Items/Item.php index 067d4e8d..f551a42f 100644 --- a/app/Models/Items/Item.php +++ b/app/Models/Items/Item.php @@ -29,12 +29,17 @@ class Item extends Model // 유형 상수 const TYPE_FG = 'FG'; // 완제품 + const TYPE_PT = 'PT'; // 부품 + const TYPE_SM = 'SM'; // 부자재 + const TYPE_RM = 'RM'; // 원자재 + const TYPE_CS = 'CS'; // 소모품 const PRODUCT_TYPES = ['FG', 'PT']; + const MATERIAL_TYPES = ['SM', 'RM', 'CS']; // ── 관계 ── @@ -73,10 +78,13 @@ public function scopeActive($query) public function scopeSearch($query, ?string $search) { - if (!$search) return $query; + if (! $search) { + return $query; + } + return $query->where(function ($q) use ($search) { $q->where('code', 'like', "%{$search}%") - ->orWhere('name', 'like', "%{$search}%"); + ->orWhere('name', 'like', "%{$search}%"); }); } diff --git a/app/Models/Juil/ConstructionSitePhotoRow.php b/app/Models/Juil/ConstructionSitePhotoRow.php index d553dc9a..b07ddab6 100644 --- a/app/Models/Juil/ConstructionSitePhotoRow.php +++ b/app/Models/Juil/ConstructionSitePhotoRow.php @@ -37,7 +37,7 @@ public function constructionSitePhoto(): BelongsTo public function hasPhoto(string $type): bool { - return !empty($this->{$type . '_photo_path'}); + return ! empty($this->{$type.'_photo_path'}); } public function getPhotoCount(): int diff --git a/app/Models/Juil/MeetingMinute.php b/app/Models/Juil/MeetingMinute.php index b2b405c1..9d524379 100644 --- a/app/Models/Juil/MeetingMinute.php +++ b/app/Models/Juil/MeetingMinute.php @@ -16,9 +16,13 @@ class MeetingMinute extends Model protected $table = 'meeting_minutes'; const STATUS_DRAFT = 'DRAFT'; + const STATUS_RECORDING = 'RECORDING'; + const STATUS_PROCESSING = 'PROCESSING'; + const STATUS_COMPLETED = 'COMPLETED'; + const STATUS_FAILED = 'FAILED'; protected $fillable = [ diff --git a/app/Models/NumberingRule.php b/app/Models/NumberingRule.php index 016049e8..9e91bbb9 100644 --- a/app/Models/NumberingRule.php +++ b/app/Models/NumberingRule.php @@ -30,9 +30,13 @@ class NumberingRule extends Model // SoftDeletes 없음 (DB에 deleted_at 컬럼 없음) → Hard Delete const DOC_QUOTE = 'quote'; + const DOC_ORDER = 'order'; + const DOC_SALE = 'sale'; + const DOC_WORK_ORDER = 'work_order'; + const DOC_MATERIAL_RECEIPT = 'material_receipt'; public static function documentTypes(): array @@ -68,8 +72,8 @@ public function getPreviewAttribute(): string 'static' => $segment['value'] ?? '', 'separator' => $segment['value'] ?? '', 'date' => now()->format($segment['format'] ?? 'ymd'), - 'param' => $segment['default'] ?? '{' . ($segment['key'] ?? '?') . '}', - 'mapping' => $segment['default'] ?? '{' . ($segment['key'] ?? '?') . '}', + 'param' => $segment['default'] ?? '{'.($segment['key'] ?? '?').'}', + 'mapping' => $segment['default'] ?? '{'.($segment['key'] ?? '?').'}', 'sequence' => str_pad('1', $this->sequence_padding, '0', STR_PAD_LEFT), default => '', }; diff --git a/app/Models/Products/CommonCode.php b/app/Models/Products/CommonCode.php index fccfb52f..6c2f2cb7 100644 --- a/app/Models/Products/CommonCode.php +++ b/app/Models/Products/CommonCode.php @@ -46,4 +46,4 @@ public static function getItemTypes(int $tenantId): array ->pluck('name', 'code') ->toArray(); } -} \ No newline at end of file +} diff --git a/app/Models/PushDeviceToken.php b/app/Models/PushDeviceToken.php index a298d6c1..999c5d29 100644 --- a/app/Models/PushDeviceToken.php +++ b/app/Models/PushDeviceToken.php @@ -61,17 +61,17 @@ public function getParsedOsVersionAttribute(): ?string // Android 버전 추출 if (preg_match('/Android\s+([\d.]+)/', $this->device_name, $matches)) { - return 'Android ' . $matches[1]; + return 'Android '.$matches[1]; } // iOS 버전 추출 if (preg_match('/iPhone\s+OS\s+([\d_]+)/', $this->device_name, $matches)) { - return 'iOS ' . str_replace('_', '.', $matches[1]); + return 'iOS '.str_replace('_', '.', $matches[1]); } // iPad 버전 추출 if (preg_match('/CPU\s+OS\s+([\d_]+)/', $this->device_name, $matches)) { - return 'iOS ' . str_replace('_', '.', $matches[1]); + return 'iOS '.str_replace('_', '.', $matches[1]); } return null; @@ -94,6 +94,7 @@ private function parseUserAgent(string $userAgent): string if (($pos = strpos($model, ' Build')) !== false) { $model = substr($model, 0, $pos); } + return $model ?: 'Android Device'; } diff --git a/app/Models/Sales/SalesCommission.php b/app/Models/Sales/SalesCommission.php index 663384ff..f1979bc4 100644 --- a/app/Models/Sales/SalesCommission.php +++ b/app/Models/Sales/SalesCommission.php @@ -51,14 +51,18 @@ class SalesCommission extends Model * 상태 상수 */ const STATUS_PENDING = 'pending'; + const STATUS_APPROVED = 'approved'; + const STATUS_PAID = 'paid'; + const STATUS_CANCELLED = 'cancelled'; /** * 입금 구분 상수 */ const PAYMENT_DEPOSIT = 'deposit'; + const PAYMENT_BALANCE = 'balance'; /** @@ -361,7 +365,7 @@ public function recordFirstPayment(?Carbon $paymentDate = null): bool */ public function recordFirstPartnerPaid(?Carbon $paidDate = null): bool { - if (!$this->first_payment_at) { + if (! $this->first_payment_at) { return false; // 1차 납입이 먼저 완료되어야 함 } @@ -385,7 +389,7 @@ public function recordSecondPayment(?Carbon $paymentDate = null): bool */ public function recordSecondPartnerPaid(?Carbon $paidDate = null): bool { - if (!$this->second_payment_at) { + if (! $this->second_payment_at) { return false; // 2차 납입이 먼저 완료되어야 함 } @@ -409,7 +413,7 @@ public function recordFirstSubscription(?Carbon $subscriptionDate = null): bool */ public function recordManagerPaid(?Carbon $paidDate = null): bool { - if (!$this->first_subscription_at) { + if (! $this->first_subscription_at) { return false; // 첫 구독료 입금이 먼저 완료되어야 함 } @@ -423,9 +427,10 @@ public function recordManagerPaid(?Carbon $paidDate = null): bool */ public function getFirstPartnerScheduledDateAttribute(): ?Carbon { - if (!$this->first_payment_at) { + if (! $this->first_payment_at) { return null; } + return Carbon::parse($this->first_payment_at)->addMonth()->day(10); } @@ -434,9 +439,10 @@ public function getFirstPartnerScheduledDateAttribute(): ?Carbon */ public function getSecondPartnerScheduledDateAttribute(): ?Carbon { - if (!$this->second_payment_at) { + if (! $this->second_payment_at) { return null; } + return Carbon::parse($this->second_payment_at)->addMonth()->day(10); } @@ -445,9 +451,10 @@ public function getSecondPartnerScheduledDateAttribute(): ?Carbon */ public function getManagerScheduledDateAttribute(): ?Carbon { - if (!$this->first_subscription_at) { + if (! $this->first_subscription_at) { return null; } + return Carbon::parse($this->first_subscription_at)->addMonth()->day(10); } @@ -480,7 +487,7 @@ public function isManagerPaid(): bool */ public function recordFirstReferrerPaid(?Carbon $paidDate = null): bool { - if (!$this->referrer_partner_id) { + if (! $this->referrer_partner_id) { return false; } @@ -494,7 +501,7 @@ public function recordFirstReferrerPaid(?Carbon $paidDate = null): bool */ public function recordSecondReferrerPaid(?Carbon $paidDate = null): bool { - if (!$this->referrer_partner_id) { + if (! $this->referrer_partner_id) { return false; } diff --git a/app/Models/Sales/SalesConsultation.php b/app/Models/Sales/SalesConsultation.php index 7d92c798..27ad86dc 100644 --- a/app/Models/Sales/SalesConsultation.php +++ b/app/Models/Sales/SalesConsultation.php @@ -2,7 +2,6 @@ namespace App\Models\Sales; -use App\Models\Sales\TenantProspect; use App\Models\Tenants\Tenant; use App\Models\User; use Illuminate\Database\Eloquent\Model; @@ -61,7 +60,9 @@ class SalesConsultation extends Model * 상담 유형 상수 */ const TYPE_TEXT = 'text'; + const TYPE_AUDIO = 'audio'; + const TYPE_FILE = 'file'; /** @@ -106,15 +107,15 @@ public static function createText(int $tenantId, string $scenarioType, ?int $ste /** * 음성 상담 기록 생성 * - * @param int $tenantId 테넌트 ID - * @param string $scenarioType 시나리오 타입 (sales/manager) - * @param int|null $stepId 단계 ID - * @param string $filePath 로컬 파일 경로 - * @param string $fileName 파일명 - * @param int $fileSize 파일 크기 - * @param string|null $transcript 음성 텍스트 변환 결과 - * @param int|null $duration 녹음 시간 (초) - * @param string|null $gcsUri GCS URI (본사 연구용 백업) + * @param int $tenantId 테넌트 ID + * @param string $scenarioType 시나리오 타입 (sales/manager) + * @param int|null $stepId 단계 ID + * @param string $filePath 로컬 파일 경로 + * @param string $fileName 파일명 + * @param int $fileSize 파일 크기 + * @param string|null $transcript 음성 텍스트 변환 결과 + * @param int|null $duration 녹음 시간 (초) + * @param string|null $gcsUri GCS URI (본사 연구용 백업) */ public static function createAudio( int $tenantId, @@ -185,7 +186,7 @@ public function deleteWithFile(): bool */ public function getFormattedDurationAttribute(): ?string { - if (!$this->duration) { + if (! $this->duration) { return null; } @@ -200,16 +201,16 @@ public function getFormattedDurationAttribute(): ?string */ public function getFormattedFileSizeAttribute(): ?string { - if (!$this->file_size) { + if (! $this->file_size) { return null; } if ($this->file_size < 1024) { - return $this->file_size . ' B'; + return $this->file_size.' B'; } elseif ($this->file_size < 1024 * 1024) { - return round($this->file_size / 1024, 1) . ' KB'; + return round($this->file_size / 1024, 1).' KB'; } else { - return round($this->file_size / (1024 * 1024), 1) . ' MB'; + return round($this->file_size / (1024 * 1024), 1).' MB'; } } diff --git a/app/Models/Sales/SalesManagerDocument.php b/app/Models/Sales/SalesManagerDocument.php index 40f32da3..f6a04278 100644 --- a/app/Models/Sales/SalesManagerDocument.php +++ b/app/Models/Sales/SalesManagerDocument.php @@ -76,14 +76,14 @@ public function getFormattedSizeAttribute(): string $bytes = $this->file_size; if ($bytes >= 1073741824) { - return number_format($bytes / 1073741824, 2) . ' GB'; + return number_format($bytes / 1073741824, 2).' GB'; } elseif ($bytes >= 1048576) { - return number_format($bytes / 1048576, 2) . ' MB'; + return number_format($bytes / 1048576, 2).' MB'; } elseif ($bytes >= 1024) { - return number_format($bytes / 1024, 2) . ' KB'; + return number_format($bytes / 1024, 2).' KB'; } - return $bytes . ' bytes'; + return $bytes.' bytes'; } /** @@ -123,7 +123,7 @@ public function existsInStorage(): bool */ public function getUrl(): ?string { - if (!$this->existsInStorage()) { + if (! $this->existsInStorage()) { return null; } @@ -135,7 +135,7 @@ public function getUrl(): ?string */ public function download() { - if (!$this->existsInStorage()) { + if (! $this->existsInStorage()) { abort(404, '파일을 찾을 수 없습니다.'); } diff --git a/app/Models/Sales/SalesPartner.php b/app/Models/Sales/SalesPartner.php index 06334b42..c18e3118 100644 --- a/app/Models/Sales/SalesPartner.php +++ b/app/Models/Sales/SalesPartner.php @@ -2,7 +2,6 @@ namespace App\Models\Sales; -use App\Models\Sales\SalesCommission; use App\Models\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -132,7 +131,7 @@ public static function generatePartnerCode(): string $sequence = $lastPartner ? (int) substr($lastPartner->partner_code, -4) + 1 : 1; - return $prefix . $year . str_pad($sequence, 4, '0', STR_PAD_LEFT); + return $prefix.$year.str_pad($sequence, 4, '0', STR_PAD_LEFT); } /** diff --git a/app/Models/Sales/SalesProduct.php b/app/Models/Sales/SalesProduct.php index 510cecd2..bee4bcaf 100644 --- a/app/Models/Sales/SalesProduct.php +++ b/app/Models/Sales/SalesProduct.php @@ -104,7 +104,7 @@ public function getCommissionAttribute(): float */ public function getFormattedDevelopmentFeeAttribute(): string { - return '₩' . number_format($this->development_fee); + return '₩'.number_format($this->development_fee); } /** @@ -112,7 +112,7 @@ public function getFormattedDevelopmentFeeAttribute(): string */ public function getFormattedRegistrationFeeAttribute(): string { - return '₩' . number_format($this->registration_fee); + return '₩'.number_format($this->registration_fee); } /** @@ -120,6 +120,6 @@ public function getFormattedRegistrationFeeAttribute(): string */ public function getFormattedSubscriptionFeeAttribute(): string { - return '₩' . number_format($this->subscription_fee); + return '₩'.number_format($this->subscription_fee); } } diff --git a/app/Models/Sales/SalesProspect.php b/app/Models/Sales/SalesProspect.php index d478cd79..83617884 100644 --- a/app/Models/Sales/SalesProspect.php +++ b/app/Models/Sales/SalesProspect.php @@ -119,8 +119,9 @@ public function getFormattedBusinessNoAttribute(): string { $bizNo = preg_replace('/[^0-9]/', '', $this->business_no); if (strlen($bizNo) === 10) { - return substr($bizNo, 0, 3) . '-' . substr($bizNo, 3, 2) . '-' . substr($bizNo, 5); + return substr($bizNo, 0, 3).'-'.substr($bizNo, 3, 2).'-'.substr($bizNo, 5); } + return $this->business_no ?? ''; } @@ -145,7 +146,7 @@ public function getTotalCommissionAttribute(): float */ public function hasBusinessCard(): bool { - return !empty($this->business_card_image); + return ! empty($this->business_card_image); } /** @@ -153,11 +154,11 @@ public function hasBusinessCard(): bool */ public function getBusinessCardUrlAttribute(): ?string { - if (!$this->hasBusinessCard()) { + if (! $this->hasBusinessCard()) { return null; } - return asset('storage/' . $this->business_card_image); + return asset('storage/'.$this->business_card_image); } /** @@ -165,7 +166,7 @@ public function getBusinessCardUrlAttribute(): ?string */ public function hasIdCard(): bool { - return !empty($this->id_card_image); + return ! empty($this->id_card_image); } /** @@ -173,11 +174,11 @@ public function hasIdCard(): bool */ public function getIdCardUrlAttribute(): ?string { - if (!$this->hasIdCard()) { + if (! $this->hasIdCard()) { return null; } - return asset('storage/' . $this->id_card_image); + return asset('storage/'.$this->id_card_image); } /** @@ -185,7 +186,7 @@ public function getIdCardUrlAttribute(): ?string */ public function hasBankbook(): bool { - return !empty($this->bankbook_image); + return ! empty($this->bankbook_image); } /** @@ -193,10 +194,10 @@ public function hasBankbook(): bool */ public function getBankbookUrlAttribute(): ?string { - if (!$this->hasBankbook()) { + if (! $this->hasBankbook()) { return null; } - return asset('storage/' . $this->bankbook_image); + return asset('storage/'.$this->bankbook_image); } } diff --git a/app/Models/Sales/SalesProspectConsultation.php b/app/Models/Sales/SalesProspectConsultation.php index 639cbde9..c8e05ea9 100644 --- a/app/Models/Sales/SalesProspectConsultation.php +++ b/app/Models/Sales/SalesProspectConsultation.php @@ -86,6 +86,6 @@ public function getAttachmentCountAttribute(): int */ public function getHasAudioAttribute(): bool { - return !empty($this->audio_file_path); + return ! empty($this->audio_file_path); } } diff --git a/app/Models/Sales/SalesProspectProduct.php b/app/Models/Sales/SalesProspectProduct.php index 2743c182..eeddb43e 100644 --- a/app/Models/Sales/SalesProspectProduct.php +++ b/app/Models/Sales/SalesProspectProduct.php @@ -78,6 +78,7 @@ public function getApprovalStatusAttribute(): string if ($this->join_approved) { return '가입승인'; } + return '대기중'; } @@ -92,6 +93,7 @@ public function getApprovalColorAttribute(): string if ($this->join_approved) { return 'bg-blue-100 text-blue-800'; } + return 'bg-gray-100 text-gray-800'; } } diff --git a/app/Models/Sales/SalesScenarioChecklist.php b/app/Models/Sales/SalesScenarioChecklist.php index 7b9a4a0d..6f61e3c7 100644 --- a/app/Models/Sales/SalesScenarioChecklist.php +++ b/app/Models/Sales/SalesScenarioChecklist.php @@ -93,7 +93,7 @@ public static function toggle(int $tenantId, string $scenarioType, int $stepId, ]); // 새 레코드인 경우 필수 필드 설정 - if (!$checklist->exists) { + if (! $checklist->exists) { $checklist->user_id = $currentUserId; $checklist->checkpoint_index = 0; // 기본값 } @@ -121,7 +121,7 @@ public static function toggleByProspect(int $prospectId, string $scenarioType, i ]); // 새 레코드인 경우 필수 필드 설정 - if (!$checklist->exists) { + if (! $checklist->exists) { $checklist->user_id = $currentUserId; $checklist->checkpoint_index = 0; } @@ -305,8 +305,7 @@ public function scopeChecked($query) /** * 간단한 진행률 계산 (전체 체크포인트 수 기준) * - * @param int $tenantId - * @param string $scenarioType 'sales' 또는 'manager' + * @param string $scenarioType 'sales' 또는 'manager' * @return array ['completed' => 완료 수, 'total' => 전체 수, 'percentage' => 백분율] */ public static function getSimpleProgress(int $tenantId, string $scenarioType): array diff --git a/app/Models/Sales/SalesTenantManagement.php b/app/Models/Sales/SalesTenantManagement.php index b812cae7..66ec5fd8 100644 --- a/app/Models/Sales/SalesTenantManagement.php +++ b/app/Models/Sales/SalesTenantManagement.php @@ -99,11 +99,17 @@ class SalesTenantManagement extends Model * 상태 상수 */ const STATUS_PROSPECT = 'prospect'; + const STATUS_APPROACH = 'approach'; + const STATUS_NEGOTIATION = 'negotiation'; + const STATUS_CONTRACTED = 'contracted'; + const STATUS_ONBOARDING = 'onboarding'; + const STATUS_ACTIVE = 'active'; + const STATUS_CHURNED = 'churned'; /** @@ -123,12 +129,19 @@ class SalesTenantManagement extends Model * 본사 진행 상태 상수 */ const HQ_STATUS_PENDING = 'pending'; // 대기 + const HQ_STATUS_REVIEW = 'review'; // 검토 + const HQ_STATUS_PLANNING = 'planning'; // 기획안작성 + const HQ_STATUS_CODING = 'coding'; // 개발코드작성 + const HQ_STATUS_DEV_TEST = 'dev_test'; // 개발테스트 + const HQ_STATUS_DEV_DONE = 'dev_done'; // 개발완료 + const HQ_STATUS_INT_TEST = 'int_test'; // 통합테스트 + const HQ_STATUS_HANDOVER = 'handover'; // 인계 /** @@ -163,7 +176,9 @@ class SalesTenantManagement extends Model * 수당 지급 상태 상수 */ const INCENTIVE_PENDING = 'pending'; // 대기 + const INCENTIVE_ELIGIBLE = 'eligible'; // 지급대상 + const INCENTIVE_PAID = 'paid'; // 지급완료 /** diff --git a/app/Models/Sales/TenantProspect.php b/app/Models/Sales/TenantProspect.php index bdb38fbc..4e52b103 100644 --- a/app/Models/Sales/TenantProspect.php +++ b/app/Models/Sales/TenantProspect.php @@ -19,11 +19,15 @@ class TenantProspect extends Model protected $table = 'tenant_prospects'; public const STATUS_ACTIVE = 'active'; // 영업권 유효 + public const STATUS_EXPIRED = 'expired'; // 영업권 만료 + public const STATUS_CONVERTED = 'converted'; // 테넌트 전환 완료 + public const STATUS_COMPLETED = 'completed'; // 영업 완료 public const VALIDITY_MONTHS = 2; // 영업권 유효기간 (개월) + public const COOLDOWN_MONTHS = 1; // 재등록 대기 기간 (개월) protected $fillable = [ @@ -179,7 +183,7 @@ public function getStatusColorAttribute(): string */ public function getRemainingDaysAttribute(): int { - if (!$this->isActive()) { + if (! $this->isActive()) { return 0; } @@ -191,7 +195,7 @@ public function getRemainingDaysAttribute(): int */ public function getBusinessCardUrlAttribute(): ?string { - if (!$this->business_card_path) { + if (! $this->business_card_path) { return null; } @@ -219,7 +223,7 @@ public function hasIdCard(): bool */ public function getIdCardUrlAttribute(): ?string { - if (!$this->id_card_path) { + if (! $this->id_card_path) { return null; } @@ -239,7 +243,7 @@ public function hasBankbook(): bool */ public function getBankbookUrlAttribute(): ?string { - if (!$this->bankbook_path) { + if (! $this->bankbook_path) { return null; } diff --git a/app/Models/Stats/StatAlert.php b/app/Models/Stats/StatAlert.php index ab3e6df7..c14eb1f7 100644 --- a/app/Models/Stats/StatAlert.php +++ b/app/Models/Stats/StatAlert.php @@ -74,8 +74,8 @@ public function getDomainLabelAttribute(): string public function getAiAnalysisSummaryAttribute(): string { $lines = []; - $lines[] = "=== SAM 시스템 알림 분석 요청 ==="; - $lines[] = ""; + $lines[] = '=== SAM 시스템 알림 분석 요청 ==='; + $lines[] = ''; $lines[] = "■ 심각도: {$this->severity} ({$this->severity_label})"; $lines[] = "■ 도메인: {$this->domain} ({$this->domain_label})"; $lines[] = "■ 알림 유형: {$this->alert_type}"; @@ -83,20 +83,20 @@ public function getAiAnalysisSummaryAttribute(): string $lines[] = "■ 발생 시간: {$this->created_at?->format('Y-m-d H:i:s')}"; if ($this->message) { - $lines[] = ""; - $lines[] = "■ 상세 메시지:"; + $lines[] = ''; + $lines[] = '■ 상세 메시지:'; $lines[] = $this->message; } if ($this->current_value || $this->threshold_value) { - $lines[] = ""; + $lines[] = ''; $lines[] = "■ 현재값: {$this->current_value}"; $lines[] = "■ 임계값: {$this->threshold_value}"; } - $lines[] = ""; - $lines[] = "위 시스템 알림의 원인과 해결 방법을 분석해주세요."; + $lines[] = ''; + $lines[] = '위 시스템 알림의 원인과 해결 방법을 분석해주세요.'; return implode("\n", $lines); } -} \ No newline at end of file +} diff --git a/app/Models/System/Schedule.php b/app/Models/System/Schedule.php index 72568aff..04b88f6d 100644 --- a/app/Models/System/Schedule.php +++ b/app/Models/System/Schedule.php @@ -48,9 +48,13 @@ class Schedule extends Model ]; public const TYPE_EVENT = 'event'; + public const TYPE_MEETING = 'meeting'; + public const TYPE_INTERVIEW = 'interview'; + public const TYPE_NOTICE = 'notice'; + public const TYPE_OTHER = 'other'; public const TYPES = [ diff --git a/app/Models/Tenants/TenantSetting.php b/app/Models/Tenants/TenantSetting.php index b52b9775..1158d74a 100644 --- a/app/Models/Tenants/TenantSetting.php +++ b/app/Models/Tenants/TenantSetting.php @@ -33,4 +33,4 @@ class TenantSetting extends Model protected $casts = [ 'setting_value' => 'array', ]; -} \ No newline at end of file +} diff --git a/app/Models/TutorialVideo.php b/app/Models/TutorialVideo.php index 5fa879d3..8213cef1 100644 --- a/app/Models/TutorialVideo.php +++ b/app/Models/TutorialVideo.php @@ -42,12 +42,19 @@ public function user() } const STATUS_PENDING = 'pending'; + const STATUS_ANALYZING = 'analyzing'; + const STATUS_GENERATING_SLIDES = 'generating_slides'; + const STATUS_GENERATING_TTS = 'generating_tts'; + const STATUS_GENERATING_BGM = 'generating_bgm'; + const STATUS_ASSEMBLING = 'assembling'; + const STATUS_COMPLETED = 'completed'; + const STATUS_FAILED = 'failed'; public function updateProgress(string $status, int $progress, string $step): void diff --git a/app/Models/VehicleLog.php b/app/Models/VehicleLog.php index 44d13968..dedba5c4 100644 --- a/app/Models/VehicleLog.php +++ b/app/Models/VehicleLog.php @@ -4,8 +4,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\SoftDeletes; class VehicleLog extends Model { diff --git a/app/Models/VehicleMaintenance.php b/app/Models/VehicleMaintenance.php index 48dc64bd..070a9408 100644 --- a/app/Models/VehicleMaintenance.php +++ b/app/Models/VehicleMaintenance.php @@ -4,8 +4,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\SoftDeletes; class VehicleMaintenance extends Model { diff --git a/app/Services/AiVoiceRecordingService.php b/app/Services/AiVoiceRecordingService.php index bd9339fd..2b064d75 100644 --- a/app/Services/AiVoiceRecordingService.php +++ b/app/Services/AiVoiceRecordingService.php @@ -109,7 +109,7 @@ public function processAudio(AiVoiceRecording $recording, string $audioBase64, i Log::info('AiVoiceRecording STT 결과', ['recording_id' => $recording->id, 'transcript_length' => strlen($transcript ?? ''), 'transcript_preview' => mb_substr($transcript ?? '', 0, 100)]); if (! $transcript) { - throw new \Exception('음성 인식 실패: STT 결과가 비어있습니다 (transcript=' . var_export($transcript, true) . ')'); + throw new \Exception('음성 인식 실패: STT 결과가 비어있습니다 (transcript='.var_export($transcript, true).')'); } // STT 토큰 사용량 기록 @@ -154,7 +154,7 @@ public function processUploadedFile(AiVoiceRecording $recording, UploadedFile $f // 임시 저장 $tempPath = $file->store('temp', 'local'); - $fullPath = storage_path('app/' . $tempPath); + $fullPath = storage_path('app/'.$tempPath); // 파일 크기로 대략적인 재생 시간 추정 (12KB/초 기준) $fileSize = $file->getSize(); @@ -240,6 +240,7 @@ private function analyzeWithGemini(string $transcript): ?string if (! $config) { Log::warning('Gemini API 설정이 없습니다.'); + return null; } @@ -279,19 +280,21 @@ private function callVertexAiApi(AiConfig $config, string $prompt): ?string if (! $projectId) { Log::error('Vertex AI 프로젝트 ID가 설정되지 않았습니다.'); + return null; } $accessToken = $this->getAccessToken($config); if (! $accessToken) { Log::error('Google Cloud 인증 실패'); + return null; } $url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent"; return $this->callGeminiApi($url, $prompt, [ - 'Authorization' => 'Bearer ' . $accessToken, + 'Authorization' => 'Bearer '.$accessToken, 'Content-Type' => 'application/json', ], true); } @@ -329,6 +332,7 @@ private function callGeminiApi(string $url, string $prompt, array $headers, bool 'status' => $response->status(), 'body' => $response->body(), ]); + return null; } @@ -340,6 +344,7 @@ private function callGeminiApi(string $url, string $prompt, array $headers, bool return $result['candidates'][0]['content']['parts'][0]['text'] ?? null; } catch (\Exception $e) { Log::error('Gemini API 예외', ['error' => $e->getMessage()]); + return null; } } @@ -367,12 +372,14 @@ private function getAccessToken(AiConfig $config): ?string if (! $serviceAccountPath) { Log::error('Service account file not found', ['tried_paths' => $possiblePaths]); + return null; } $serviceAccount = json_decode(file_get_contents($serviceAccountPath), true); if (! $serviceAccount) { Log::error('Service account JSON parse failed'); + return null; } @@ -389,11 +396,12 @@ private function getAccessToken(AiConfig $config): ?string $privateKey = openssl_pkey_get_private($serviceAccount['private_key']); if (! $privateKey) { Log::error('Failed to load private key'); + return null; } - openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); - $jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature); + openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); + $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature); try { $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ @@ -409,9 +417,11 @@ private function getAccessToken(AiConfig $config): ?string 'status' => $response->status(), 'body' => $response->body(), ]); + return null; } catch (\Exception $e) { Log::error('OAuth token request exception', ['error' => $e->getMessage()]); + return null; } } diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php index 5503e9e5..9e95ac06 100644 --- a/app/Services/AuthService.php +++ b/app/Services/AuthService.php @@ -23,9 +23,9 @@ public function __construct( * - 이메일 로그인: 본사(HQ) 테넌트 소속만 허용 * - 아이디 로그인: 본사(HQ) 테넌트 소속만 허용 * - * @param array $credentials 인증 정보 ['email' => ..., 'password' => ...] 또는 ['user_id' => ..., 'password' => ...] - * @param bool $remember 로그인 유지 여부 - * @param string $loginField 로그인 필드 ('email' 또는 'user_id') + * @param array $credentials 인증 정보 ['email' => ..., 'password' => ...] 또는 ['user_id' => ..., 'password' => ...] + * @param bool $remember 로그인 유지 여부 + * @param string $loginField 로그인 필드 ('email' 또는 'user_id') */ public function login(array $credentials, bool $remember = false, string $loginField = 'email'): bool { @@ -37,12 +37,14 @@ public function login(array $credentials, bool $remember = false, string $loginF if (! $user) { $this->loginError = '아이디 또는 비밀번호가 올바르지 않습니다.'; + return false; } // 아이디 로그인은 본사 소속만 허용 if (! $user->belongsToHQ()) { $this->loginError = '아이디 로그인은 본사 소속 직원만 가능합니다.'; + return false; } } diff --git a/app/Services/BankAccountService.php b/app/Services/BankAccountService.php index 8f681f43..b68ddf06 100644 --- a/app/Services/BankAccountService.php +++ b/app/Services/BankAccountService.php @@ -347,8 +347,8 @@ public function getSummary(): array return [ 'total_accounts' => $accounts->count(), 'total_balance' => $accounts->sum('balance'), - 'formatted_total_balance' => number_format($accounts->sum('balance')) . '원', - 'by_bank' => $accounts->groupBy('bank_name')->map(fn($group) => [ + 'formatted_total_balance' => number_format($accounts->sum('balance')).'원', + 'by_bank' => $accounts->groupBy('bank_name')->map(fn ($group) => [ 'count' => $group->count(), 'total' => $group->sum('balance'), ]), diff --git a/app/Services/Barobill/BarobillBillingService.php b/app/Services/Barobill/BarobillBillingService.php index f139503b..041201e9 100644 --- a/app/Services/Barobill/BarobillBillingService.php +++ b/app/Services/Barobill/BarobillBillingService.php @@ -3,11 +3,9 @@ namespace App\Services\Barobill; use App\Models\Barobill\BarobillBillingRecord; -use App\Models\Barobill\BarobillMember; use App\Models\Barobill\BarobillMonthlySummary; use App\Models\Barobill\BarobillSubscription; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; /** @@ -54,7 +52,7 @@ public function saveSubscription(int $memberId, string $serviceType, array $data public function cancelSubscription(int $subscriptionId): bool { $subscription = BarobillSubscription::find($subscriptionId); - if (!$subscription) { + if (! $subscription) { return false; } @@ -74,7 +72,7 @@ public function cancelSubscription(int $subscriptionId): bool public function processMonthlyBilling(?string $billingMonth = null): array { $billingMonth = $billingMonth ?? now()->format('Y-m'); - $billedAt = Carbon::parse($billingMonth . '-01'); + $billedAt = Carbon::parse($billingMonth.'-01'); $results = [ 'billing_month' => $billingMonth, @@ -99,6 +97,7 @@ public function processMonthlyBilling(?string $billingMonth = null): array if ($exists) { $results['skipped']++; + continue; } @@ -112,7 +111,7 @@ public function processMonthlyBilling(?string $billingMonth = null): array 'unit_price' => $subscription->monthly_fee, 'total_amount' => $subscription->monthly_fee, 'billed_at' => $billedAt, - 'description' => BarobillSubscription::SERVICE_TYPES[$subscription->service_type] . ' 월정액', + 'description' => BarobillSubscription::SERVICE_TYPES[$subscription->service_type].' 월정액', ]); $results['processed']++; @@ -156,7 +155,7 @@ public function recordUsage(int $memberId, string $serviceType, int $quantity, ? 'unit_price' => $unitPrice, 'total_amount' => $quantity * $unitPrice, 'billed_at' => now()->toDateString(), - 'description' => BarobillBillingRecord::SERVICE_TYPES[$serviceType] . ' ' . $quantity . '건', + 'description' => BarobillBillingRecord::SERVICE_TYPES[$serviceType].' '.$quantity.'건', ] ); @@ -247,6 +246,7 @@ public function getYearlyTrend(int $year, ?int $tenantId = null): array $billingMonth = sprintf('%d-%02d', $year, $m); $months[$billingMonth] = $this->getMonthlyTotal($billingMonth, $tenantId); } + return $months; } } diff --git a/app/Services/Barobill/BarobillUsageService.php b/app/Services/Barobill/BarobillUsageService.php index 8bcc2534..e1b49aec 100644 --- a/app/Services/Barobill/BarobillUsageService.php +++ b/app/Services/Barobill/BarobillUsageService.php @@ -6,7 +6,6 @@ use App\Models\Barobill\BarobillPricingPolicy; use App\Models\Barobill\HometaxInvoice; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Collection; /** * 바로빌 사용량 집계 서비스 @@ -31,11 +30,10 @@ public function __construct(BarobillService $barobillService) /** * 전체 테넌트 사용량 목록 조회 * - * @param string $startDate 시작일 (YYYYMMDD) - * @param string $endDate 종료일 (YYYYMMDD) - * @param int|null $tenantId 특정 테넌트만 조회 (null이면 전체) - * @param bool $productionOnly 운영 모드만 조회 (기본값: true) - * @return array + * @param string $startDate 시작일 (YYYYMMDD) + * @param string $endDate 종료일 (YYYYMMDD) + * @param int|null $tenantId 특정 테넌트만 조회 (null이면 전체) + * @param bool $productionOnly 운영 모드만 조회 (기본값: true) */ public function getUsageList(string $startDate, string $endDate, ?int $tenantId = null, bool $productionOnly = true): array { @@ -66,10 +64,8 @@ public function getUsageList(string $startDate, string $endDate, ?int $tenantId /** * 단일 회원사 사용량 조회 * - * @param BarobillMember $member - * @param string $startDate 시작일 (YYYYMMDD) - * @param string $endDate 종료일 (YYYYMMDD) - * @return array + * @param string $startDate 시작일 (YYYYMMDD) + * @param string $endDate 종료일 (YYYYMMDD) */ public function getMemberUsage(BarobillMember $member, string $startDate, string $endDate): array { @@ -110,8 +106,7 @@ public function getMemberUsage(BarobillMember $member, string $startDate, string /** * 통계 데이터 집계 * - * @param array $usageList 사용량 목록 - * @return array + * @param array $usageList 사용량 목록 */ public function aggregateStats(array $usageList): array { @@ -150,8 +145,8 @@ protected function getTaxInvoiceCount(BarobillMember $member, string $startDate, { try { // YYYYMMDD -> YYYY-MM-DD 형식 변환 - $start = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); - $end = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); + $start = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2); + $end = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2); return HometaxInvoice::where('tenant_id', $member->tenant_id) ->where('invoice_type', 'sales') // 매출 (발행한 세금계산서) @@ -197,10 +192,12 @@ protected function getBankAccountCount(BarobillMember $member, string $startDate // GetBankAccountEx 응답: BankAccount 또는 BankAccountEx 배열 if (isset($result['data']->BankAccount)) { $accounts = $result['data']->BankAccount; + return is_array($accounts) ? count($accounts) : 1; } if (isset($result['data']->BankAccountEx)) { $accounts = $result['data']->BankAccountEx; + return is_array($accounts) ? count($accounts) : 1; } } @@ -244,6 +241,7 @@ protected function getCardCount(BarobillMember $member, string $startDate, strin // GetCardEx2 응답: CardEx 배열 if (isset($result['data']->CardEx)) { $cards = $result['data']->CardEx; + return is_array($cards) ? count($cards) : 1; } } @@ -268,8 +266,8 @@ protected function getHometaxCount(BarobillMember $member, string $startDate, st { try { // YYYYMMDD -> YYYY-MM-DD 형식 변환 - $start = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2); - $end = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2); + $start = substr($startDate, 0, 4).'-'.substr($startDate, 4, 2).'-'.substr($startDate, 6, 2); + $end = substr($endDate, 0, 4).'-'.substr($endDate, 4, 2).'-'.substr($endDate, 6, 2); return HometaxInvoice::where('tenant_id', $member->tenant_id) ->whereBetween('write_date', [$start, $end]) @@ -348,7 +346,7 @@ public static function getPriceInfo(): array // 없는 정책은 기본값으로 채움 foreach ($defaults as $type => $default) { - if (!isset($priceInfo[$type])) { + if (! isset($priceInfo[$type])) { $priceInfo[$type] = $default; } } @@ -359,15 +357,15 @@ public static function getPriceInfo(): array /** * 사용량에 따른 과금액 계산 * - * @param string $serviceType 서비스 유형 - * @param int $usageCount 사용량/등록 수 + * @param string $serviceType 서비스 유형 + * @param int $usageCount 사용량/등록 수 * @return array ['free_count' => int, 'billable_count' => int, 'billable_amount' => int] */ public static function calculateBillingByPolicy(string $serviceType, int $usageCount): array { $policy = BarobillPricingPolicy::getByServiceType($serviceType); - if (!$policy) { + if (! $policy) { // 기본 정책이 없으면 과금 없음 return [ 'free_count' => $usageCount, diff --git a/app/Services/Barobill/HometaxSyncService.php b/app/Services/Barobill/HometaxSyncService.php index 0fd01f37..6cb02fe1 100644 --- a/app/Services/Barobill/HometaxSyncService.php +++ b/app/Services/Barobill/HometaxSyncService.php @@ -15,9 +15,9 @@ class HometaxSyncService /** * API 응답 데이터를 로컬 DB에 동기화 * - * @param array $invoices API에서 받은 세금계산서 목록 - * @param int $tenantId 테넌트 ID - * @param string $invoiceType 'sales' 또는 'purchase' + * @param array $invoices API에서 받은 세금계산서 목록 + * @param int $tenantId 테넌트 ID + * @param string $invoiceType 'sales' 또는 'purchase' * @return array 동기화 결과 ['inserted' => int, 'updated' => int, 'failed' => int] */ public function syncInvoices(array $invoices, int $tenantId, string $invoiceType): array @@ -40,6 +40,7 @@ public function syncInvoices(array $invoices, int $tenantId, string $invoiceType // 국세청승인번호가 없으면 스킵 if (empty($apiData['ntsConfirmNum'])) { $result['failed']++; + continue; } @@ -103,13 +104,12 @@ public function syncInvoices(array $invoices, int $tenantId, string $invoiceType /** * 로컬 DB에서 세금계산서 목록 조회 * - * @param int $tenantId 테넌트 ID - * @param string $invoiceType 'sales' 또는 'purchase' - * @param string $startDate 시작일 (Y-m-d) - * @param string $endDate 종료일 (Y-m-d) - * @param string $dateType 날짜 타입 ('write', 'issue', 'send') - * @param string|null $searchCorp 거래처 검색어 - * @return array + * @param int $tenantId 테넌트 ID + * @param string $invoiceType 'sales' 또는 'purchase' + * @param string $startDate 시작일 (Y-m-d) + * @param string $endDate 종료일 (Y-m-d) + * @param string $dateType 날짜 타입 ('write', 'issue', 'send') + * @param string|null $searchCorp 거래처 검색어 */ public function getLocalInvoices( int $tenantId, @@ -123,7 +123,7 @@ public function getLocalInvoices( ->where('invoice_type', $invoiceType) ->period($startDate, $endDate, $dateType); - if (!empty($searchCorp)) { + if (! empty($searchCorp)) { $query->searchCorp($searchCorp, $invoiceType); } @@ -215,11 +215,12 @@ public function toggleChecked(int $id, int $tenantId): bool ->where('tenant_id', $tenantId) ->first(); - if (!$invoice) { + if (! $invoice) { return false; } - $invoice->is_checked = !$invoice->is_checked; + $invoice->is_checked = ! $invoice->is_checked; + return $invoice->save(); } diff --git a/app/Services/BoardService.php b/app/Services/BoardService.php index 8dd0d1ef..314b086f 100644 --- a/app/Services/BoardService.php +++ b/app/Services/BoardService.php @@ -424,7 +424,7 @@ public function getAllBoards(array $filters = [], int $perPage = 15): LengthAwar // 본사: 시스템 게시판 + 본사 테넌트 게시판 $query->where(function ($q) use ($selectedTenantId) { $q->where('is_system', true) - ->orWhere('tenant_id', $selectedTenantId); + ->orWhere('tenant_id', $selectedTenantId); }); } else { // 일반 테넌트: 해당 테넌트 게시판만 (시스템 게시판 제외) @@ -468,7 +468,7 @@ public function getAllBoards(array $filters = [], int $perPage = 15): LengthAwar $sortBy = $filters['sort_by'] ?? 'id'; $sortDirection = $filters['sort_direction'] ?? 'desc'; $query->orderBy('is_system', 'desc') // 시스템 게시판 우선 - ->orderBy($sortBy, $sortDirection); + ->orderBy($sortBy, $sortDirection); return $query->paginate($perPage); } diff --git a/app/Services/BusinessCardOcrService.php b/app/Services/BusinessCardOcrService.php index 0fb16654..b73f3af3 100644 --- a/app/Services/BusinessCardOcrService.php +++ b/app/Services/BusinessCardOcrService.php @@ -38,13 +38,13 @@ private function callVertexAiApi(AiConfig $config, string $base64Image): array $projectId = $config->getProjectId(); $region = $config->getRegion(); - if (!$projectId) { + if (! $projectId) { throw new \RuntimeException('Vertex AI 프로젝트 ID가 설정되지 않았습니다.'); } // 액세스 토큰 가져오기 $accessToken = $this->getAccessToken($config); - if (!$accessToken) { + if (! $accessToken) { throw new \RuntimeException('Google Cloud 인증 실패'); } @@ -52,7 +52,7 @@ private function callVertexAiApi(AiConfig $config, string $base64Image): array $url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent"; return $this->callGeminiApi($url, $base64Image, [ - 'Authorization' => 'Bearer ' . $accessToken, + 'Authorization' => 'Bearer '.$accessToken, 'Content-Type' => 'application/json', ], true); // Vertex AI } @@ -127,7 +127,7 @@ private function callGeminiApi(string $url, string $base64Image, array $headers, 'status' => $response->status(), 'body' => $response->body(), ]); - throw new \RuntimeException('AI API 호출 실패: ' . $response->status()); + throw new \RuntimeException('AI API 호출 실패: '.$response->status()); } $result = $response->json(); @@ -178,14 +178,16 @@ private function getAccessToken(AiConfig $config): ?string } } - if (!$serviceAccountPath) { + if (! $serviceAccountPath) { Log::error('Service account file not found', ['tried_paths' => $possiblePaths]); + return null; } $serviceAccount = json_decode(file_get_contents($serviceAccountPath), true); - if (!$serviceAccount) { + if (! $serviceAccount) { Log::error('Service account JSON parse failed'); + return null; } @@ -201,13 +203,14 @@ private function getAccessToken(AiConfig $config): ?string ])); $privateKey = openssl_pkey_get_private($serviceAccount['private_key']); - if (!$privateKey) { + if (! $privateKey) { Log::error('Failed to load private key'); + return null; } - openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); - $jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature); + openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); + $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature); // OAuth 토큰 요청 try { @@ -218,6 +221,7 @@ private function getAccessToken(AiConfig $config): ?string if ($response->successful()) { $data = $response->json(); + return $data['access_token'] ?? null; } @@ -225,9 +229,11 @@ private function getAccessToken(AiConfig $config): ?string 'status' => $response->status(), 'body' => $response->body(), ]); + return null; } catch (\Exception $e) { Log::error('OAuth token request exception', ['error' => $e->getMessage()]); + return null; } } @@ -245,7 +251,7 @@ private function base64UrlEncode(string $data): string */ private function buildPrompt(): string { - return <<orderBy('work_date', 'desc') ->orderBy('id', 'desc'); - if (!empty($params['search'])) { + if (! empty($params['search'])) { $search = $params['search']; $query->where(function ($q) use ($search) { $q->where('site_name', 'like', "%{$search}%") @@ -29,11 +29,11 @@ public function getList(array $params): LengthAwarePaginator }); } - if (!empty($params['date_from'])) { + if (! empty($params['date_from'])) { $query->where('work_date', '>=', $params['date_from']); } - if (!empty($params['date_to'])) { + if (! empty($params['date_to'])) { $query->where('work_date', '<=', $params['date_to']); } @@ -60,7 +60,7 @@ public function create(array $data): ConstructionSitePhoto public function uploadPhoto(ConstructionSitePhotoRow $row, $file, string $type): bool { - if (!in_array($type, ['before', 'during', 'after'])) { + if (! in_array($type, ['before', 'during', 'after'])) { return false; } @@ -70,7 +70,7 @@ public function uploadPhoto(ConstructionSitePhotoRow $row, $file, string $type): $objectName = "construction-site-photos/{$photo->tenant_id}/{$photo->id}/{$row->id}_{$timestamp}_{$type}.{$extension}"; // 기존 사진이 있으면 GCS에서 삭제 - $oldPath = $row->{$type . '_photo_path'}; + $oldPath = $row->{$type.'_photo_path'}; if ($oldPath) { $this->googleCloudService->deleteFromStorage($oldPath); } @@ -79,7 +79,7 @@ public function uploadPhoto(ConstructionSitePhotoRow $row, $file, string $type): $tempPath = $file->getRealPath(); $result = $this->googleCloudService->uploadToStorage($tempPath, $objectName); - if (!$result) { + if (! $result) { Log::error('ConstructionSitePhoto: GCS 업로드 실패', [ 'photo_id' => $photo->id, 'row_id' => $row->id, @@ -90,9 +90,9 @@ public function uploadPhoto(ConstructionSitePhotoRow $row, $file, string $type): } $row->update([ - $type . '_photo_path' => $objectName, - $type . '_photo_gcs_uri' => $result['uri'], - $type . '_photo_size' => $result['size'], + $type.'_photo_path' => $objectName, + $type.'_photo_gcs_uri' => $result['uri'], + $type.'_photo_size' => $result['size'], ]); AiTokenHelper::saveGcsStorageUsage('공사현장사진대지-GCS저장', $result['size']); @@ -116,7 +116,7 @@ public function delete(ConstructionSitePhoto $photo): bool // rows 순회하여 모든 GCS 파일 삭제 foreach ($photo->rows as $row) { foreach (['before', 'during', 'after'] as $type) { - $path = $row->{$type . '_photo_path'}; + $path = $row->{$type.'_photo_path'}; if ($path) { $this->googleCloudService->deleteFromStorage($path); } @@ -128,19 +128,19 @@ public function delete(ConstructionSitePhoto $photo): bool public function deletePhotoByType(ConstructionSitePhotoRow $row, string $type): bool { - if (!in_array($type, ['before', 'during', 'after'])) { + if (! in_array($type, ['before', 'during', 'after'])) { return false; } - $path = $row->{$type . '_photo_path'}; + $path = $row->{$type.'_photo_path'}; if ($path) { $this->googleCloudService->deleteFromStorage($path); } $row->update([ - $type . '_photo_path' => null, - $type . '_photo_gcs_uri' => null, - $type . '_photo_size' => null, + $type.'_photo_path' => null, + $type.'_photo_gcs_uri' => null, + $type.'_photo_size' => null, ]); return true; @@ -157,7 +157,7 @@ public function deleteRow(ConstructionSitePhotoRow $row): bool { // 행의 GCS 파일 삭제 foreach (['before', 'during', 'after'] as $type) { - $path = $row->{$type . '_photo_path'}; + $path = $row->{$type.'_photo_path'}; if ($path) { $this->googleCloudService->deleteFromStorage($path); } diff --git a/app/Services/Coocon/CooconService.php b/app/Services/Coocon/CooconService.php index 476ddcd9..30988ac1 100644 --- a/app/Services/Coocon/CooconService.php +++ b/app/Services/Coocon/CooconService.php @@ -13,17 +13,24 @@ class CooconService { private ?CooconConfig $config = null; + private bool $isTestMode = true; /** * API ID 상수 */ public const API_COMPANY_INFO = 'OA08'; // 기업 기본정보 + public const API_CREDIT_SUMMARY = 'OA12'; // 신용요약정보 + public const API_SHORT_TERM_OVERDUE = 'OA13'; // 단기연체정보 (한국신용정보원) + public const API_NEGATIVE_INFO_KCI = 'OA14'; // 신용도판단정보 (한국신용정보원) + public const API_NEGATIVE_INFO_CB = 'OA15'; // 신용도판단정보 (신용정보사) + public const API_SUSPENSION_INFO = 'OA16'; // 당좌거래정지정보 (금융결제원) + public const API_WORKOUT_INFO = 'OA17'; // 법정관리/워크아웃정보 /** @@ -43,6 +50,7 @@ class CooconService * 기본 URL */ private const BASE_URL_TEST = 'https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp'; + private const BASE_URL_PRODUCTION = 'https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp'; public function __construct(bool $isTestMode = true) @@ -65,6 +73,7 @@ private function loadConfig(): void public function reloadConfig(): self { $this->loadConfig(); + return $this; } @@ -75,6 +84,7 @@ public function setTestMode(bool $isTestMode): self { $this->isTestMode = $isTestMode; $this->loadConfig(); + return $this; } @@ -111,7 +121,7 @@ private function getBaseUrl(): string */ private function callApi(string $apiId, array $params = []): array { - if (!$this->config) { + if (! $this->config) { return [ 'success' => false, 'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.', @@ -151,10 +161,10 @@ private function callApi(string $apiId, array $params = []): array 'rslt_msg' => $result['RSLT_MSG'] ?? 'N/A', ]); - if (!$response->successful()) { + if (! $response->successful()) { return [ 'success' => false, - 'error' => 'HTTP 오류: ' . $response->status(), + 'error' => 'HTTP 오류: '.$response->status(), 'code' => 'HTTP_ERROR', 'http_status' => $response->status(), ]; @@ -186,7 +196,7 @@ private function callApi(string $apiId, array $params = []): array return [ 'success' => false, - 'error' => '쿠콘 API 호출 중 오류가 발생했습니다: ' . $e->getMessage(), + 'error' => '쿠콘 API 호출 중 오류가 발생했습니다: '.$e->getMessage(), 'code' => 'EXCEPTION', ]; } @@ -197,14 +207,14 @@ private function callApi(string $apiId, array $params = []): array */ private function generateTransactionSequence(): string { - return date('YmdHis') . substr(microtime(), 2, 6); + return date('YmdHis').substr(microtime(), 2, 6); } /** * 기업 기본정보 조회 (OA08) * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 - * @param string $idscdcg 식별자구분코드 (기본값: 09-사업자등록번호) + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $idscdcg 식별자구분코드 (기본값: 09-사업자등록번호) */ public function getCompanyInfo(string $companyKey, string $idscdcg = '09'): array { @@ -217,7 +227,7 @@ public function getCompanyInfo(string $companyKey, string $idscdcg = '09'): arra /** * 신용요약정보 조회 (OA12) * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 */ public function getCreditSummary(string $companyKey): array { @@ -229,8 +239,8 @@ public function getCreditSummary(string $companyKey): array /** * 단기연체정보 조회 (OA13) - 한국신용정보원 * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 - * @param string|null $reqDate 기준일자 (YYYYMMDD), 미입력시 현재 날짜 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string|null $reqDate 기준일자 (YYYYMMDD), 미입력시 현재 날짜 */ public function getShortTermOverdueInfo(string $companyKey, ?string $reqDate = null): array { @@ -246,7 +256,7 @@ public function getShortTermOverdueInfo(string $companyKey, ?string $reqDate = n /** * 신용도판단정보 조회 (OA14) - 한국신용정보원 (공공정보 포함) * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 */ public function getNegativeInfoKCI(string $companyKey): array { @@ -258,7 +268,7 @@ public function getNegativeInfoKCI(string $companyKey): array /** * 신용도판단정보 조회 (OA15) - 신용정보사 * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 */ public function getNegativeInfoCB(string $companyKey): array { @@ -270,7 +280,7 @@ public function getNegativeInfoCB(string $companyKey): array /** * 당좌거래정지정보 조회 (OA16) - 금융결제원 * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 */ public function getSuspensionInfo(string $companyKey): array { @@ -282,9 +292,9 @@ public function getSuspensionInfo(string $companyKey): array /** * 법정관리/워크아웃정보 조회 (OA17) * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 - * @param int $pageNo 페이지 번호 - * @param int $pageSize 페이지 사이즈 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param int $pageNo 페이지 번호 + * @param int $pageSize 페이지 사이즈 */ public function getWorkoutInfo(string $companyKey, int $pageNo = 1, int $pageSize = 10): array { @@ -298,7 +308,7 @@ public function getWorkoutInfo(string $companyKey, int $pageNo = 1, int $pageSiz /** * 전체 신용정보 조회 (모든 API 호출) * - * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 */ public function getAllCreditInfo(string $companyKey): array { diff --git a/app/Services/ESign/DocxToPdfConverter.php b/app/Services/ESign/DocxToPdfConverter.php index ad54cc05..7a45db65 100644 --- a/app/Services/ESign/DocxToPdfConverter.php +++ b/app/Services/ESign/DocxToPdfConverter.php @@ -16,7 +16,7 @@ class DocxToPdfConverter */ public function convertAndStore(UploadedFile $file, string $storagePath): array { - if (!$this->isWordFile($file)) { + if (! $this->isWordFile($file)) { // PDF는 그대로 저장 $path = $file->store($storagePath, 'local'); @@ -32,11 +32,11 @@ public function convertAndStore(UploadedFile $file, string $storagePath): array $originalName = $file->getClientOriginalName(); $pdfName = preg_replace('/\.(docx?|DOCX?)$/', '.pdf', $originalName); - $tmpDir = sys_get_temp_dir() . '/esign_convert_' . Str::random(16); + $tmpDir = sys_get_temp_dir().'/esign_convert_'.Str::random(16); mkdir($tmpDir, 0755, true); try { - $tmpWordPath = $tmpDir . '/' . 'source.' . $file->getClientOriginalExtension(); + $tmpWordPath = $tmpDir.'/'.'source.'.$file->getClientOriginalExtension(); copy($file->getRealPath(), $tmpWordPath); $command = sprintf( @@ -50,12 +50,12 @@ public function convertAndStore(UploadedFile $file, string $storagePath): array if ($exitCode !== 0) { throw new RuntimeException( - 'LibreOffice 변환 실패 (exit code: ' . $exitCode . '): ' . implode("\n", $output) + 'LibreOffice 변환 실패 (exit code: '.$exitCode.'): '.implode("\n", $output) ); } - $tmpPdfPath = $tmpDir . '/source.pdf'; - if (!file_exists($tmpPdfPath)) { + $tmpPdfPath = $tmpDir.'/source.pdf'; + if (! file_exists($tmpPdfPath)) { throw new RuntimeException('변환된 PDF 파일을 찾을 수 없습니다.'); } @@ -63,7 +63,7 @@ public function convertAndStore(UploadedFile $file, string $storagePath): array $size = filesize($tmpPdfPath); // 최종 저장 경로 - $finalPath = $storagePath . '/' . Str::random(40) . '.pdf'; + $finalPath = $storagePath.'/'.Str::random(40).'.pdf'; Storage::disk('local')->put($finalPath, file_get_contents($tmpPdfPath)); return [ @@ -74,7 +74,7 @@ public function convertAndStore(UploadedFile $file, string $storagePath): array ]; } finally { // 임시 파일 정리 - array_map('unlink', glob($tmpDir . '/*')); + array_map('unlink', glob($tmpDir.'/*')); @rmdir($tmpDir); } } diff --git a/app/Services/ESign/PdfSignatureService.php b/app/Services/ESign/PdfSignatureService.php index bedf35c5..ccd00d5f 100644 --- a/app/Services/ESign/PdfSignatureService.php +++ b/app/Services/ESign/PdfSignatureService.php @@ -42,7 +42,7 @@ public function mergeSignatures(EsignContract $contract): string } // FPDI(TCPDF 확장)로 원본 PDF 임포트 - $pdf = new Fpdi(); + $pdf = new Fpdi; $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); @@ -91,7 +91,7 @@ public function mergeSignatures(EsignContract $contract): string 'signed_file_hash' => hash_file('sha256', $signedAbsPath), ]); - Log::info("PDF 서명 합성 완료", [ + Log::info('PDF 서명 합성 완료', [ 'contract_id' => $contract->id, 'signed_file_path' => $signedRelPath, ]); @@ -112,7 +112,7 @@ public function generatePreview(EsignContract $contract): string throw new \RuntimeException("원본 PDF 파일이 존재하지 않습니다: {$contract->original_file_path}"); } - $pdf = new Fpdi(); + $pdf = new Fpdi; $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); @@ -199,10 +199,11 @@ private function overlayImage(Fpdi $pdf, EsignSignField $field, float $x, float $imagePath = Storage::disk('local')->path($signer->signature_image_path); if (! file_exists($imagePath)) { - Log::warning("서명 이미지 파일 없음", [ + Log::warning('서명 이미지 파일 없음', [ 'signer_id' => $signer->id, 'path' => $signer->signature_image_path, ]); + return; } diff --git a/app/Services/FcmApiService.php b/app/Services/FcmApiService.php index f3ca3a5e..7d97ae83 100644 --- a/app/Services/FcmApiService.php +++ b/app/Services/FcmApiService.php @@ -111,4 +111,4 @@ public function previewCount(array $filters): array ]; } } -} \ No newline at end of file +} diff --git a/app/Services/FlowTester/ConditionEvaluator.php b/app/Services/FlowTester/ConditionEvaluator.php index a4f3ba3d..acc0589c 100644 --- a/app/Services/FlowTester/ConditionEvaluator.php +++ b/app/Services/FlowTester/ConditionEvaluator.php @@ -505,4 +505,4 @@ public function getStepResults(): array { return $this->stepResults; } -} \ No newline at end of file +} diff --git a/app/Services/FlowTester/FlowExecutor.php b/app/Services/FlowTester/FlowExecutor.php index 4b70177f..f50434f6 100644 --- a/app/Services/FlowTester/FlowExecutor.php +++ b/app/Services/FlowTester/FlowExecutor.php @@ -239,6 +239,7 @@ private function checkDependencies(array $step): array // 의존 스텝이 아직 실행되지 않은 경우 (순서 문제) if ($depResult === null && ! isset($this->stepSuccessMap[$depId])) { $failedDeps[] = "{$depId} (not executed yet)"; + continue; } diff --git a/app/Services/FormulaApiService.php b/app/Services/FormulaApiService.php index 74386266..df6ca8b5 100644 --- a/app/Services/FormulaApiService.php +++ b/app/Services/FormulaApiService.php @@ -16,9 +16,9 @@ class FormulaApiService * - SSL 우회: withoutVerifying() (내부 자체 서명 인증서) * - 인증: X-API-KEY + Bearer token (ApiTokenService 토큰 교환) * - * @param string $finishedGoodsCode 완제품 코드 (예: FG-KQTS01) - * @param array $variables 입력 변수 ['W0' => 3000, 'H0' => 3000, 'QTY' => 1] - * @param int $tenantId 테넌트 ID + * @param string $finishedGoodsCode 완제품 코드 (예: FG-KQTS01) + * @param array $variables 입력 변수 ['W0' => 3000, 'H0' => 3000, 'QTY' => 1] + * @param int $tenantId 테넌트 ID * @return array 성공 시 API 응답, 실패 시 ['success' => false, 'error' => '...'] */ public function calculateBom(string $finishedGoodsCode, array $variables, int $tenantId): array @@ -54,6 +54,7 @@ public function calculateBom(string $finishedGoodsCode, array $variables, int $t if ($response->successful()) { $json = $response->json(); + // ApiResponse::handle()는 {success, message, data} 구조로 래핑 return $json['data'] ?? $json; } @@ -66,7 +67,7 @@ public function calculateBom(string $finishedGoodsCode, array $variables, int $t return [ 'success' => false, - 'error' => 'API 응답 오류: HTTP ' . $response->status(), + 'error' => 'API 응답 오류: HTTP '.$response->status(), ]; } catch (\Exception $e) { Log::error('FormulaApiService: 예외 발생', [ @@ -76,7 +77,7 @@ public function calculateBom(string $finishedGoodsCode, array $variables, int $t return [ 'success' => false, - 'error' => '수식 계산 서버 연결 실패: ' . $e->getMessage(), + 'error' => '수식 계산 서버 연결 실패: '.$e->getMessage(), ]; } } diff --git a/app/Services/FundScheduleService.php b/app/Services/FundScheduleService.php index a5c55abc..15290918 100644 --- a/app/Services/FundScheduleService.php +++ b/app/Services/FundScheduleService.php @@ -315,13 +315,13 @@ public function getMonthlySummary(int $year, int $month): array 'count' => $incomeSchedules->count(), 'total' => $incomeSchedules->sum('amount'), 'pending' => $incomeSchedules->where('status', FundSchedule::STATUS_PENDING)->sum('amount'), - 'completed' => $incomeSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn($s) => $s->completed_amount ?: $s->amount), + 'completed' => $incomeSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn ($s) => $s->completed_amount ?: $s->amount), ], 'expense' => [ 'count' => $expenseSchedules->count(), 'total' => $expenseSchedules->sum('amount'), 'pending' => $expenseSchedules->where('status', FundSchedule::STATUS_PENDING)->sum('amount'), - 'completed' => $expenseSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn($s) => $s->completed_amount ?: $s->amount), + 'completed' => $expenseSchedules->where('status', FundSchedule::STATUS_COMPLETED)->sum(fn ($s) => $s->completed_amount ?: $s->amount), ], 'net' => $incomeSchedules->sum('amount') - $expenseSchedules->sum('amount'), ]; diff --git a/app/Services/GoogleCloudService.php b/app/Services/GoogleCloudService.php index f9406d75..549029bf 100644 --- a/app/Services/GoogleCloudService.php +++ b/app/Services/GoogleCloudService.php @@ -97,6 +97,7 @@ public function getAccessToken(): ?string /** * GCS에 파일 업로드 + * * @return array|null ['uri' => 'gs://...', 'size' => bytes] or null */ public function uploadToStorage(string $localPath, string $objectName): ?array @@ -153,6 +154,7 @@ public function uploadToStorage(string $localPath, string $objectName): ?array /** * Base64 오디오를 GCS에 업로드 + * * @return array|null ['uri' => 'gs://...', 'size' => bytes] or null */ public function uploadBase64Audio(string $base64Audio, string $objectName): ?array @@ -415,7 +417,7 @@ private function parseDiarizationResult(array $operationResult): ?array // word-level 결과 없으면 일반 transcript로 폴백 $transcript = ''; foreach ($results as $res) { - $transcript .= ($res['alternatives'][0]['transcript'] ?? '') . ' '; + $transcript .= ($res['alternatives'][0]['transcript'] ?? '').' '; } $transcript = $this->cleanSttText(trim($transcript)); @@ -428,7 +430,7 @@ private function parseDiarizationResult(array $operationResult): ?array 'end_time_ms' => null, 'is_manual_speaker' => false, ]], - 'full_transcript' => '[화자 1] ' . $transcript, + 'full_transcript' => '[화자 1] '.$transcript, 'speaker_count' => 1, ]; } @@ -457,7 +459,7 @@ private function parseDiarizationResult(array $operationResult): ?array if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentTokens)) { $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => $this->joinSentencePieceTokens($currentTokens), 'start_time_ms' => $segmentStartMs, @@ -476,7 +478,7 @@ private function parseDiarizationResult(array $operationResult): ?array if (! empty($currentTokens)) { $lastWord = end($words); $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => $this->joinSentencePieceTokens($currentTokens), 'start_time_ms' => $segmentStartMs, @@ -526,7 +528,7 @@ private function joinSentencePieceTokens(array $tokens): string if ($i === 0) { $result = $token['text']; } elseif ($token['new_word']) { - $result .= ' ' . $token['text']; + $result .= ' '.$token['text']; } else { $result .= $token['text']; } @@ -739,7 +741,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array // word-level 결과 없으면 일반 transcript 사용 $transcript = ''; foreach ($results as $res) { - $transcript .= ($res['alternatives'][0]['transcript'] ?? '') . ' '; + $transcript .= ($res['alternatives'][0]['transcript'] ?? '').' '; } $transcript = trim($transcript); @@ -752,7 +754,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array 'end_time_ms' => null, 'is_manual_speaker' => false, ]], - 'full_transcript' => '[화자 1] ' . $transcript, + 'full_transcript' => '[화자 1] '.$transcript, 'speaker_count' => 1, ]; } @@ -789,7 +791,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentTokens)) { $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => $this->joinSentencePieceTokens($currentTokens), 'start_time_ms' => $segmentStartMs, @@ -806,7 +808,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array // 일반 단어 방식 처리 (Chirp 2) if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentWords)) { $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => implode(' ', $currentWords), 'start_time_ms' => $segmentStartMs, @@ -826,7 +828,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array if ($hasSentencePiece && ! empty($currentTokens)) { $lastWord = end($words); $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => $this->joinSentencePieceTokens($currentTokens), 'start_time_ms' => $segmentStartMs, @@ -836,7 +838,7 @@ private function parseV2Result(array $operationResult, string $gcsUri): ?array } elseif (! $hasSentencePiece && ! empty($currentWords)) { $lastWord = end($words); $segments[] = [ - 'speaker_name' => '화자 ' . $currentSpeaker, + 'speaker_name' => '화자 '.$currentSpeaker, 'speaker_label' => (string) $currentSpeaker, 'text' => implode(' ', $currentWords), 'start_time_ms' => $segmentStartMs, diff --git a/app/Services/ItemManagementService.php b/app/Services/ItemManagementService.php index cc7bc861..1ed13b75 100644 --- a/app/Services/ItemManagementService.php +++ b/app/Services/ItemManagementService.php @@ -57,7 +57,7 @@ public function getItemDetail(int $itemId): array // BOM 1depth: 직접 연결된 자식 품목만 $bomChildren = []; $bomData = $item->bom ?? []; - if (!empty($bomData)) { + if (! empty($bomData)) { $childIds = array_column($bomData, 'child_item_id'); $children = Item::withoutGlobalScopes() ->where('tenant_id', $tenantId) @@ -99,7 +99,7 @@ private function buildBomNode(Item $item, int $depth, int $maxDepth, array $visi $children = []; $bomData = $item->bom ?? []; - if (!empty($bomData)) { + if (! empty($bomData)) { $childIds = array_column($bomData, 'child_item_id'); $childItems = Item::withoutGlobalScopes() ->where('tenant_id', session('selected_tenant_id')) diff --git a/app/Services/MeetingMinuteService.php b/app/Services/MeetingMinuteService.php index e7ae1ee3..74206bf6 100644 --- a/app/Services/MeetingMinuteService.php +++ b/app/Services/MeetingMinuteService.php @@ -391,7 +391,7 @@ private function splitSpeakersWithGemini(string $fullText, int $expectedSpeakers continue; } $segments[] = [ - 'speaker_name' => '화자 ' . $speakerNum, + 'speaker_name' => '화자 '.$speakerNum, 'speaker_label' => (string) $speakerNum, 'text' => $text, 'start_time_ms' => 0, @@ -505,7 +505,7 @@ private function callVertexAiApi(AiConfig $config, string $prompt): ?string $url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent"; return $this->callGeminiApi($url, $prompt, [ - 'Authorization' => 'Bearer ' . $accessToken, + 'Authorization' => 'Bearer '.$accessToken, 'Content-Type' => 'application/json', ], true); } @@ -554,8 +554,8 @@ private function getAccessToken(AiConfig $config): ?string return null; } - openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); - $jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature); + openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); + $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature); try { $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ diff --git a/app/Services/Nts/NtsBusinessService.php b/app/Services/Nts/NtsBusinessService.php index c64e3416..1f618d70 100644 --- a/app/Services/Nts/NtsBusinessService.php +++ b/app/Services/Nts/NtsBusinessService.php @@ -29,8 +29,7 @@ public function __construct() /** * 사업자등록 상태 조회 * - * @param string $businessNumber 사업자등록번호 (10자리, 하이픈 없이) - * @return array + * @param string $businessNumber 사업자등록번호 (10자리, 하이픈 없이) */ public function getBusinessStatus(string $businessNumber): array { @@ -45,7 +44,7 @@ public function getBusinessStatus(string $businessNumber): array ]; } - $url = self::API_URL . '?serviceKey=' . $this->serviceKey; + $url = self::API_URL.'?serviceKey='.$this->serviceKey; Log::info('국세청 사업자등록 상태 조회', [ 'biz_no' => $bizNo, @@ -69,10 +68,10 @@ public function getBusinessStatus(string $businessNumber): array 'status_code' => $response->status(), ]); - if (!$response->successful()) { + if (! $response->successful()) { return [ 'success' => false, - 'error' => 'HTTP 오류: ' . $response->status(), + 'error' => 'HTTP 오류: '.$response->status(), 'code' => 'HTTP_ERROR', 'http_status' => $response->status(), ]; @@ -119,7 +118,7 @@ public function getBusinessStatus(string $businessNumber): array return [ 'success' => false, - 'error' => '국세청 API 호출 중 오류가 발생했습니다: ' . $e->getMessage(), + 'error' => '국세청 API 호출 중 오류가 발생했습니다: '.$e->getMessage(), 'code' => 'EXCEPTION', ]; } @@ -128,8 +127,7 @@ public function getBusinessStatus(string $businessNumber): array /** * 여러 사업자번호 일괄 상태 조회 * - * @param array $businessNumbers 사업자등록번호 배열 - * @return array + * @param array $businessNumbers 사업자등록번호 배열 */ public function getBusinessStatusBulk(array $businessNumbers): array { @@ -138,7 +136,7 @@ public function getBusinessStatusBulk(array $businessNumbers): array }, $businessNumbers); // 유효하지 않은 번호 필터링 - $validBizNos = array_filter($bizNos, fn($num) => strlen($num) === 10); + $validBizNos = array_filter($bizNos, fn ($num) => strlen($num) === 10); if (empty($validBizNos)) { return [ @@ -148,7 +146,7 @@ public function getBusinessStatusBulk(array $businessNumbers): array ]; } - $url = self::API_URL . '?serviceKey=' . $this->serviceKey; + $url = self::API_URL.'?serviceKey='.$this->serviceKey; try { $response = Http::timeout(30) @@ -162,10 +160,10 @@ public function getBusinessStatusBulk(array $businessNumbers): array $result = $response->json(); - if (!$response->successful()) { + if (! $response->successful()) { return [ 'success' => false, - 'error' => 'HTTP 오류: ' . $response->status(), + 'error' => 'HTTP 오류: '.$response->status(), 'code' => 'HTTP_ERROR', ]; } @@ -179,7 +177,7 @@ public function getBusinessStatusBulk(array $businessNumbers): array } catch (\Exception $e) { return [ 'success' => false, - 'error' => '국세청 API 호출 중 오류가 발생했습니다: ' . $e->getMessage(), + 'error' => '국세청 API 호출 중 오류가 발생했습니다: '.$e->getMessage(), 'code' => 'EXCEPTION', ]; } diff --git a/app/Services/NumberingRuleService.php b/app/Services/NumberingRuleService.php index 7ffed286..7642683f 100644 --- a/app/Services/NumberingRuleService.php +++ b/app/Services/NumberingRuleService.php @@ -106,8 +106,8 @@ public function generatePreview(array $pattern, int $sequencePadding = 2): strin 'static' => $segment['value'] ?? '', 'separator' => $segment['value'] ?? '', 'date' => now()->format($segment['format'] ?? 'ymd'), - 'param' => $segment['default'] ?? '{' . ($segment['key'] ?? '?') . '}', - 'mapping' => $segment['default'] ?? '{' . ($segment['key'] ?? '?') . '}', + 'param' => $segment['default'] ?? '{'.($segment['key'] ?? '?').'}', + 'mapping' => $segment['default'] ?? '{'.($segment['key'] ?? '?').'}', 'sequence' => str_pad('1', $sequencePadding, '0', STR_PAD_LEFT), default => '', }; diff --git a/app/Services/PartitionManagementService.php b/app/Services/PartitionManagementService.php index f4bec8fb..33ac4c43 100644 --- a/app/Services/PartitionManagementService.php +++ b/app/Services/PartitionManagementService.php @@ -113,7 +113,7 @@ public function addFuturePartitions(int $months): array for ($i = 0; $i <= $months; $i++) { $target = $now->copy()->addMonths($i)->startOfMonth()->addMonth(); $ts = $target->timestamp; - $name = 'p' . $target->copy()->subMonth()->format('Ym'); + $name = 'p'.$target->copy()->subMonth()->format('Ym'); if (in_array($ts, $existingBounds)) { continue; diff --git a/app/Services/Sales/InterviewScenarioService.php b/app/Services/Sales/InterviewScenarioService.php index 6a35afb2..f75247dc 100644 --- a/app/Services/Sales/InterviewScenarioService.php +++ b/app/Services/Sales/InterviewScenarioService.php @@ -223,11 +223,11 @@ public function getSessions(array $filters = []) ->orderByDesc('interview_date') ->orderByDesc('id'); - if (!empty($filters['status'])) { + if (! empty($filters['status'])) { $query->where('status', $filters['status']); } - if (!empty($filters['category_id'])) { + if (! empty($filters['category_id'])) { $query->where('interview_category_id', $filters['category_id']); } @@ -249,7 +249,7 @@ public function startSession(array $data): InterviewSession ->orderBy('sort_order') ->get(); - $totalQuestions = $templates->sum(fn($t) => $t->questions->count()); + $totalQuestions = $templates->sum(fn ($t) => $t->questions->count()); // 세션 생성 $session = InterviewSession::create([ @@ -302,7 +302,7 @@ public function toggleAnswer(array $data): InterviewAnswer ->firstOrFail(); $answer->update([ - 'is_checked' => !$answer->is_checked, + 'is_checked' => ! $answer->is_checked, 'answer_text' => $data['answer_text'] ?? $answer->answer_text, 'memo' => $data['memo'] ?? $answer->memo, ]); diff --git a/app/Services/Sales/SalesDevelopmentApprovalService.php b/app/Services/Sales/SalesDevelopmentApprovalService.php index 62ee5150..d4124798 100644 --- a/app/Services/Sales/SalesDevelopmentApprovalService.php +++ b/app/Services/Sales/SalesDevelopmentApprovalService.php @@ -3,9 +3,7 @@ namespace App\Services\Sales; use App\Models\Sales\SalesTenantManagement; -use App\Models\Tenants\Tenant; use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Facades\DB; /** * 개발 승인 관리 서비스 @@ -168,8 +166,8 @@ public function reject(int $id, string $reason): SalesTenantManagement // notes 필드에 반려 사유 추가 $currentNotes = $management->notes ?? ''; - $rejectionNote = '[반려 ' . now()->format('Y-m-d H:i') . '] ' . $reason; - $newNotes = $currentNotes ? $currentNotes . "\n" . $rejectionNote : $rejectionNote; + $rejectionNote = '[반려 '.now()->format('Y-m-d H:i').'] '.$reason; + $newNotes = $currentNotes ? $currentNotes."\n".$rejectionNote : $rejectionNote; $management->update([ 'notes' => $newNotes, @@ -186,7 +184,7 @@ public function updateHqStatus(int $id, string $status): SalesTenantManagement $management = SalesTenantManagement::findOrFail($id); // 유효한 상태인지 확인 - if (!array_key_exists($status, SalesTenantManagement::$hqStatusLabels)) { + if (! array_key_exists($status, SalesTenantManagement::$hqStatusLabels)) { throw new \InvalidArgumentException('유효하지 않은 상태입니다.'); } diff --git a/app/Services/Sales/SalesManagerService.php b/app/Services/Sales/SalesManagerService.php index 4b44d543..d9854c4a 100644 --- a/app/Services/Sales/SalesManagerService.php +++ b/app/Services/Sales/SalesManagerService.php @@ -51,7 +51,7 @@ public function createSalesPartner(array $data, array $documents = []): User ]); // 3. 역할 할당 - if (!empty($data['role_ids'])) { + if (! empty($data['role_ids'])) { $this->syncRoles($user, $tenantId, $data['role_ids']); } @@ -59,7 +59,7 @@ public function createSalesPartner(array $data, array $documents = []): User $this->assignSalesDepartment($user, $tenantId); // 4-1. 사업자 정보 저장 (선택) - if (!empty($data['company_name']) || !empty($data['biz_no']) || !empty($data['address'])) { + if (! empty($data['company_name']) || ! empty($data['biz_no']) || ! empty($data['address'])) { $partnerType = $data['partner_type'] ?? 'individual'; $spData = [ 'partner_code' => SalesPartner::generatePartnerCode(), @@ -84,7 +84,7 @@ public function createSalesPartner(array $data, array $documents = []): User } // 5. 첨부 서류 저장 - if (!empty($documents)) { + if (! empty($documents)) { $this->uploadDocuments($user, $tenantId, $documents); } @@ -108,7 +108,7 @@ public function updateSalesPartner(User $user, array $data, array $documents = [ ]; // 비밀번호 변경 시에만 업데이트 - if (!empty($data['password'])) { + if (! empty($data['password'])) { $updateData['password'] = Hash::make($data['password']); } @@ -120,13 +120,13 @@ public function updateSalesPartner(User $user, array $data, array $documents = [ } // 2-1. 사업자 정보 업데이트 - $hasBizInfo = !empty($data['company_name']) || !empty($data['biz_no']) || !empty($data['address']); + $hasBizInfo = ! empty($data['company_name']) || ! empty($data['biz_no']) || ! empty($data['address']); $existingSp = SalesPartner::where('user_id', $user->id)->first(); - $hasPartnerType = !empty($data['partner_type']); + $hasPartnerType = ! empty($data['partner_type']); if ($hasBizInfo || $existingSp || $hasPartnerType) { $sp = $existingSp ?? new SalesPartner(['user_id' => $user->id]); - if (!$sp->exists) { + if (! $sp->exists) { $sp->partner_code = SalesPartner::generatePartnerCode(); $sp->partner_type = $data['partner_type'] ?? 'individual'; $sp->status = 'active'; @@ -151,7 +151,7 @@ public function updateSalesPartner(User $user, array $data, array $documents = [ } // 3. 새 첨부 서류 저장 - if (!empty($documents)) { + if (! empty($documents)) { $this->uploadDocuments($user, $tenantId, $documents); } @@ -194,9 +194,9 @@ public function reject(User $user, int $approverId, string $reason): User /** * 역할 위임 * - * @param User $fromUser 역할을 넘기는 파트너 - * @param User $toUser 역할을 받는 파트너 - * @param string $roleName 위임할 역할 (manager, recruiter 등) + * @param User $fromUser 역할을 넘기는 파트너 + * @param User $toUser 역할을 받는 파트너 + * @param string $roleName 위임할 역할 (manager, recruiter 등) */ public function delegateRole(User $fromUser, User $toUser, string $roleName): bool { @@ -208,7 +208,7 @@ public function delegateRole(User $fromUser, User $toUser, string $roleName): bo ->where('name', $roleName) ->first(); - if (!$role) { + if (! $role) { throw new \InvalidArgumentException("역할을 찾을 수 없습니다: {$roleName}"); } @@ -218,7 +218,7 @@ public function delegateRole(User $fromUser, User $toUser, string $roleName): bo ->where('role_id', $role->id) ->exists(); - if (!$hasRole) { + if (! $hasRole) { throw new \InvalidArgumentException("{$fromUser->name}님이 {$roleName} 역할을 보유하고 있지 않습니다."); } @@ -252,7 +252,7 @@ public function assignRole(User $user, string $roleName): bool ->where('name', $roleName) ->first(); - if (!$role) { + if (! $role) { return false; } @@ -278,7 +278,7 @@ public function removeRole(User $user, string $roleName): bool ->where('name', $roleName) ->first(); - if (!$role) { + if (! $role) { return false; } @@ -338,7 +338,7 @@ public function syncRoles(User $user, int $tenantId, array $roleIds): void // 제거할 역할 soft delete (새 역할 목록에 없는 것들) $roleIdsToRemove = array_diff($salesRoleIds, $roleIds); - if (!empty($roleIdsToRemove)) { + if (! empty($roleIdsToRemove)) { UserRole::where('user_id', $user->id) ->where('tenant_id', $tenantId) ->whereIn('role_id', $roleIdsToRemove) @@ -379,7 +379,7 @@ public function uploadDocuments(User $user, int $tenantId, array $documents): ar $uploaded = []; foreach ($documents as $doc) { - if (!isset($doc['file']) || !$doc['file'] instanceof UploadedFile) { + if (! isset($doc['file']) || ! $doc['file'] instanceof UploadedFile) { continue; } @@ -388,7 +388,7 @@ public function uploadDocuments(User $user, int $tenantId, array $documents): ar $description = $doc['description'] ?? null; // 파일 저장 - $storedName = Str::uuid() . '.' . $file->getClientOriginalExtension(); + $storedName = Str::uuid().'.'.$file->getClientOriginalExtension(); $filePath = "sales-partners/{$user->id}/{$storedName}"; Storage::disk('tenant')->put($filePath, file_get_contents($file)); @@ -448,7 +448,7 @@ public function getSalesPartners(array $filters = []) ->with(['parent', 'userRoles.role', 'salesDocuments', 'salesPartner']); // 검색 - if (!empty($filters['search'])) { + if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") @@ -459,7 +459,7 @@ public function getSalesPartners(array $filters = []) } // 역할 필터 - if (!empty($filters['role'])) { + if (! empty($filters['role'])) { $roleName = $filters['role']; $query->whereHas('userRoles', function ($q) use ($tenantId, $roleName) { $q->where('tenant_id', $tenantId) @@ -470,7 +470,7 @@ public function getSalesPartners(array $filters = []) } // 승인 상태 필터 - if (!empty($filters['approval_status'])) { + if (! empty($filters['approval_status'])) { $query->where('approval_status', $filters['approval_status']); } @@ -480,7 +480,7 @@ public function getSalesPartners(array $filters = []) } // 유치자(추천인) 필터 - 현재 로그인한 사용자가 유치한 파트너만 - if (!empty($filters['parent_id'])) { + if (! empty($filters['parent_id'])) { $query->where('parent_id', $filters['parent_id']); } @@ -562,10 +562,10 @@ public function getStats(?int $parentId = null): array 'pending' => (clone $baseQuery)->where('approval_status', 'pending')->count(), 'approved' => (clone $baseQuery)->where('approval_status', 'approved')->count(), 'sales' => (clone $baseQuery) - ->whereHas('userRoles.role', fn($q) => $q->where('name', 'sales')) + ->whereHas('userRoles.role', fn ($q) => $q->where('name', 'sales')) ->count(), 'manager' => (clone $baseQuery) - ->whereHas('userRoles.role', fn($q) => $q->where('name', 'manager')) + ->whereHas('userRoles.role', fn ($q) => $q->where('name', 'manager')) ->count(), ]; } @@ -626,6 +626,7 @@ public function getAllDescendants(User $user, int $maxDepth = 10): array { $descendants = []; $this->collectDescendants($user, $descendants, 1, $maxDepth); + return $descendants; } diff --git a/app/Services/Sales/TenantProspectService.php b/app/Services/Sales/TenantProspectService.php index a4a0c021..59abc8ec 100644 --- a/app/Services/Sales/TenantProspectService.php +++ b/app/Services/Sales/TenantProspectService.php @@ -104,7 +104,7 @@ public function convertToTenant(TenantProspect $prospect, int $convertedBy): Ten { return DB::transaction(function () use ($prospect, $convertedBy) { // 고유 테넌트 코드 생성 (T + 타임스탬프 + 랜덤) - $tenantCode = 'T' . now()->format('ymd') . strtoupper(substr(uniqid(), -4)); + $tenantCode = 'T'.now()->format('ymd').strtoupper(substr(uniqid(), -4)); // 테넌트 생성 $tenant = Tenant::create([ @@ -208,7 +208,7 @@ public function getProspects(array $filters = []) $query = TenantProspect::with(['registeredBy', 'tenant']); // 검색 - if (!empty($filters['search'])) { + if (! empty($filters['search'])) { $search = $filters['search']; $query->where(function ($q) use ($search) { $q->where('company_name', 'like', "%{$search}%") @@ -219,7 +219,7 @@ public function getProspects(array $filters = []) } // 상태 필터 - if (!empty($filters['status'])) { + if (! empty($filters['status'])) { if ($filters['status'] === 'active') { $query->active(); } elseif ($filters['status'] === 'expired') { @@ -230,7 +230,7 @@ public function getProspects(array $filters = []) } // 특정 영업파트너 - if (!empty($filters['registered_by'])) { + if (! empty($filters['registered_by'])) { $query->byPartner($filters['registered_by']); } @@ -261,7 +261,7 @@ public function getStats(?int $partnerId = null): array */ private function uploadAttachment(UploadedFile $file, int $userId): string { - $storedName = Str::uuid() . '.' . $file->getClientOriginalExtension(); + $storedName = Str::uuid().'.'.$file->getClientOriginalExtension(); $filePath = "prospects/{$userId}/{$storedName}"; Storage::disk('tenant')->put($filePath, file_get_contents($file)); @@ -295,7 +295,7 @@ private function saveBase64Image(string $base64Data, int $userId): ?string return null; } - $storedName = Str::uuid() . '.' . $extension; + $storedName = Str::uuid().'.'.$extension; $filePath = "prospects/{$userId}/{$storedName}"; Storage::disk('tenant')->put($filePath, $imageData); @@ -316,9 +316,9 @@ public function deleteBusinessCard(TenantProspect $prospect): bool */ public function deleteAttachment(TenantProspect $prospect, string $type): bool { - $pathField = $type . '_path'; + $pathField = $type.'_path'; - if (!$prospect->$pathField) { + if (! $prospect->$pathField) { return false; } @@ -327,6 +327,7 @@ public function deleteAttachment(TenantProspect $prospect, string $type): bool } $prospect->update([$pathField => null]); + return true; } } diff --git a/app/Services/SalesCommissionService.php b/app/Services/SalesCommissionService.php index 861a3fef..b1f8a16f 100644 --- a/app/Services/SalesCommissionService.php +++ b/app/Services/SalesCommissionService.php @@ -4,7 +4,6 @@ use App\Models\Sales\SalesCommission; use App\Models\Sales\SalesCommissionDetail; -use App\Models\Sales\SalesContractProduct; use App\Models\Sales\SalesPartner; use App\Models\Sales\SalesTenantManagement; use Carbon\Carbon; @@ -18,8 +17,11 @@ class SalesCommissionService * 기본 수당률 */ const DEFAULT_PARTNER_RATE = 20.00; + const DEFAULT_GROUP_RATE = 30.00; // 단체 파트너 수당률 + const DEFAULT_INDIVIDUAL_REFERRER_RATE = 5.00; // 개인 유치수당률 + const DEFAULT_GROUP_REFERRER_RATE = 3.00; // 단체 유치수당률 // ========================================================================= @@ -42,44 +44,44 @@ public function getCommissions(array $filters = [], int $perPage = 20): LengthAw ]); // 상태 필터 - if (!empty($filters['status'])) { + if (! empty($filters['status'])) { $query->where('status', $filters['status']); } // 입금구분 필터 - if (!empty($filters['payment_type'])) { + if (! empty($filters['payment_type'])) { $query->where('payment_type', $filters['payment_type']); } // 영업파트너 필터 - if (!empty($filters['partner_id'])) { + if (! empty($filters['partner_id'])) { $query->where('partner_id', $filters['partner_id']); } // 매니저 필터 - if (!empty($filters['manager_user_id'])) { + if (! empty($filters['manager_user_id'])) { $query->where('manager_user_id', $filters['manager_user_id']); } // 지급예정 기간 범위 필터 - if (!empty($filters['scheduled_start_year']) && !empty($filters['scheduled_start_month']) - && !empty($filters['scheduled_end_year']) && !empty($filters['scheduled_end_month'])) { + if (! empty($filters['scheduled_start_year']) && ! empty($filters['scheduled_start_month']) + && ! empty($filters['scheduled_end_year']) && ! empty($filters['scheduled_end_month'])) { $startDate = \Carbon\Carbon::create($filters['scheduled_start_year'], $filters['scheduled_start_month'], 1)->startOfMonth(); $endDate = \Carbon\Carbon::create($filters['scheduled_end_year'], $filters['scheduled_end_month'], 1)->endOfMonth(); $query->whereBetween('scheduled_payment_date', [$startDate, $endDate]); } // 지급예정 년/월 필터 (단일) - elseif (!empty($filters['scheduled_year']) && !empty($filters['scheduled_month'])) { + elseif (! empty($filters['scheduled_year']) && ! empty($filters['scheduled_month'])) { $query->forScheduledMonth((int) $filters['scheduled_year'], (int) $filters['scheduled_month']); } // 입금일 기간 필터 - if (!empty($filters['payment_start_date']) && !empty($filters['payment_end_date'])) { + if (! empty($filters['payment_start_date']) && ! empty($filters['payment_end_date'])) { $query->paymentDateBetween($filters['payment_start_date'], $filters['payment_end_date']); } // 수당유형 필터 - if (!empty($filters['commission_type'])) { + if (! empty($filters['commission_type'])) { $commissionType = $filters['commission_type']; if ($commissionType === 'partner') { $query->where('partner_commission', '>', 0); @@ -92,7 +94,7 @@ public function getCommissions(array $filters = [], int $perPage = 20): LengthAw } // 고객사 검색 (management → tenant 또는 tenantProspect) - if (!empty($filters['search'])) { + if (! empty($filters['search'])) { $search = $filters['search']; $query->whereHas('management', function ($q) use ($search) { $q->where(function ($sub) use ($search) { @@ -144,10 +146,10 @@ public function createCommission(int $managementId, string $paymentType, float $ // 영업파트너 resolve (fallback: tenantProspect → registeredBy → salesPartner) $partner = $management->salesPartner; - if (!$partner) { + if (! $partner) { $partner = $management->tenantProspect?->registeredBy?->salesPartner; } - if (!$partner) { + if (! $partner) { throw new \Exception('영업파트너가 지정되지 않았습니다.'); } @@ -271,7 +273,7 @@ public function approve(int $commissionId, int $approverId): SalesCommission $this->recalculateCommission($commission); } - if (!$commission->approve($approverId)) { + if (! $commission->approve($approverId)) { throw new \Exception('승인할 수 없는 상태입니다.'); } @@ -311,7 +313,7 @@ public function markAsPaid(int $commissionId, ?string $bankReference = null): Sa { $commission = SalesCommission::findOrFail($commissionId); - if (!$commission->markAsPaid($bankReference)) { + if (! $commission->markAsPaid($bankReference)) { throw new \Exception('지급완료 처리할 수 없는 상태입니다.'); } @@ -357,7 +359,7 @@ public function unapprove(int $commissionId): SalesCommission { $commission = SalesCommission::findOrFail($commissionId); - if (!$commission->unapprove()) { + if (! $commission->unapprove()) { throw new \Exception('승인취소할 수 없는 상태입니다.'); } @@ -371,7 +373,7 @@ public function cancel(int $commissionId): SalesCommission { $commission = SalesCommission::findOrFail($commissionId); - if (!$commission->cancel()) { + if (! $commission->cancel()) { throw new \Exception('취소할 수 없는 상태입니다.'); } @@ -401,7 +403,7 @@ public function getPartnerCommissionSummary(int $partnerId): array // 이번 달 지급예정 (승인 완료된 건) 'scheduled_this_month' => $commissions ->where('status', SalesCommission::STATUS_APPROVED) - ->filter(fn($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) + ->filter(fn ($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) ->sum('partner_commission'), // 누적 수령 수당 @@ -416,7 +418,7 @@ public function getPartnerCommissionSummary(int $partnerId): array // 이번 달 신규 계약 건수 'contracts_this_month' => $commissions - ->filter(fn($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) + ->filter(fn ($c) => $c->payment_date >= $thisMonthStart && $c->payment_date <= $thisMonthEnd) ->count(), // 1차 수당 상세 @@ -485,7 +487,7 @@ public function getManagerCommissionSummary(int $managerUserId): array // 이번 달 지급예정 (승인 완료된 건) 'scheduled_this_month' => $commissions ->where('status', SalesCommission::STATUS_APPROVED) - ->filter(fn($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) + ->filter(fn ($c) => $c->scheduled_payment_date->format('Y-m') === $thisMonth) ->sum('manager_commission'), // 누적 수령 수당 @@ -616,16 +618,16 @@ private function recalculateCommission(SalesCommission $commission): void 'tenantProspect.registeredBy.salesPartner', ])->find($commission->management_id); - if (!$management) { + if (! $management) { return; } // 파트너 resolve (fallback: tenantProspect → registeredBy → salesPartner) $partner = $management->salesPartner; - if (!$partner) { + if (! $partner) { $partner = $management->tenantProspect?->registeredBy?->salesPartner; } - if (!$partner) { + if (! $partner) { return; } diff --git a/app/Services/SidebarMenuService.php b/app/Services/SidebarMenuService.php index 58f202d6..8c7362f0 100644 --- a/app/Services/SidebarMenuService.php +++ b/app/Services/SidebarMenuService.php @@ -180,8 +180,8 @@ public function isMenuActive(Menu $menu): bool // prefix 매칭: 더 구체적인 메뉴가 있으면 덜 구체적인 prefix 매칭 비활성화 // (예: /esign/templates/5/fields 접속 시, /esign/templates 메뉴만 활성화, /esign 메뉴는 비활성) if (str_starts_with($currentPath, $menu->url.'/')) { - return !self::hasExactMenuMatch($currentPath) - && !self::hasMoreSpecificPrefixMenu($currentPath, $menu->url); + return ! self::hasExactMenuMatch($currentPath) + && ! self::hasMoreSpecificPrefixMenu($currentPath, $menu->url); } return false; @@ -275,7 +275,7 @@ private static function hasExactMenuMatch(string $currentPath): bool private static function hasMoreSpecificPrefixMenu(string $currentPath, string $menuUrl): bool { - $cacheKey = $currentPath . '|' . $menuUrl; + $cacheKey = $currentPath.'|'.$menuUrl; if (isset(self::$prefixMenuCache[$cacheKey])) { return self::$prefixMenuCache[$cacheKey]; } @@ -293,6 +293,7 @@ private static function hasMoreSpecificPrefixMenu(string $currentPath, string $m ->exists(); self::$prefixMenuCache[$cacheKey] = $result; + return $result; } diff --git a/app/Services/TradingPartnerOcrService.php b/app/Services/TradingPartnerOcrService.php index ba25f4ed..2d424265 100644 --- a/app/Services/TradingPartnerOcrService.php +++ b/app/Services/TradingPartnerOcrService.php @@ -37,19 +37,19 @@ private function callVertexAiApi(AiConfig $config, string $base64Image): array $projectId = $config->getProjectId(); $region = $config->getRegion(); - if (!$projectId) { + if (! $projectId) { throw new \RuntimeException('Vertex AI 프로젝트 ID가 설정되지 않았습니다.'); } $accessToken = $this->getAccessToken($config); - if (!$accessToken) { + if (! $accessToken) { throw new \RuntimeException('Google Cloud 인증 실패'); } $url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent"; return $this->callGeminiApi($url, $base64Image, [ - 'Authorization' => 'Bearer ' . $accessToken, + 'Authorization' => 'Bearer '.$accessToken, 'Content-Type' => 'application/json', ], true); } @@ -122,7 +122,7 @@ private function callGeminiApi(string $url, string $base64Image, array $headers, 'status' => $response->status(), 'body' => $response->body(), ]); - throw new \RuntimeException('AI API 호출 실패: ' . $response->status()); + throw new \RuntimeException('AI API 호출 실패: '.$response->status()); } $result = $response->json(); @@ -169,14 +169,16 @@ private function getAccessToken(AiConfig $config): ?string } } - if (!$serviceAccountPath) { + if (! $serviceAccountPath) { Log::error('Service account file not found', ['tried_paths' => $possiblePaths]); + return null; } $serviceAccount = json_decode(file_get_contents($serviceAccountPath), true); - if (!$serviceAccount) { + if (! $serviceAccount) { Log::error('Service account JSON parse failed'); + return null; } @@ -191,13 +193,14 @@ private function getAccessToken(AiConfig $config): ?string ])); $privateKey = openssl_pkey_get_private($serviceAccount['private_key']); - if (!$privateKey) { + if (! $privateKey) { Log::error('Failed to load private key'); + return null; } - openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); - $jwt = $jwtHeader . '.' . $jwtClaim . '.' . $this->base64UrlEncode($signature); + openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256); + $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature); try { $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ @@ -207,6 +210,7 @@ private function getAccessToken(AiConfig $config): ?string if ($response->successful()) { $data = $response->json(); + return $data['access_token'] ?? null; } @@ -214,9 +218,11 @@ private function getAccessToken(AiConfig $config): ?string 'status' => $response->status(), 'body' => $response->body(), ]); + return null; } catch (\Exception $e) { Log::error('OAuth token request exception', ['error' => $e->getMessage()]); + return null; } } @@ -234,7 +240,7 @@ private function base64UrlEncode(string $data): string */ private function buildPrompt(): string { - return <<cnt; + ', [$dbName, $table])->cnt; $tableTriggers = $triggers->where('EVENT_OBJECT_TABLE', $table); @@ -205,8 +205,8 @@ private function createTriggersForTable(string $dbName, string $table): void return; } - $newJson = 'JSON_OBJECT(' . collect($cols)->map(fn ($c) => "'{$c}', NEW.`{$c}`")->implode(', ') . ')'; - $oldJson = 'JSON_OBJECT(' . collect($cols)->map(fn ($c) => "'{$c}', OLD.`{$c}`")->implode(', ') . ')'; + $newJson = 'JSON_OBJECT('.collect($cols)->map(fn ($c) => "'{$c}', NEW.`{$c}`")->implode(', ').')'; + $oldJson = 'JSON_OBJECT('.collect($cols)->map(fn ($c) => "'{$c}', OLD.`{$c}`")->implode(', ').')'; $changedCols = collect($cols)->map(fn ($c) => "IF(NOT (NEW.`{$c}` <=> OLD.`{$c}`), '{$c}', NULL)")->implode(', '); $changeCheck = collect($cols)->map(fn ($c) => "NOT (NEW.`{$c}` <=> OLD.`{$c}`)")->implode(' OR '); diff --git a/app/Services/Video/BgmService.php b/app/Services/Video/BgmService.php index c9f6de5d..9c3c49c6 100644 --- a/app/Services/Video/BgmService.php +++ b/app/Services/Video/BgmService.php @@ -264,14 +264,14 @@ public function generateAmbient(string $mood, int $durationSec, string $savePath foreach ($chords as $i => $freq) { $vol = 0.06 - ($i * 0.01); // 각 음의 볼륨을 점차 줄여 자연스럽게 $modFreq = 0.08 + ($i * 0.03); // 각 음의 떨림 속도를 다르게 - $expr .= ($expr ? ' + ' : '') . "{$vol}*sin({$freq}*2*PI*t)*((0.5+0.5*sin({$modFreq}*2*PI*t)))"; + $expr .= ($expr ? ' + ' : '')."{$vol}*sin({$freq}*2*PI*t)*((0.5+0.5*sin({$modFreq}*2*PI*t)))"; } // 페이드인(3초) + 페이드아웃(3초) 추가 $cmd = sprintf( 'ffmpeg -y -f lavfi -i "aevalsrc=%s:s=44100:d=%d" ' - . '-af "lowpass=f=1500,highpass=f=80,afade=t=in:d=3,afade=t=out:st=%d:d=3" ' - . '-c:a libmp3lame -q:a 4 %s 2>&1', + .'-af "lowpass=f=1500,highpass=f=80,afade=t=in:d=3,afade=t=out:st=%d:d=3" ' + .'-c:a libmp3lame -q:a 4 %s 2>&1', escapeshellarg($expr), $durationSec + 2, // 페이드아웃 여유분 max(0, $durationSec - 2), diff --git a/app/Services/Video/GeminiScriptService.php b/app/Services/Video/GeminiScriptService.php index b7975e67..ab79d639 100644 --- a/app/Services/Video/GeminiScriptService.php +++ b/app/Services/Video/GeminiScriptService.php @@ -31,7 +31,7 @@ public function filterHealthTrending(array $trendingKeywords): array $keywordList = collect($trendingKeywords)->map(function ($item, $i) { $news = ! empty($item['news_title']) ? " (뉴스: {$item['news_title']})" : ''; - return ($i + 1) . ". {$item['keyword']}{$news}"; + return ($i + 1).". {$item['keyword']}{$news}"; })->implode("\n"); $prompt = <<take(10)->map(function ($item, $i) { $news = ! empty($item['news_title']) ? " (뉴스: {$item['news_title']})" : ''; - return ($i + 1) . ". {$item['keyword']}{$news}"; + return ($i + 1).". {$item['keyword']}{$news}"; })->implode("\n"); $prompt = << '...'], ['inlineData' => ['mimeType' => '...', 'data' => '...']]] + * @param array $parts [['text' => '...'], ['inlineData' => ['mimeType' => '...', 'data' => '...']]] */ public function callGeminiWithParts(array $parts, float $temperature = 0.9, int $maxTokens = 4096): ?string { diff --git a/app/Services/Video/SlideAnnotationService.php b/app/Services/Video/SlideAnnotationService.php index 56ed8ecf..c2562d50 100644 --- a/app/Services/Video/SlideAnnotationService.php +++ b/app/Services/Video/SlideAnnotationService.php @@ -7,8 +7,11 @@ class SlideAnnotationService { private const TARGET_WIDTH = 1920; + private const TARGET_HEIGHT = 1080; + private const CAPTION_HEIGHT = 180; + private const MARKER_RADIUS = 35; private string $fontPath; @@ -25,11 +28,11 @@ public function __construct() /** * 스크린샷 위에 시각적 어노테이션 추가 * - * @param string $imagePath 원본 스크린샷 경로 - * @param array $uiElements UI 요소 배열 [{type, label, x, y, description}] - * @param int $stepNumber 현재 스텝 번호 - * @param string $caption 하단 캡션 텍스트 - * @param string $outputPath 출력 파일 경로 + * @param string $imagePath 원본 스크린샷 경로 + * @param array $uiElements UI 요소 배열 [{type, label, x, y, description}] + * @param int $stepNumber 현재 스텝 번호 + * @param string $caption 하단 캡션 텍스트 + * @param string $outputPath 출력 파일 경로 * @return string|null 성공 시 출력 경로 */ public function annotateSlide( @@ -41,6 +44,7 @@ public function annotateSlide( ): ?string { if (! file_exists($imagePath)) { Log::error("SlideAnnotation: 원본 이미지 없음 - {$imagePath}"); + return null; } @@ -94,11 +98,12 @@ public function annotateSlide( imagepng($canvas, $outputPath, 6); imagedestroy($canvas); - Log::info("SlideAnnotation: 슬라이드 생성 완료", ['output' => $outputPath]); + Log::info('SlideAnnotation: 슬라이드 생성 완료', ['output' => $outputPath]); return $outputPath; } catch (\Exception $e) { - Log::error("SlideAnnotation: 예외 발생", ['error' => $e->getMessage()]); + Log::error('SlideAnnotation: 예외 발생', ['error' => $e->getMessage()]); + return null; } } @@ -240,6 +245,7 @@ public function annotateSlideWithSpotlight( ): ?string { if (! file_exists($imagePath)) { Log::error("SlideAnnotation: 원본 이미지 없음 - {$imagePath}"); + return null; } @@ -353,11 +359,12 @@ public function annotateSlideWithSpotlight( imagepng($canvas, $outputPath, 6); imagedestroy($canvas); - Log::info("SlideAnnotation: 스포트라이트 슬라이드 생성 완료", ['output' => $outputPath]); + Log::info('SlideAnnotation: 스포트라이트 슬라이드 생성 완료', ['output' => $outputPath]); return $outputPath; } catch (\Exception $e) { - Log::error("SlideAnnotation: 스포트라이트 예외 발생", ['error' => $e->getMessage()]); + Log::error('SlideAnnotation: 스포트라이트 예외 발생', ['error' => $e->getMessage()]); + return null; } } @@ -418,7 +425,8 @@ public function createIntroSlide(string $title, string $outputPath): ?string return $outputPath; } catch (\Exception $e) { - Log::error("SlideAnnotation: 인트로 슬라이드 생성 실패", ['error' => $e->getMessage()]); + Log::error('SlideAnnotation: 인트로 슬라이드 생성 실패', ['error' => $e->getMessage()]); + return null; } } @@ -474,7 +482,8 @@ public function createOutroSlide(string $title, string $outputPath): ?string return $outputPath; } catch (\Exception $e) { - Log::error("SlideAnnotation: 아웃트로 슬라이드 생성 실패", ['error' => $e->getMessage()]); + Log::error('SlideAnnotation: 아웃트로 슬라이드 생성 실패', ['error' => $e->getMessage()]); + return null; } } @@ -540,6 +549,7 @@ private function loadImage(string $path): ?\GdImage $info = getimagesize($path); if (! $info) { Log::error("SlideAnnotation: 이미지 정보를 읽을 수 없음 - {$path}"); + return null; } @@ -566,7 +576,7 @@ private function wrapText(string $text, int $maxChars): string $currentLine = ''; foreach ($words as $word) { - if (mb_strlen($currentLine . $word) > $maxChars && $currentLine !== '') { + if (mb_strlen($currentLine.$word) > $maxChars && $currentLine !== '') { $lines[] = trim($currentLine); $currentLine = $word; } else { diff --git a/app/Services/Video/TutorialAssemblyService.php b/app/Services/Video/TutorialAssemblyService.php index 6ced1dc2..0c63b48f 100644 --- a/app/Services/Video/TutorialAssemblyService.php +++ b/app/Services/Video/TutorialAssemblyService.php @@ -16,12 +16,12 @@ public function __construct(VideoAssemblyService $videoAssembly) /** * 어노테이션 이미지들 → MP4 영상 합성 * - * @param array $slidePaths 슬라이드 이미지 경로 배열 - * @param array $durations 각 슬라이드 표시 시간(초) 배열 - * @param string|null $narrationPath 나레이션 오디오 경로 - * @param string|null $bgmPath BGM 오디오 경로 - * @param string $subtitlePath ASS 자막 파일 경로 - * @param string $outputPath 최종 MP4 출력 경로 + * @param array $slidePaths 슬라이드 이미지 경로 배열 + * @param array $durations 각 슬라이드 표시 시간(초) 배열 + * @param string|null $narrationPath 나레이션 오디오 경로 + * @param string|null $bgmPath BGM 오디오 경로 + * @param string $subtitlePath ASS 자막 파일 경로 + * @param string $outputPath 최종 MP4 출력 경로 * @return string|null 성공 시 출력 경로 */ public function assembleFromImages( @@ -34,6 +34,7 @@ public function assembleFromImages( ): ?string { if (empty($slidePaths)) { Log::error('TutorialAssembly: 슬라이드가 없습니다'); + return null; } @@ -48,6 +49,7 @@ public function assembleFromImages( if (! $silentVideo) { Log::error('TutorialAssembly: 이미지→영상 변환 실패'); + return null; } @@ -93,6 +95,7 @@ private function imagesToVideo(array $slidePaths, array $durations, string $outp Log::error('TutorialAssembly: 단일 이미지 변환 실패', [ 'output' => implode("\n", array_slice($output, -10)), ]); + return null; } @@ -122,6 +125,7 @@ private function imagesToVideo(array $slidePaths, array $durations, string $outp Log::error("TutorialAssembly: 클립 {$i} 변환 실패", [ 'output' => implode("\n", array_slice($output, -10)), ]); + // 실패 시 crossfade 없이 fallback return $this->imagesToVideoSimple($slidePaths, $durations, $outputPath); } @@ -169,6 +173,7 @@ private function xfadeConcat(array $clipPaths, array $durations, float $fadeDura Log::warning('TutorialAssembly: xfade 실패, concat fallback', [ 'output' => implode("\n", array_slice($output, -10)), ]); + return $this->simpleConcatClips($clipPaths, $outputPath); } @@ -178,7 +183,7 @@ private function xfadeConcat(array $clipPaths, array $durations, float $fadeDura // 3개 이상: 체인 xfade $inputs = ''; foreach ($clipPaths as $path) { - $inputs .= '-i ' . escapeshellarg($path) . ' '; + $inputs .= '-i '.escapeshellarg($path).' '; } $filter = ''; @@ -188,8 +193,8 @@ private function xfadeConcat(array $clipPaths, array $durations, float $fadeDura $cumulativeOffset += ($durations[$i] ?? 8) - $fadeDuration; $inputA = ($i === 0) ? '[0:v]' : "[v{$i}]"; - $inputB = '[' . ($i + 1) . ':v]'; - $outputLabel = ($i === $count - 2) ? '[v]' : "[v" . ($i + 1) . "]"; + $inputB = '['.($i + 1).':v]'; + $outputLabel = ($i === $count - 2) ? '[v]' : '[v'.($i + 1).']'; $filter .= "{$inputA}{$inputB}xfade=transition=fade:duration={$fadeDuration}:offset={$cumulativeOffset}{$outputLabel}"; @@ -211,6 +216,7 @@ private function xfadeConcat(array $clipPaths, array $durations, float $fadeDura Log::warning('TutorialAssembly: 체인 xfade 실패, concat fallback', [ 'output' => implode("\n", array_slice($output, -10)), ]); + return $this->simpleConcatClips($clipPaths, $outputPath); } @@ -227,7 +233,7 @@ private function simpleConcatClips(array $clipPaths, string $outputPath): ?strin $listContent = ''; foreach ($clipPaths as $path) { - $listContent .= "file " . escapeshellarg($path) . "\n"; + $listContent .= 'file '.escapeshellarg($path)."\n"; } file_put_contents($listFile, $listContent); @@ -245,6 +251,7 @@ private function simpleConcatClips(array $clipPaths, string $outputPath): ?strin Log::error('TutorialAssembly: concat fallback도 실패', [ 'output' => implode("\n", array_slice($output, -10)), ]); + return null; } diff --git a/app/Services/Video/VeoVideoService.php b/app/Services/Video/VeoVideoService.php index 56fac0a9..4c59527f 100644 --- a/app/Services/Video/VeoVideoService.php +++ b/app/Services/Video/VeoVideoService.php @@ -40,7 +40,7 @@ public function generateClip(string $prompt, int $duration = 8): ?array // 한국인 여성 등장인물 프롬프트 프리픽스 추가 $characterPrefix = 'Featuring a young Korean woman in her 20s with natural black hair. '; - $fullPrompt = $characterPrefix . $prompt; + $fullPrompt = $characterPrefix.$prompt; $response = Http::withToken($token) ->timeout(60) @@ -118,7 +118,7 @@ public function checkOperation(string $operationName): array 'body' => substr($response->body(), 0, 500), ]); - return ['done' => false, 'video' => null, 'error' => 'HTTP ' . $response->status()]; + return ['done' => false, 'video' => null, 'error' => 'HTTP '.$response->status()]; } $data = $response->json(); @@ -195,7 +195,7 @@ public function waitAndSave(string $operationName, string $savePath, int $maxAtt 'operationName' => $operationName, ]); - return ['path' => null, 'error' => '연속 폴링 실패: ' . $result['error']]; + return ['path' => null, 'error' => '연속 폴링 실패: '.$result['error']]; } continue; @@ -227,7 +227,7 @@ public function waitAndSave(string $operationName, string $savePath, int $maxAtt if (($i + 1) % 3 === 0) { Log::info('VeoVideoService: 영상 생성 대기 중', [ 'attempt' => $i + 1, - 'elapsed' => ($i + 1) * 10 . '초', + 'elapsed' => ($i + 1) * 10 .'초', ]); } } @@ -237,6 +237,6 @@ public function waitAndSave(string $operationName, string $savePath, int $maxAtt 'attempts' => $maxAttempts, ]); - return ['path' => null, 'error' => '타임아웃 (' . ($maxAttempts * 10) . '초)']; + return ['path' => null, 'error' => '타임아웃 ('.($maxAttempts * 10).'초)']; } } diff --git a/app/Services/Video/VideoAssemblyService.php b/app/Services/Video/VideoAssemblyService.php index 244e7c34..267b0219 100644 --- a/app/Services/Video/VideoAssemblyService.php +++ b/app/Services/Video/VideoAssemblyService.php @@ -35,7 +35,7 @@ public function concatClips(array $clipPaths, string $outputPath): ?string $listContent = ''; foreach ($clipPaths as $path) { - $listContent .= "file " . escapeshellarg($path) . "\n"; + $listContent .= 'file '.escapeshellarg($path)."\n"; } file_put_contents($listFile, $listContent); @@ -67,7 +67,7 @@ public function concatClips(array $clipPaths, string $outputPath): ?string $scaledListFile = "{$dir}/scaled_list.txt"; $scaledListContent = ''; foreach ($scaledPaths as $path) { - $scaledListContent .= "file " . escapeshellarg($path) . "\n"; + $scaledListContent .= 'file '.escapeshellarg($path)."\n"; } file_put_contents($scaledListFile, $scaledListContent); @@ -151,7 +151,7 @@ public function concatNarrations(array $audioPaths, array $scenes, string $outpu $listFile = "{$dir}/narration_list.txt"; $listContent = ''; foreach ($paddedPaths as $path) { - $listContent .= "file " . escapeshellarg($path) . "\n"; + $listContent .= 'file '.escapeshellarg($path)."\n"; } file_put_contents($listFile, $listContent); @@ -209,7 +209,7 @@ public function getAudioDuration(string $path): float * @param array $narrationDurations [scene_number => 실제 오디오 초] (ffprobe 측정값) */ /** - * @param string $layout 'portrait' (9:16 Shorts) 또는 'landscape' (16:9 튜토리얼) + * @param string $layout 'portrait' (9:16 Shorts) 또는 'landscape' (16:9 튜토리얼) */ public function generateAssSubtitle(array $scenes, string $outputPath, array $narrationDurations = [], string $layout = 'portrait'): string { @@ -306,7 +306,7 @@ public function generateAssSubtitle(array $scenes, string $outputPath, array $na $endTime = $this->formatAssTime($currentTime + $offset + $sentDuration); $text = $this->wrapText($sentence, $maxCharsPerLine); - $text = str_replace("\n", "\\N", $text); + $text = str_replace("\n", '\\N', $text); $ass .= "Dialogue: 0,{$startTime},{$endTime},Default,,0,0,0,,{$text}\n"; @@ -379,7 +379,7 @@ private function splitIntoSentences(string $text): array if (mb_strlen($part) < 5 && ! empty($merged)) { // 짧은 조각 → 이전 항목 뒤에 붙임 - $merged[count($merged) - 1] .= ' ' . $part; + $merged[count($merged) - 1] .= ' '.$part; } else { $merged[] = $part; } @@ -403,21 +403,21 @@ public function assemble( mkdir($dir, 0755, true); } - $inputs = ['-i ' . escapeshellarg($videoPath)]; + $inputs = ['-i '.escapeshellarg($videoPath)]; $filterParts = []; $mapParts = ['-map 0:v']; $audioIndex = 1; // 나레이션 추가 if ($narrationPath && file_exists($narrationPath)) { - $inputs[] = '-i ' . escapeshellarg($narrationPath); + $inputs[] = '-i '.escapeshellarg($narrationPath); $filterParts[] = "[{$audioIndex}:a]volume=2.0[nar]"; $audioIndex++; } // BGM 추가 if ($bgmPath && file_exists($bgmPath)) { - $inputs[] = '-i ' . escapeshellarg($bgmPath); + $inputs[] = '-i '.escapeshellarg($bgmPath); $filterParts[] = "[{$audioIndex}:a]volume=1.2[bgm]"; $audioIndex++; } @@ -425,35 +425,35 @@ public function assemble( // 오디오 믹싱 필터 if (count($filterParts) === 2) { // 나레이션 + BGM - $filterComplex = implode(';', $filterParts) . ';[nar][bgm]amix=inputs=2:duration=first[a]'; + $filterComplex = implode(';', $filterParts).';[nar][bgm]amix=inputs=2:duration=first[a]'; $mapParts[] = '-map "[a]"'; } elseif (count($filterParts) === 1) { // 나레이션 또는 BGM 중 하나만 $streamName = $narrationPath ? 'nar' : 'bgm'; $filterComplex = $filterParts[0]; - $mapParts[] = '-map "[' . $streamName . ']"'; + $mapParts[] = '-map "['.$streamName.']"'; } else { $filterComplex = null; } // FFmpeg 명령 조립 - $cmd = 'ffmpeg -y ' . implode(' ', $inputs); + $cmd = 'ffmpeg -y '.implode(' ', $inputs); if ($filterComplex) { - $cmd .= ' -filter_complex "' . $filterComplex . '"'; + $cmd .= ' -filter_complex "'.$filterComplex.'"'; } - $cmd .= ' ' . implode(' ', $mapParts); + $cmd .= ' '.implode(' ', $mapParts); // 자막 비디오 필터 (슬라이드 캡션바에 텍스트 포함 시 생략 가능) if ($subtitlePath && file_exists($subtitlePath)) { - $vf = sprintf("ass=%s", escapeshellarg($subtitlePath)); - $cmd .= ' -vf ' . escapeshellarg($vf); + $vf = sprintf('ass=%s', escapeshellarg($subtitlePath)); + $cmd .= ' -vf '.escapeshellarg($vf); } $cmd .= ' -c:v libx264 -preset fast -crf 23 -r 30'; $cmd .= ' -c:a aac -b:a 192k'; $cmd .= ' -shortest'; - $cmd .= ' ' . escapeshellarg($outputPath); + $cmd .= ' '.escapeshellarg($outputPath); $cmd .= ' 2>&1'; Log::info('VideoAssemblyService: 최종 합성 시작', ['cmd' => $cmd]); @@ -504,11 +504,11 @@ private function wrapText(string $text, int $maxCharsPerLine): string $currentLine = ''; foreach ($words as $word) { - if (mb_strlen($currentLine . ' ' . $word) > $maxCharsPerLine && $currentLine !== '') { + if (mb_strlen($currentLine.' '.$word) > $maxCharsPerLine && $currentLine !== '') { $lines[] = trim($currentLine); $currentLine = $word; } else { - $currentLine .= ($currentLine ? ' ' : '') . $word; + $currentLine .= ($currentLine ? ' ' : '').$word; } } diff --git a/config/categories.php b/config/categories.php index 457fbb10..11b282c6 100644 --- a/config/categories.php +++ b/config/categories.php @@ -24,4 +24,4 @@ 'process_type' => '공정 유형', 'procurement_type' => '조달 유형', ], -]; \ No newline at end of file +]; diff --git a/config/database.php b/config/database.php index 6da81efc..034e1694 100644 --- a/config/database.php +++ b/config/database.php @@ -83,7 +83,6 @@ ]) : [], ], - 'mariadb' => [ 'driver' => 'mariadb', 'url' => env('DB_URL'), diff --git a/config/fcm.php b/config/fcm.php index 2886ab2c..c5ea8840 100644 --- a/config/fcm.php +++ b/config/fcm.php @@ -38,13 +38,13 @@ | */ 'channels' => [ - 'default' => 'push_default', - 'vendor_register' => 'push_vendor_register', + 'default' => 'push_default', + 'vendor_register' => 'push_vendor_register', 'approval_request' => 'push_approval_request', - 'income' => 'push_income', - 'sales_order' => 'push_sales_order', - 'purchase_order' => 'push_purchase_order', - 'contract' => 'push_contract', + 'income' => 'push_income', + 'sales_order' => 'push_sales_order', + 'purchase_order' => 'push_purchase_order', + 'contract' => 'push_contract', ], /* diff --git a/database/seeders/AccountCodeSeeder.php b/database/seeders/AccountCodeSeeder.php index 74e92b69..64090b08 100644 --- a/database/seeders/AccountCodeSeeder.php +++ b/database/seeders/AccountCodeSeeder.php @@ -2,8 +2,8 @@ namespace Database\Seeders; -use Illuminate\Database\Seeder; use App\Models\Barobill\AccountCode; +use Illuminate\Database\Seeder; /** * 더존 표준 계정과목 시더 @@ -34,7 +34,7 @@ public function run(): void ]); } - $this->command->info("계정과목 " . count($accountCodes) . "개 생성 완료"); + $this->command->info('계정과목 '.count($accountCodes).'개 생성 완료'); } /** diff --git a/database/seeders/AdminProspectMenuSeeder.php b/database/seeders/AdminProspectMenuSeeder.php index d0243a69..4bd0b994 100644 --- a/database/seeders/AdminProspectMenuSeeder.php +++ b/database/seeders/AdminProspectMenuSeeder.php @@ -21,8 +21,9 @@ public function run(): void ->whereNull('parent_id') ->value('id'); - if (!$salesParentId) { + if (! $salesParentId) { $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -42,6 +43,7 @@ public function run(): void if ($existingMenu) { $this->command->info('영업파트너 고객관리 메뉴가 이미 존재합니다.'); + return; } @@ -56,7 +58,7 @@ public function run(): void 'is_active' => true, ]); - $this->command->info('영업파트너 고객관리 메뉴 생성 완료 (sort_order: ' . $sortOrder . ')'); + $this->command->info('영업파트너 고객관리 메뉴 생성 완료 (sort_order: '.$sortOrder.')'); // 결과 출력 $this->command->info(''); diff --git a/database/seeders/AiTokenUsageMenuSeeder.php b/database/seeders/AiTokenUsageMenuSeeder.php index 905c9d4c..3c851df4 100644 --- a/database/seeders/AiTokenUsageMenuSeeder.php +++ b/database/seeders/AiTokenUsageMenuSeeder.php @@ -46,11 +46,11 @@ public function run(): void 'parent_id' => $aiGroup->id, 'sort_order' => 1, ]); - $this->command->info("AI 설정 메뉴를 AI 관리 그룹으로 이동 완료"); + $this->command->info('AI 설정 메뉴를 AI 관리 그룹으로 이동 완료'); } elseif (! $aiConfig) { - $this->command->warn("AI 설정 메뉴가 존재하지 않습니다. (건너뜀)"); + $this->command->warn('AI 설정 메뉴가 존재하지 않습니다. (건너뜀)'); } else { - $this->command->info("AI 설정 메뉴가 이미 AI 관리 그룹에 있습니다."); + $this->command->info('AI 설정 메뉴가 이미 AI 관리 그룹에 있습니다.'); } // 3. AI 토큰 사용량 메뉴 생성 또는 이동 @@ -65,7 +65,7 @@ public function run(): void 'parent_id' => $aiGroup->id, 'sort_order' => 2, ]); - $this->command->info("AI 토큰 사용량 메뉴를 AI 관리 그룹으로 이동 완료"); + $this->command->info('AI 토큰 사용량 메뉴를 AI 관리 그룹으로 이동 완료'); } elseif (! $aiToken) { Menu::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, @@ -76,9 +76,9 @@ public function run(): void 'sort_order' => 2, 'is_active' => true, ]); - $this->command->info("AI 토큰 사용량 메뉴 생성 완료"); + $this->command->info('AI 토큰 사용량 메뉴 생성 완료'); } else { - $this->command->info("AI 토큰 사용량 메뉴가 이미 AI 관리 그룹에 있습니다."); + $this->command->info('AI 토큰 사용량 메뉴가 이미 AI 관리 그룹에 있습니다.'); } // 4. AI 음성녹음 메뉴 생성 또는 이동 @@ -93,7 +93,7 @@ public function run(): void 'parent_id' => $aiGroup->id, 'sort_order' => 3, ]); - $this->command->info("AI 음성녹음 메뉴를 AI 관리 그룹으로 이동 완료"); + $this->command->info('AI 음성녹음 메뉴를 AI 관리 그룹으로 이동 완료'); } elseif (! $aiVoice) { Menu::withoutGlobalScopes()->create([ 'tenant_id' => $tenantId, @@ -104,9 +104,9 @@ public function run(): void 'sort_order' => 3, 'is_active' => true, ]); - $this->command->info("AI 음성녹음 메뉴 생성 완료"); + $this->command->info('AI 음성녹음 메뉴 생성 완료'); } else { - $this->command->info("AI 음성녹음 메뉴가 이미 AI 관리 그룹에 있습니다."); + $this->command->info('AI 음성녹음 메뉴가 이미 AI 관리 그룹에 있습니다.'); } // 결과 출력 diff --git a/database/seeders/BarobillPricingPolicySeeder.php b/database/seeders/BarobillPricingPolicySeeder.php index ff90105d..13110a56 100644 --- a/database/seeders/BarobillPricingPolicySeeder.php +++ b/database/seeders/BarobillPricingPolicySeeder.php @@ -2,8 +2,8 @@ namespace Database\Seeders; -use Illuminate\Database\Seeder; use App\Models\Barobill\BarobillPricingPolicy; +use Illuminate\Database\Seeder; class BarobillPricingPolicySeeder extends Seeder { diff --git a/database/seeders/DesignPriceSeeder.php b/database/seeders/DesignPriceSeeder.php index ab54a32d..742ea4cc 100644 --- a/database/seeders/DesignPriceSeeder.php +++ b/database/seeders/DesignPriceSeeder.php @@ -63,6 +63,7 @@ protected function seedPricesFromItems(): void if ($salesPrice <= 0) { $skipped++; + continue; } @@ -79,7 +80,7 @@ protected function seedPricesFromItems(): void 'sales_price' => $salesPrice, 'updated_by' => 1, ]); - $this->command->line(" 업데이트: {$item->code} => " . number_format($salesPrice)); + $this->command->line(" 업데이트: {$item->code} => ".number_format($salesPrice)); } else { // 새 단가 생성 Price::create([ @@ -102,10 +103,10 @@ protected function seedPricesFromItems(): void 'created_by' => 1, ]); $created++; - $this->command->line(" 생성: {$item->code} => " . number_format($salesPrice)); + $this->command->line(" 생성: {$item->code} => ".number_format($salesPrice)); } } $this->command->info(" 생성: {$created}개, 스킵(단가없음): {$skipped}개"); } -} \ No newline at end of file +} diff --git a/database/seeders/DevelopmentApprovalMenuSeeder.php b/database/seeders/DevelopmentApprovalMenuSeeder.php index 5343cc63..5cabe368 100644 --- a/database/seeders/DevelopmentApprovalMenuSeeder.php +++ b/database/seeders/DevelopmentApprovalMenuSeeder.php @@ -21,8 +21,9 @@ public function run(): void ->whereNull('parent_id') ->value('id'); - if (!$salesParentId) { + if (! $salesParentId) { $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -34,6 +35,7 @@ public function run(): void if ($existingMenu) { $this->command->info('개발 승인 메뉴가 이미 존재합니다.'); + return; } diff --git a/database/seeders/FixServerMenuSeeder.php b/database/seeders/FixServerMenuSeeder.php index 4560a0d5..8c1173af 100644 --- a/database/seeders/FixServerMenuSeeder.php +++ b/database/seeders/FixServerMenuSeeder.php @@ -18,8 +18,9 @@ public function run(): void ->whereNull('parent_id') ->first(); - if (!$targetGroup) { + if (! $targetGroup) { $this->command->error('고객/거래처/채권관리 그룹을 찾을 수 없습니다.'); + return; } @@ -41,10 +42,12 @@ public function run(): void ->where('name', $name) ->where('is_active', true) ->first(); - if ($menu) break; + if ($menu) { + break; + } } - if (!$menu) { + if (! $menu) { // 이미 이동된 경우 확인 $existing = Menu::withoutGlobalScopes() ->where('tenant_id', $tenantId) @@ -55,8 +58,9 @@ public function run(): void if ($existing) { $this->command->info(" 이미 이동됨: {$newName} (id: {$existing->id})"); } else { - $this->command->warn(" 메뉴 없음: " . implode(' / ', $searchNames)); + $this->command->warn(' 메뉴 없음: '.implode(' / ', $searchNames)); } + continue; } diff --git a/database/seeders/FundScheduleSeeder.php b/database/seeders/FundScheduleSeeder.php index d0e98e34..15ae9035 100644 --- a/database/seeders/FundScheduleSeeder.php +++ b/database/seeders/FundScheduleSeeder.php @@ -155,6 +155,6 @@ public function run(): void // 데이터 삽입 DB::table('fund_schedules')->insert($schedules); - $this->command->info('FundScheduleSeeder 완료: 자금계획일정 ' . count($schedules) . '개 생성'); + $this->command->info('FundScheduleSeeder 완료: 자금계획일정 '.count($schedules).'개 생성'); } } diff --git a/database/seeders/HolidayMenuSeeder.php b/database/seeders/HolidayMenuSeeder.php index 0b26fac8..5c12def9 100644 --- a/database/seeders/HolidayMenuSeeder.php +++ b/database/seeders/HolidayMenuSeeder.php @@ -16,8 +16,9 @@ public function run(): void ->where('name', '시스템 관리') ->first(); - if (!$parentMenu) { + if (! $parentMenu) { $this->command->error('시스템 관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -29,6 +30,7 @@ public function run(): void if ($existingMenu) { $this->command->info('달력 휴일 관리 메뉴가 이미 존재합니다.'); + return; } diff --git a/database/seeders/InterviewMenuSeeder.php b/database/seeders/InterviewMenuSeeder.php index c468144e..8c561960 100644 --- a/database/seeders/InterviewMenuSeeder.php +++ b/database/seeders/InterviewMenuSeeder.php @@ -18,6 +18,7 @@ public function run(): void if ($salesMenus->isEmpty()) { $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -34,6 +35,7 @@ public function run(): void if ($exists) { $this->command->info("[{$label}] 인터뷰 시나리오 메뉴가 이미 존재합니다."); + continue; } diff --git a/database/seeders/JournalEntryMenuSeeder.php b/database/seeders/JournalEntryMenuSeeder.php index d6a4cb44..f90f399b 100644 --- a/database/seeders/JournalEntryMenuSeeder.php +++ b/database/seeders/JournalEntryMenuSeeder.php @@ -17,8 +17,9 @@ public function run(): void ->where('name', '일일자금일보') ->first(); - if (!$dailyFundMenu) { + if (! $dailyFundMenu) { $this->command->error('일일자금일보 메뉴를 찾을 수 없습니다.'); + return; } @@ -33,6 +34,7 @@ public function run(): void if ($existingMenu) { $this->command->info('일반전표입력 메뉴가 이미 존재합니다.'); + return; } diff --git a/database/seeders/JuilPlanningMenuSeeder.php b/database/seeders/JuilPlanningMenuSeeder.php index 6cdad4b5..ca4f76a9 100644 --- a/database/seeders/JuilPlanningMenuSeeder.php +++ b/database/seeders/JuilPlanningMenuSeeder.php @@ -20,6 +20,7 @@ public function run(): void if ($existingParent) { $this->command->info('주일기업 기획 메뉴가 이미 존재합니다.'); + return; } diff --git a/database/seeders/PartnerMenuRenameSeeder.php b/database/seeders/PartnerMenuRenameSeeder.php index ee6aef1d..77c155ca 100644 --- a/database/seeders/PartnerMenuRenameSeeder.php +++ b/database/seeders/PartnerMenuRenameSeeder.php @@ -19,7 +19,7 @@ public function run(): void $menu = Menu::where('tenant_id', $tenantId) ->where(function ($q) { $q->where('name', '협력사관리') - ->orWhere('name', '협력사 관리'); + ->orWhere('name', '협력사 관리'); }) ->first(); diff --git a/database/seeders/PayableMenuRenameSeeder.php b/database/seeders/PayableMenuRenameSeeder.php index 78c7db6e..edb4d026 100644 --- a/database/seeders/PayableMenuRenameSeeder.php +++ b/database/seeders/PayableMenuRenameSeeder.php @@ -14,7 +14,7 @@ public function run(): void $menu = Menu::where('tenant_id', $tenantId) ->where(function ($q) { $q->where('name', '채무관리') - ->orWhere('name', '채무 관리'); + ->orWhere('name', '채무 관리'); }) ->first(); diff --git a/database/seeders/ProductInspectionTemplateSeeder.php b/database/seeders/ProductInspectionTemplateSeeder.php index 3f93a930..dd9bf299 100644 --- a/database/seeders/ProductInspectionTemplateSeeder.php +++ b/database/seeders/ProductInspectionTemplateSeeder.php @@ -375,4 +375,4 @@ private function cleanupExisting(string $name): void DocumentTemplateApprovalLine::where('template_id', $existing->id)->delete(); $existing->forceDelete(); } -} \ No newline at end of file +} diff --git a/database/seeders/QuoteFormulaCategorySeeder.php b/database/seeders/QuoteFormulaCategorySeeder.php index 66133230..cb21c1b7 100644 --- a/database/seeders/QuoteFormulaCategorySeeder.php +++ b/database/seeders/QuoteFormulaCategorySeeder.php @@ -73,6 +73,6 @@ public function run(): void ); } - $this->command->info('QuoteFormulaCategorySeeder: ' . count($categories) . '개 카테고리 생성 완료'); + $this->command->info('QuoteFormulaCategorySeeder: '.count($categories).'개 카테고리 생성 완료'); } -} \ No newline at end of file +} diff --git a/database/seeders/QuoteFormulaItemSeeder.php b/database/seeders/QuoteFormulaItemSeeder.php index 849e715c..ab90e514 100644 --- a/database/seeders/QuoteFormulaItemSeeder.php +++ b/database/seeders/QuoteFormulaItemSeeder.php @@ -168,8 +168,8 @@ public function run(): void ])); } - $this->command->info('QuoteFormulaItemSeeder: ' . count($formulaItems) . '개 품목 매핑 생성 완료'); - $this->command->info(' - FG-SCR-001: ' . (isset($products['FG-SCR-001']) ? '14개 품목' : '미생성')); - $this->command->info(' - FG-STL-001: ' . (isset($products['FG-STL-001']) ? '10개 품목' : '미생성')); + $this->command->info('QuoteFormulaItemSeeder: '.count($formulaItems).'개 품목 매핑 생성 완료'); + $this->command->info(' - FG-SCR-001: '.(isset($products['FG-SCR-001']) ? '14개 품목' : '미생성')); + $this->command->info(' - FG-STL-001: '.(isset($products['FG-STL-001']) ? '10개 품목' : '미생성')); } -} \ No newline at end of file +} diff --git a/database/seeders/QuoteFormulaRangeSeeder.php b/database/seeders/QuoteFormulaRangeSeeder.php index 23d32e8c..f140ea1e 100644 --- a/database/seeders/QuoteFormulaRangeSeeder.php +++ b/database/seeders/QuoteFormulaRangeSeeder.php @@ -128,9 +128,9 @@ public function run(): void ])); } - $this->command->info('QuoteFormulaRangeSeeder: ' . count($ranges) . '개 범위 조건 생성 완료'); - $this->command->info(' - 모터 선택: ' . (isset($rangeFormulas['MOTOR']) ? '4개' : '0개')); - $this->command->info(' - 가이드레일 선택: ' . (isset($rangeFormulas['GUIDE']) ? '4개' : '0개')); - $this->command->info(' - 케이스 선택: ' . (isset($rangeFormulas['CASE']) ? '4개' : '0개')); + $this->command->info('QuoteFormulaRangeSeeder: '.count($ranges).'개 범위 조건 생성 완료'); + $this->command->info(' - 모터 선택: '.(isset($rangeFormulas['MOTOR']) ? '4개' : '0개')); + $this->command->info(' - 가이드레일 선택: '.(isset($rangeFormulas['GUIDE']) ? '4개' : '0개')); + $this->command->info(' - 케이스 선택: '.(isset($rangeFormulas['CASE']) ? '4개' : '0개')); } -} \ No newline at end of file +} diff --git a/database/seeders/QuoteFormulaSeeder.php b/database/seeders/QuoteFormulaSeeder.php index dacbf577..dc9220c5 100644 --- a/database/seeders/QuoteFormulaSeeder.php +++ b/database/seeders/QuoteFormulaSeeder.php @@ -274,9 +274,9 @@ public function run(): void ])); } - $this->command->info('QuoteFormulaSeeder: ' . count($formulas) . '개 수식 생성 완료'); - $this->command->info(' - 입력변수: ' . count($inputVars) . '개'); - $this->command->info(' - 계산변수: ' . count($calcVars) . '개'); - $this->command->info(' - 범위선택: ' . count($rangeVars) . '개'); + $this->command->info('QuoteFormulaSeeder: '.count($formulas).'개 수식 생성 완료'); + $this->command->info(' - 입력변수: '.count($inputVars).'개'); + $this->command->info(' - 계산변수: '.count($calcVars).'개'); + $this->command->info(' - 범위선택: '.count($rangeVars).'개'); } -} \ No newline at end of file +} diff --git a/database/seeders/ReceivableMenuRenameSeeder.php b/database/seeders/ReceivableMenuRenameSeeder.php index 35c452c3..0f9fbb14 100644 --- a/database/seeders/ReceivableMenuRenameSeeder.php +++ b/database/seeders/ReceivableMenuRenameSeeder.php @@ -14,7 +14,7 @@ public function run(): void $menu = Menu::where('tenant_id', $tenantId) ->where(function ($q) { $q->where('name', '채권관리') - ->orWhere('name', '채권 관리'); + ->orWhere('name', '채권 관리'); }) ->first(); diff --git a/database/seeders/RefundMenuRenameSeeder.php b/database/seeders/RefundMenuRenameSeeder.php index 334f9a0c..6ad71d68 100644 --- a/database/seeders/RefundMenuRenameSeeder.php +++ b/database/seeders/RefundMenuRenameSeeder.php @@ -14,7 +14,7 @@ public function run(): void $menu = Menu::where('tenant_id', $tenantId) ->where(function ($q) { $q->where('name', '환불관리') - ->orWhere('name', '환불 관리'); + ->orWhere('name', '환불 관리'); }) ->first(); diff --git a/database/seeders/RemoveRnDLabsMenuSeeder.php b/database/seeders/RemoveRnDLabsMenuSeeder.php index ef9edc4d..7cd98416 100644 --- a/database/seeders/RemoveRnDLabsMenuSeeder.php +++ b/database/seeders/RemoveRnDLabsMenuSeeder.php @@ -15,8 +15,9 @@ public function run(): void ->where('name', 'R&D Labs') ->first(); - if (!$menu) { + if (! $menu) { $this->command->warn('R&D Labs 메뉴를 찾을 수 없습니다.'); + return; } diff --git a/database/seeders/ReorganizeFinanceMenuSeeder.php b/database/seeders/ReorganizeFinanceMenuSeeder.php index 86b5e9b3..dd253239 100644 --- a/database/seeders/ReorganizeFinanceMenuSeeder.php +++ b/database/seeders/ReorganizeFinanceMenuSeeder.php @@ -15,13 +15,13 @@ public function run(): void // 1. 새 대분류 그룹 생성 // ============================== $newGroups = [ - '재무/자금관리' => ['icon' => 'currency-dollar', 'sort' => 6], - '회계/세무관리' => ['icon' => 'calculator', 'sort' => 7], - '카드/차량관리' => ['icon' => 'credit-card', 'sort' => 8], - '정산관리' => ['icon' => 'cash', 'sort' => 9], - '고객/거래처/채권관리' => ['icon' => 'users', 'sort' => 10], - '영업/매출관리' => ['icon' => 'briefcase', 'sort' => 11], - '시스템/설정/내부관리' => ['icon' => 'settings', 'sort' => 12], + '재무/자금관리' => ['icon' => 'currency-dollar', 'sort' => 6], + '회계/세무관리' => ['icon' => 'calculator', 'sort' => 7], + '카드/차량관리' => ['icon' => 'credit-card', 'sort' => 8], + '정산관리' => ['icon' => 'cash', 'sort' => 9], + '고객/거래처/채권관리' => ['icon' => 'users', 'sort' => 10], + '영업/매출관리' => ['icon' => 'briefcase', 'sort' => 11], + '시스템/설정/내부관리' => ['icon' => 'settings', 'sort' => 12], ]; $parentIds = []; @@ -56,37 +56,37 @@ public function run(): void // [현재 이름 => [새 부모 그룹, 새 sort_order, 새 이름(null이면 변경 없음), 새 URL(null이면 변경 없음)]] $menuMoves = [ // --- 재무/자금관리 --- - '재무 대시보드' => ['재무/자금관리', 1, null, null], - '일일자금일보' => ['재무/자금관리', 2, null, null], - '자금계획일정' => ['재무/자금관리', 3, null, null], - '보유계좌관리' => ['재무/자금관리', 4, null, null], - '계좌입출금내역' => ['재무/자금관리', 5, null, null], + '재무 대시보드' => ['재무/자금관리', 1, null, null], + '일일자금일보' => ['재무/자금관리', 2, null, null], + '자금계획일정' => ['재무/자금관리', 3, null, null], + '보유계좌관리' => ['재무/자금관리', 4, null, null], + '계좌입출금내역' => ['재무/자금관리', 5, null, null], // --- 회계/세무관리 --- - '일반전표입력' => ['회계/세무관리', 1, null, null], - '전자세금계산서' => ['회계/세무관리', 2, null, null], + '일반전표입력' => ['회계/세무관리', 1, null, null], + '전자세금계산서' => ['회계/세무관리', 2, null, null], '홈택스 매출/매입' => ['회계/세무관리', 3, '홈택스매출/매입', null], - '부가세관리' => ['회계/세무관리', 4, null, null], + '부가세관리' => ['회계/세무관리', 4, null, null], // --- 카드/차량관리 --- - '법인카드관리' => ['카드/차량관리', 1, null, null], - '카드사용내역' => ['카드/차량관리', 2, null, null], - '법인차량관리' => ['카드/차량관리', 3, '차량목록', '/finance/corporate-vehicles'], + '법인카드관리' => ['카드/차량관리', 1, null, null], + '카드사용내역' => ['카드/차량관리', 2, null, null], + '법인차량관리' => ['카드/차량관리', 3, '차량목록', '/finance/corporate-vehicles'], // 차량일지는 아래에서 별도 생성 - '차량정비이력' => ['카드/차량관리', 5, '정비이력', null], + '차량정비이력' => ['카드/차량관리', 5, '정비이력', null], // --- 정산관리 --- - '영업수수료 정산' => ['정산관리', 1, '영업수수료정산', null], - '컨설팅비용 정산' => ['정산관리', 2, '컨설팅비용정산', null], - '고객사 정산' => ['정산관리', 3, '고객사정산', null], - '구독료 정산' => ['정산관리', 4, '구독료정산', null], + '영업수수료 정산' => ['정산관리', 1, '영업수수료정산', null], + '컨설팅비용 정산' => ['정산관리', 2, '컨설팅비용정산', null], + '고객사 정산' => ['정산관리', 3, '고객사정산', null], + '구독료 정산' => ['정산관리', 4, '구독료정산', null], // --- 고객/거래처/채권관리 --- - '거래처 관리' => ['고객/거래처/채권관리', 1, '거래처관리', null], - '고객사관리' => ['고객/거래처/채권관리', 2, null, null], - '채권관리' => ['고객/거래처/채권관리', 3, '미수금관리', null], - '채무관리' => ['고객/거래처/채권관리', 4, '미지급금관리', null], - '환불관리' => ['고객/거래처/채권관리', 5, '환불/해지관리', null], + '거래처 관리' => ['고객/거래처/채권관리', 1, '거래처관리', null], + '고객사관리' => ['고객/거래처/채권관리', 2, null, null], + '채권관리' => ['고객/거래처/채권관리', 3, '미수금관리', null], + '채무관리' => ['고객/거래처/채권관리', 4, '미지급금관리', null], + '환불관리' => ['고객/거래처/채권관리', 5, '환불/해지관리', null], // --- 영업/매출관리 --- (영업관리의 자식 중) // 대시보드는 이름이 겹칠 수 있으므로 ID로 처리 @@ -106,8 +106,9 @@ public function run(): void ->where('is_active', true) ->first(); - if (!$menu) { + if (! $menu) { $this->command->warn("메뉴 없음: {$currentName}"); + continue; } @@ -142,7 +143,7 @@ public function run(): void 'parent_id' => $parentIds['카드/차량관리'], 'sort_order' => 4, ]); - $this->command->info(" 이동: 차량일지 → 카드/차량관리 (sort: 4)"); + $this->command->info(' 이동: 차량일지 → 카드/차량관리 (sort: 4)'); } else { Menu::create([ 'tenant_id' => $tenantId, @@ -153,7 +154,7 @@ public function run(): void 'sort_order' => 4, 'is_active' => true, ]); - $this->command->info(" 신규: 차량일지 → 카드/차량관리 (sort: 4)"); + $this->command->info(' 신규: 차량일지 → 카드/차량관리 (sort: 4)'); } // ============================== @@ -212,7 +213,7 @@ public function run(): void 'parent_id' => $parentIds['영업/매출관리'], 'sort_order' => $nextSort++, ]); - $this->command->info(" 이동(잔여): {$child->name} → 영업/매출관리 (sort: " . ($nextSort - 1) . ")"); + $this->command->info(" 이동(잔여): {$child->name} → 영업/매출관리 (sort: ".($nextSort - 1).')'); } } @@ -256,7 +257,7 @@ public function run(): void 'url' => '/barobill/ecard', 'sort_order' => 1, ]); - $this->command->info(" 변환: 바로빌본사 → 시스템/설정/내부관리/바로빌본사 (sort: 1)"); + $this->command->info(' 변환: 바로빌본사 → 시스템/설정/내부관리/바로빌본사 (sort: 1)'); } // 바로빌(15504)의 자식들 이동 @@ -269,7 +270,7 @@ public function run(): void if ($barobillTenant) { $tenantMoves = [ '사용량조회' => 4, - '과금관리' => 5, + '과금관리' => 5, ]; $barobillChildren = Menu::withoutGlobalScopes() @@ -311,7 +312,7 @@ public function run(): void 'parent_id' => $sysGroupId, 'sort_order' => $nextSort++, ]); - $this->command->info(" 이동: {$child->name} → 시스템/설정/내부관리 (sort: " . ($nextSort - 1) . ")"); + $this->command->info(" 이동: {$child->name} → 시스템/설정/내부관리 (sort: ".($nextSort - 1).')'); } $sysOld->update(['is_active' => false]); diff --git a/database/seeders/SalesMenuFixSeeder.php b/database/seeders/SalesMenuFixSeeder.php index 001e0ede..040deaf9 100644 --- a/database/seeders/SalesMenuFixSeeder.php +++ b/database/seeders/SalesMenuFixSeeder.php @@ -43,8 +43,9 @@ public function run(): void ->whereNull('parent_id') ->value('id'); - if (!$salesParentId) { + if (! $salesParentId) { $this->command->error('영업관리 메뉴를 찾을 수 없습니다.'); + return; } diff --git a/database/seeders/SampleItemsSeeder.php b/database/seeders/SampleItemsSeeder.php index f132826f..e6d5e711 100644 --- a/database/seeders/SampleItemsSeeder.php +++ b/database/seeders/SampleItemsSeeder.php @@ -482,4 +482,4 @@ private function buildBomJson(int $tenantId, array $bomItems): string return json_encode($result); } -} \ No newline at end of file +} diff --git a/database/seeders/VehicleLogMenuSeeder.php b/database/seeders/VehicleLogMenuSeeder.php index be81b80d..633e4cda 100644 --- a/database/seeders/VehicleLogMenuSeeder.php +++ b/database/seeders/VehicleLogMenuSeeder.php @@ -20,8 +20,9 @@ public function run(): void ->where('name', '법인차량관리') ->first(); - if (!$vehicleMenu) { + if (! $vehicleMenu) { $this->command->error('법인차량관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -33,6 +34,7 @@ public function run(): void if ($existingMenu) { $this->command->info('차량일지 메뉴가 이미 존재합니다.'); + return; } diff --git a/database/seeders/VehicleMaintenanceMenuMoveSeeder.php b/database/seeders/VehicleMaintenanceMenuMoveSeeder.php index 3db4222f..94777aff 100644 --- a/database/seeders/VehicleMaintenanceMenuMoveSeeder.php +++ b/database/seeders/VehicleMaintenanceMenuMoveSeeder.php @@ -19,8 +19,9 @@ public function run(): void ->where('name', '법인차량관리') ->first(); - if (!$parentMenu) { + if (! $parentMenu) { $this->command->error('법인차량관리 메뉴를 찾을 수 없습니다.'); + return; } @@ -28,23 +29,25 @@ public function run(): void $maintenanceMenu = Menu::where('tenant_id', $tenantId) ->where(function ($q) { $q->where('name', '차량정비이력') - ->orWhere('name', '차량정비'); + ->orWhere('name', '차량정비'); }) ->first(); - if (!$maintenanceMenu) { + if (! $maintenanceMenu) { $this->command->error('차량정비이력 메뉴를 찾을 수 없습니다.'); Menu::where('tenant_id', $tenantId) ->whereNull('parent_id') ->orderBy('sort_order') ->get(['id', 'name', 'url']) ->each(fn ($m) => $this->command->line(" - [{$m->id}] {$m->name} ({$m->url})")); + return; } // 이미 법인차량관리 하위에 있는지 확인 if ($maintenanceMenu->parent_id == $parentMenu->id) { $this->command->info('차량정비이력은 이미 법인차량관리 하위에 있습니다.'); + return; } diff --git a/resources/views/finance/account-transactions.blade.php b/resources/views/finance/account-transactions.blade.php index feabc615..f1310e21 100644 --- a/resources/views/finance/account-transactions.blade.php +++ b/resources/views/finance/account-transactions.blade.php @@ -16,6 +16,19 @@ @push('scripts') @include('partials.react-cdn') + @verbatim