style: Pint 포맷팅 적용
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')));
|
||||
}
|
||||
|
||||
|
||||
@@ -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' => '정책을 찾을 수 없습니다.',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', '세금계산서(건)', '계좌조회(건)', '카드내역(건)', '홈텍스(건)', '과금액(원)'];
|
||||
|
||||
@@ -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' => '슈퍼관리자만 복원할 수 있습니다.',
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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')));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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', '다른 테넌트의 코드는 삭제할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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 설정이 없습니다.',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'])),
|
||||
];
|
||||
|
||||
// 카드 목록
|
||||
|
||||
@@ -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' => '컨설팅비가 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' => []];
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' => '정산이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' => '지출이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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' => '수입이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' => '매입이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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, [
|
||||
'번호', '테넌트', '입금구분', '입금액', '입금일',
|
||||
'기준액', '영업파트너', '파트너수당', '매니저', '매니저수당',
|
||||
'지급예정일', '상태', '실제지급일'
|
||||
'지급예정일', '상태', '실제지급일',
|
||||
]);
|
||||
|
||||
// 데이터
|
||||
|
||||
@@ -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' => '매출이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' => '구독이 삭제되었습니다.']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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' => '사진 행을 찾을 수 없습니다.',
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, '접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
|
||||
@@ -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, '접근 권한이 없습니다.');
|
||||
}
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ public function bulkStore(Request $request): JsonResponse
|
||||
|
||||
if ($exists) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 계정과목 모델
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 바로빌 계좌 거래 동기화 상태 모델
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).'...';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'] ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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).'...';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
class DocumentTemplate extends Model
|
||||
{
|
||||
use HasFactory, BelongsToTenant, SoftDeletes;
|
||||
use BelongsToTenant, HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -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).'원';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -18,17 +18,23 @@ class FundSchedule extends Model
|
||||
|
||||
// 일정 유형 상수
|
||||
public const TYPE_INCOME = 'income';
|
||||
|
||||
public const TYPE_EXPENSE = 'expense';
|
||||
|
||||
// 상태 상수
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
// 반복 규칙 상수
|
||||
public const RECURRENCE_DAILY = 'daily';
|
||||
|
||||
public const RECURRENCE_WEEKLY = 'weekly';
|
||||
|
||||
public const RECURRENCE_MONTHLY = 'monthly';
|
||||
|
||||
public const RECURRENCE_YEARLY = 'yearly';
|
||||
|
||||
protected $fillable = [
|
||||
@@ -102,14 +108,14 @@ public function getFormattedAmountAttribute(): string
|
||||
$amount = abs($this->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).'원';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Finance;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -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}%");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user