Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
@@ -427,6 +427,7 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
$totalAmount = 0;
|
||||
$approvalCount = 0;
|
||||
$cancelCount = 0;
|
||||
$totalTax = 0;
|
||||
|
||||
foreach ($cardList as $card) {
|
||||
if (!is_object($card)) continue;
|
||||
@@ -484,6 +485,7 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
$totalAmount += $parsed['summary']['totalAmount'];
|
||||
$approvalCount += $parsed['summary']['approvalCount'];
|
||||
$cancelCount += $parsed['summary']['cancelCount'];
|
||||
$totalTax += $parsed['summary']['totalTax'] ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,6 +495,24 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
return strcmp($b['useDt'] ?? '', $a['useDt'] ?? '');
|
||||
});
|
||||
|
||||
// 전체 데이터에서 공제/불공제 통계 계산
|
||||
$deductibleAmount = 0;
|
||||
$deductibleCount = 0;
|
||||
$nonDeductibleAmount = 0;
|
||||
$nonDeductibleCount = 0;
|
||||
|
||||
foreach ($allLogs as $log) {
|
||||
$type = $log['deductionType'] ?? 'non_deductible';
|
||||
$amount = abs($log['approvalAmount'] ?? 0);
|
||||
if ($type === 'deductible') {
|
||||
$deductibleAmount += $amount;
|
||||
$deductibleCount++;
|
||||
} else {
|
||||
$nonDeductibleAmount += $amount;
|
||||
$nonDeductibleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지네이션
|
||||
$totalCount = count($allLogs);
|
||||
$maxPageNum = (int)ceil($totalCount / $limit);
|
||||
@@ -513,7 +533,12 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
'totalAmount' => $totalAmount,
|
||||
'count' => $totalCount,
|
||||
'approvalCount' => $approvalCount,
|
||||
'cancelCount' => $cancelCount
|
||||
'cancelCount' => $cancelCount,
|
||||
'totalTax' => $totalTax,
|
||||
'deductibleAmount' => $deductibleAmount,
|
||||
'deductibleCount' => $deductibleCount,
|
||||
'nonDeductibleAmount' => $nonDeductibleAmount,
|
||||
'nonDeductibleCount' => $nonDeductibleCount,
|
||||
]
|
||||
]
|
||||
]);
|
||||
@@ -528,6 +553,11 @@ private function parseTransactionLogs($resultData, $savedData = null): array
|
||||
$totalAmount = 0;
|
||||
$approvalCount = 0;
|
||||
$cancelCount = 0;
|
||||
$totalTax = 0;
|
||||
$deductibleAmount = 0;
|
||||
$deductibleCount = 0;
|
||||
$nonDeductibleAmount = 0;
|
||||
$nonDeductibleCount = 0;
|
||||
|
||||
$rawLogs = [];
|
||||
if (isset($resultData->CardLogList) && isset($resultData->CardLogList->CardApprovalLog)) {
|
||||
@@ -626,6 +656,18 @@ private function parseTransactionLogs($resultData, $savedData = null): array
|
||||
'isSaved' => $savedItem !== null,
|
||||
];
|
||||
|
||||
// 공제/불공제 통계 집계
|
||||
$deductionType = $logItem['deductionType'];
|
||||
$absAmount = abs($amount);
|
||||
$totalTax += abs(floatval($log->Tax ?? 0));
|
||||
if ($deductionType === 'deductible') {
|
||||
$deductibleAmount += $absAmount;
|
||||
$deductibleCount++;
|
||||
} else {
|
||||
$nonDeductibleAmount += $absAmount;
|
||||
$nonDeductibleCount++;
|
||||
}
|
||||
|
||||
$logs[] = $logItem;
|
||||
}
|
||||
|
||||
@@ -635,7 +677,12 @@ private function parseTransactionLogs($resultData, $savedData = null): array
|
||||
'totalAmount' => $totalAmount,
|
||||
'count' => count($logs),
|
||||
'approvalCount' => $approvalCount,
|
||||
'cancelCount' => $cancelCount
|
||||
'cancelCount' => $cancelCount,
|
||||
'totalTax' => $totalTax,
|
||||
'deductibleAmount' => $deductibleAmount,
|
||||
'deductibleCount' => $deductibleCount,
|
||||
'nonDeductibleAmount' => $nonDeductibleAmount,
|
||||
'nonDeductibleCount' => $nonDeductibleCount,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -339,6 +339,82 @@ public function sendToNts(Request $request): JsonResponse
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공급자 기초정보 조회
|
||||
*/
|
||||
public function getSupplier(): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id');
|
||||
if (!$tenantId) {
|
||||
return response()->json(['success' => false, 'error' => '테넌트가 선택되지 않았습니다.'], 400);
|
||||
}
|
||||
|
||||
$member = BarobillMember::where('tenant_id', $tenantId)->first();
|
||||
if (!$member) {
|
||||
return response()->json(['success' => false, 'error' => '바로빌 회원사 정보가 없습니다.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'supplier' => [
|
||||
'bizno' => $member->biz_no,
|
||||
'name' => $member->corp_name ?? '',
|
||||
'ceo' => $member->ceo_name ?? '',
|
||||
'addr' => $member->addr ?? '',
|
||||
'bizType' => $member->biz_type ?? '',
|
||||
'bizClass' => $member->biz_class ?? '',
|
||||
'contact' => $member->manager_name ?? '',
|
||||
'contactPhone' => $member->manager_hp ?? '',
|
||||
'email' => $member->manager_email ?? '',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공급자 기초정보 수정
|
||||
*/
|
||||
public function updateSupplier(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id');
|
||||
if (!$tenantId) {
|
||||
return response()->json(['success' => false, 'error' => '테넌트가 선택되지 않았습니다.'], 400);
|
||||
}
|
||||
|
||||
$member = BarobillMember::where('tenant_id', $tenantId)->first();
|
||||
if (!$member) {
|
||||
return response()->json(['success' => false, 'error' => '바로빌 회원사 정보가 없습니다.'], 404);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'corp_name' => 'required|string|max:100',
|
||||
'ceo_name' => 'required|string|max:50',
|
||||
'addr' => 'nullable|string|max:255',
|
||||
'biz_type' => 'nullable|string|max:100',
|
||||
'biz_class' => 'nullable|string|max:100',
|
||||
'manager_name' => 'nullable|string|max:50',
|
||||
'manager_email' => 'nullable|email|max:100',
|
||||
'manager_hp' => 'nullable|string|max:20',
|
||||
]);
|
||||
|
||||
$member->update($validated);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '공급자 정보가 수정되었습니다.',
|
||||
'supplier' => [
|
||||
'bizno' => $member->biz_no,
|
||||
'name' => $member->corp_name ?? '',
|
||||
'ceo' => $member->ceo_name ?? '',
|
||||
'addr' => $member->addr ?? '',
|
||||
'bizType' => $member->biz_type ?? '',
|
||||
'bizClass' => $member->biz_class ?? '',
|
||||
'contact' => $member->manager_name ?? '',
|
||||
'contactPhone' => $member->manager_hp ?? '',
|
||||
'email' => $member->manager_email ?? '',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 삭제
|
||||
*/
|
||||
@@ -501,9 +577,17 @@ private function issueTaxInvoice(array $invoiceData): array
|
||||
'TaxInvoiceTradeLineItems' => ['TaxInvoiceTradeLineItem' => []],
|
||||
];
|
||||
|
||||
$year = substr($invoiceData['supplyDate'] ?? date('Y-m-d'), 0, 4);
|
||||
|
||||
foreach ($invoiceData['items'] ?? [] as $item) {
|
||||
$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
|
||||
: '';
|
||||
|
||||
$taxInvoice['TaxInvoiceTradeLineItems']['TaxInvoiceTradeLineItem'][] = [
|
||||
'PurchaseExpiry' => '', // 공제기한
|
||||
'PurchaseExpiry' => $purchaseExpiry, // 거래일자 (YYYYMMDD)
|
||||
'Name' => $item['name'] ?? '', // 품명
|
||||
'Information' => $item['spec'] ?? '', // 규격
|
||||
'ChargeableUnit' => $item['qty'] ?? '1', // 수량
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
use App\Models\Barobill\BarobillConfig;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use App\Services\Barobill\HometaxSyncService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -1174,4 +1175,230 @@ private function xmlToObject(\SimpleXMLElement $xml): object
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 DB에서 매출 세금계산서 조회
|
||||
*/
|
||||
public function localSales(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
// YYYYMMDD 형식을 Y-m-d로 변환
|
||||
if (strlen($startDate) === 8) {
|
||||
$startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2);
|
||||
}
|
||||
if (strlen($endDate) === 8) {
|
||||
$endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2);
|
||||
}
|
||||
|
||||
$dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
|
||||
$searchCorp = $request->input('searchCorp');
|
||||
|
||||
$data = $syncService->getLocalInvoices(
|
||||
$tenantId,
|
||||
'sales',
|
||||
$startDate,
|
||||
$endDate,
|
||||
$dateType,
|
||||
$searchCorp
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'sales'),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('로컬 매출 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '조회 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로컬 DB에서 매입 세금계산서 조회
|
||||
*/
|
||||
public function localPurchases(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
|
||||
$startDate = $request->input('startDate');
|
||||
$endDate = $request->input('endDate');
|
||||
|
||||
// YYYYMMDD 형식을 Y-m-d로 변환
|
||||
if (strlen($startDate) === 8) {
|
||||
$startDate = substr($startDate, 0, 4) . '-' . substr($startDate, 4, 2) . '-' . substr($startDate, 6, 2);
|
||||
}
|
||||
if (strlen($endDate) === 8) {
|
||||
$endDate = substr($endDate, 0, 4) . '-' . substr($endDate, 4, 2) . '-' . substr($endDate, 6, 2);
|
||||
}
|
||||
|
||||
$dateType = $request->input('dateType', 1) == 1 ? 'write' : 'issue';
|
||||
$searchCorp = $request->input('searchCorp');
|
||||
|
||||
$data = $syncService->getLocalInvoices(
|
||||
$tenantId,
|
||||
'purchase',
|
||||
$startDate,
|
||||
$endDate,
|
||||
$dateType,
|
||||
$searchCorp
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'lastSyncAt' => $syncService->getLastSyncTime($tenantId, 'purchase'),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('로컬 매입 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '조회 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 API에서 데이터를 가져와 로컬 DB에 동기화
|
||||
*/
|
||||
public function sync(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$type = $request->input('type', 'all'); // 'sales', 'purchase', 'all'
|
||||
$startDate = $request->input('startDate', date('Ymd', strtotime('-1 month')));
|
||||
$endDate = $request->input('endDate', date('Ymd'));
|
||||
$dateType = (int)$request->input('dateType', 1);
|
||||
|
||||
$results = [];
|
||||
|
||||
// 매출 동기화
|
||||
if ($type === 'all' || $type === 'sales') {
|
||||
$salesRequest = new Request([
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'dateType' => $dateType,
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
$salesResponse = $this->sales($salesRequest);
|
||||
$salesData = json_decode($salesResponse->getContent(), true);
|
||||
|
||||
if ($salesData['success'] && !empty($salesData['data']['invoices'])) {
|
||||
$results['sales'] = $syncService->syncInvoices(
|
||||
$salesData['data']['invoices'],
|
||||
$tenantId,
|
||||
'sales'
|
||||
);
|
||||
} else {
|
||||
$results['sales'] = [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'failed' => 0,
|
||||
'total' => 0,
|
||||
'error' => $salesData['error'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 매입 동기화
|
||||
if ($type === 'all' || $type === 'purchase') {
|
||||
$purchaseRequest = new Request([
|
||||
'startDate' => $startDate,
|
||||
'endDate' => $endDate,
|
||||
'dateType' => $dateType,
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
$purchaseResponse = $this->purchases($purchaseRequest);
|
||||
$purchaseData = json_decode($purchaseResponse->getContent(), true);
|
||||
|
||||
if ($purchaseData['success'] && !empty($purchaseData['data']['invoices'])) {
|
||||
$results['purchase'] = $syncService->syncInvoices(
|
||||
$purchaseData['data']['invoices'],
|
||||
$tenantId,
|
||||
'purchase'
|
||||
);
|
||||
} else {
|
||||
$results['purchase'] = [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'failed' => 0,
|
||||
'total' => 0,
|
||||
'error' => $purchaseData['error'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 총 결과 계산
|
||||
$totalInserted = ($results['sales']['inserted'] ?? 0) + ($results['purchase']['inserted'] ?? 0);
|
||||
$totalUpdated = ($results['sales']['updated'] ?? 0) + ($results['purchase']['updated'] ?? 0);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "동기화 완료: {$totalInserted}건 추가, {$totalUpdated}건 갱신",
|
||||
'data' => $results,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('홈택스 동기화 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '동기화 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 메모 업데이트
|
||||
*/
|
||||
public function updateMemo(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$id = $request->input('id');
|
||||
$memo = $request->input('memo');
|
||||
|
||||
$success = $syncService->updateMemo($id, $tenantId, $memo);
|
||||
|
||||
return response()->json([
|
||||
'success' => $success,
|
||||
'message' => $success ? '메모가 저장되었습니다.' : '저장에 실패했습니다.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 확인 여부 토글
|
||||
*/
|
||||
public function toggleChecked(Request $request, HometaxSyncService $syncService): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$id = $request->input('id');
|
||||
|
||||
$success = $syncService->toggleChecked($id, $tenantId);
|
||||
|
||||
return response()->json([
|
||||
'success' => $success,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class VehicleLogController extends Controller
|
||||
{
|
||||
@@ -21,82 +18,112 @@ public function index(Request $request): View|Response
|
||||
return response('', 200)->header('HX-Redirect', route('finance.vehicle-logs'));
|
||||
}
|
||||
|
||||
return view('finance.vehicle-logs');
|
||||
}
|
||||
|
||||
/**
|
||||
* 차량 목록 조회
|
||||
*/
|
||||
public function vehicles(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$vehicles = CorporateVehicle::where('tenant_id', $tenantId)
|
||||
->where('status', 'active')
|
||||
->orderBy('plate_number')
|
||||
->get();
|
||||
|
||||
return view('finance.vehicle-logs', [
|
||||
'vehicles' => $vehicles,
|
||||
'tripTypes' => VehicleLog::tripTypeLabels(),
|
||||
'locationTypes' => VehicleLog::locationTypeLabels(),
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $vehicles,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 운행기록 목록 조회
|
||||
*/
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|integer',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
]);
|
||||
$query = VehicleLog::with('vehicle')
|
||||
->where('tenant_id', $tenantId);
|
||||
|
||||
$vehicleId = $request->vehicle_id;
|
||||
$startDate = $request->start_date;
|
||||
$endDate = $request->end_date;
|
||||
// 차량 필터
|
||||
if ($request->filled('vehicle_id') && $request->vehicle_id !== 'all') {
|
||||
$query->where('vehicle_id', $request->vehicle_id);
|
||||
}
|
||||
|
||||
// 차량 정보
|
||||
$vehicle = CorporateVehicle::where('tenant_id', $tenantId)
|
||||
->findOrFail($vehicleId);
|
||||
// 년/월 필터
|
||||
if ($request->filled('year')) {
|
||||
$query->whereYear('log_date', $request->year);
|
||||
}
|
||||
if ($request->filled('month')) {
|
||||
$query->whereMonth('log_date', $request->month);
|
||||
}
|
||||
|
||||
// 전체 운행기록 수 (해당 차량)
|
||||
$totalCount = VehicleLog::where('tenant_id', $tenantId)
|
||||
->where('vehicle_id', $vehicleId)
|
||||
->count();
|
||||
// 구분 필터
|
||||
if ($request->filled('trip_type') && $request->trip_type !== 'all') {
|
||||
$query->where('trip_type', $request->trip_type);
|
||||
}
|
||||
|
||||
$logs = VehicleLog::where('tenant_id', $tenantId)
|
||||
->where('vehicle_id', $vehicleId)
|
||||
->whereBetween('log_date', [$startDate, $endDate])
|
||||
->orderBy('log_date', 'desc')
|
||||
// 검색어
|
||||
if ($request->filled('search')) {
|
||||
$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}%");
|
||||
});
|
||||
}
|
||||
|
||||
$logs = $query->orderBy('log_date', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
// 월별 합계
|
||||
$totals = [
|
||||
'business_km' => $logs->whereIn('trip_type', ['commute_to', 'commute_from', 'business', 'commute_round', 'business_round'])->sum('distance_km'),
|
||||
'personal_km' => $logs->whereIn('trip_type', ['personal', 'personal_round'])->sum('distance_km'),
|
||||
'total_km' => $logs->sum('distance_km'),
|
||||
];
|
||||
// 응답 포맷팅
|
||||
$data = $logs->map(function ($log) {
|
||||
return [
|
||||
'id' => $log->id,
|
||||
'logDate' => $log->log_date->format('Y-m-d'),
|
||||
'vehicleId' => $log->vehicle_id,
|
||||
'plateNumber' => $log->vehicle?->plate_number,
|
||||
'model' => $log->vehicle?->model,
|
||||
'department' => $log->department,
|
||||
'driverName' => $log->driver_name,
|
||||
'tripType' => $log->trip_type,
|
||||
'departureType' => $log->departure_type,
|
||||
'departureName' => $log->departure_name,
|
||||
'departureAddress' => $log->departure_address,
|
||||
'arrivalType' => $log->arrival_type,
|
||||
'arrivalName' => $log->arrival_name,
|
||||
'arrivalAddress' => $log->arrival_address,
|
||||
'distanceKm' => $log->distance_km,
|
||||
'note' => $log->note,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'vehicle' => $vehicle,
|
||||
'logs' => $logs,
|
||||
'totals' => $totals,
|
||||
'totalCount' => $totalCount,
|
||||
],
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 운행기록 등록
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|integer|exists:corporate_vehicles,id',
|
||||
'vehicle_id' => 'required|exists:corporate_vehicles,id',
|
||||
'log_date' => 'required|date',
|
||||
'driver_name' => 'required|string|max:50',
|
||||
'trip_type' => 'required|in:commute_to,commute_from,business,personal,commute_round,business_round,personal_round',
|
||||
'trip_type' => 'required|in:commute_to,commute_from,business,personal',
|
||||
'distance_km' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
// 해당 차량이 현재 테넌트의 것인지 확인
|
||||
CorporateVehicle::where('tenant_id', $tenantId)
|
||||
->findOrFail($request->vehicle_id);
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$log = VehicleLog::create([
|
||||
'tenant_id' => $tenantId,
|
||||
@@ -122,6 +149,9 @@ public function store(Request $request): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 운행기록 수정
|
||||
*/
|
||||
public function update(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
@@ -129,13 +159,15 @@ public function update(Request $request, int $id): JsonResponse
|
||||
$log = VehicleLog::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|exists:corporate_vehicles,id',
|
||||
'log_date' => 'required|date',
|
||||
'driver_name' => 'required|string|max:50',
|
||||
'trip_type' => 'required|in:commute_to,commute_from,business,personal,commute_round,business_round,personal_round',
|
||||
'trip_type' => 'required|in:commute_to,commute_from,business,personal',
|
||||
'distance_km' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
$log->update([
|
||||
'vehicle_id' => $request->vehicle_id,
|
||||
'log_date' => $request->log_date,
|
||||
'department' => $request->department,
|
||||
'driver_name' => $request->driver_name,
|
||||
@@ -157,6 +189,9 @@ public function update(Request $request, int $id): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 운행기록 삭제
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
@@ -170,96 +205,65 @@ public function destroy(int $id): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
public function export(Request $request): StreamedResponse
|
||||
/**
|
||||
* 월간 통계 조회
|
||||
*/
|
||||
public function summary(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|integer',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
]);
|
||||
$query = VehicleLog::where('tenant_id', $tenantId);
|
||||
|
||||
$vehicleId = $request->vehicle_id;
|
||||
$startDate = $request->start_date;
|
||||
$endDate = $request->end_date;
|
||||
|
||||
$vehicle = CorporateVehicle::where('tenant_id', $tenantId)
|
||||
->findOrFail($vehicleId);
|
||||
|
||||
$logs = VehicleLog::where('tenant_id', $tenantId)
|
||||
->where('vehicle_id', $vehicleId)
|
||||
->whereBetween('log_date', [$startDate, $endDate])
|
||||
->orderBy('log_date')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('운행기록부');
|
||||
|
||||
// 기본 정보
|
||||
$sheet->setCellValue('A1', '업무용승용차 운행기록부');
|
||||
$sheet->setCellValue('A3', '차량번호');
|
||||
$sheet->setCellValue('B3', $vehicle->plate_number);
|
||||
$sheet->setCellValue('C3', '차종');
|
||||
$sheet->setCellValue('D3', $vehicle->model);
|
||||
$sheet->setCellValue('E3', '구분');
|
||||
$sheet->setCellValue('F3', $this->getOwnershipTypeLabel($vehicle->ownership_type));
|
||||
$sheet->setCellValue('A4', '조회기간');
|
||||
$sheet->setCellValue('B4', sprintf('%s ~ %s', $startDate, $endDate));
|
||||
|
||||
// 헤더
|
||||
$headers = ['일자', '부서', '성명', '구분', '출발지', '도착지', '주행km', '비고'];
|
||||
$col = 'A';
|
||||
foreach ($headers as $header) {
|
||||
$sheet->setCellValue($col . '6', $header);
|
||||
$col++;
|
||||
// 차량 필터
|
||||
if ($request->filled('vehicle_id') && $request->vehicle_id !== 'all') {
|
||||
$query->where('vehicle_id', $request->vehicle_id);
|
||||
}
|
||||
|
||||
// 데이터
|
||||
$row = 7;
|
||||
$tripTypeLabels = VehicleLog::tripTypeLabels();
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$sheet->setCellValue('A' . $row, $log->log_date->format('Y-m-d'));
|
||||
$sheet->setCellValue('B' . $row, $log->department ?? '');
|
||||
$sheet->setCellValue('C' . $row, $log->driver_name);
|
||||
$sheet->setCellValue('D' . $row, $tripTypeLabels[$log->trip_type] ?? $log->trip_type);
|
||||
$sheet->setCellValue('E' . $row, $log->departure_name ?? '');
|
||||
$sheet->setCellValue('F' . $row, $log->arrival_name ?? '');
|
||||
$sheet->setCellValue('G' . $row, $log->distance_km);
|
||||
$sheet->setCellValue('H' . $row, $log->note ?? '');
|
||||
$row++;
|
||||
// 년/월 필터
|
||||
if ($request->filled('year')) {
|
||||
$query->whereYear('log_date', $request->year);
|
||||
}
|
||||
if ($request->filled('month')) {
|
||||
$query->whereMonth('log_date', $request->month);
|
||||
}
|
||||
|
||||
// 합계
|
||||
$businessKm = $logs->whereIn('trip_type', ['commute_to', 'commute_from', 'business', 'commute_round', 'business_round'])->sum('distance_km');
|
||||
$personalKm = $logs->whereIn('trip_type', ['personal', 'personal_round'])->sum('distance_km');
|
||||
$totalKm = $logs->sum('distance_km');
|
||||
// 구분별 주행거리 합계
|
||||
$summary = $query->selectRaw('
|
||||
trip_type,
|
||||
COUNT(*) as count,
|
||||
SUM(distance_km) as total_distance
|
||||
')
|
||||
->groupBy('trip_type')
|
||||
->get()
|
||||
->keyBy('trip_type');
|
||||
|
||||
$sheet->setCellValue('A' . $row, '합계');
|
||||
$sheet->setCellValue('F' . $row, '업무용: ' . number_format($businessKm) . 'km');
|
||||
$sheet->setCellValue('G' . $row, number_format($totalKm));
|
||||
$sheet->setCellValue('H' . $row, '비업무: ' . number_format($personalKm) . 'km');
|
||||
$tripTypes = VehicleLog::getTripTypes();
|
||||
$result = [];
|
||||
$totalCount = 0;
|
||||
$totalDistance = 0;
|
||||
|
||||
$filename = sprintf('운행기록부_%s_%s_%s.xlsx', $vehicle->plate_number, $startDate, $endDate);
|
||||
foreach ($tripTypes as $type => $label) {
|
||||
$data = $summary->get($type);
|
||||
$count = $data ? $data->count : 0;
|
||||
$distance = $data ? $data->total_distance : 0;
|
||||
$result[$type] = [
|
||||
'label' => $label,
|
||||
'count' => $count,
|
||||
'distance' => $distance,
|
||||
];
|
||||
$totalCount += $count;
|
||||
$totalDistance += $distance;
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($spreadsheet) {
|
||||
$writer = new Xlsx($spreadsheet);
|
||||
$writer->save('php://output');
|
||||
}, $filename, [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'byType' => $result,
|
||||
'total' => [
|
||||
'count' => $totalCount,
|
||||
'distance' => $totalDistance,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function getOwnershipTypeLabel(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'corporate' => '회사',
|
||||
'rent' => '렌트',
|
||||
'lease' => '리스',
|
||||
default => $type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
200
app/Http/Controllers/Finance/VehicleMaintenanceController.php
Normal file
200
app/Http/Controllers/Finance/VehicleMaintenanceController.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Finance;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CorporateVehicle;
|
||||
use App\Models\VehicleMaintenance;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class VehicleMaintenanceController extends Controller
|
||||
{
|
||||
public function index(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('finance.vehicle-maintenance'));
|
||||
}
|
||||
|
||||
return view('finance.vehicle-maintenance');
|
||||
}
|
||||
|
||||
/**
|
||||
* 차량 목록 조회
|
||||
*/
|
||||
public function vehicles(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$vehicles = CorporateVehicle::where('tenant_id', $tenantId)
|
||||
->orderBy('plate_number')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $vehicles,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정비 이력 목록 조회
|
||||
*/
|
||||
public function list(Request $request): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$query = VehicleMaintenance::with('vehicle')
|
||||
->where('tenant_id', $tenantId);
|
||||
|
||||
// 차량 필터
|
||||
if ($request->filled('vehicle_id') && $request->vehicle_id !== 'all') {
|
||||
$query->where('vehicle_id', $request->vehicle_id);
|
||||
}
|
||||
|
||||
// 카테고리 필터
|
||||
if ($request->filled('category') && $request->category !== 'all') {
|
||||
$query->where('category', $request->category);
|
||||
}
|
||||
|
||||
// 날짜 범위 필터
|
||||
if ($request->filled('start_date')) {
|
||||
$query->whereDate('date', '>=', $request->start_date);
|
||||
}
|
||||
if ($request->filled('end_date')) {
|
||||
$query->whereDate('date', '<=', $request->end_date);
|
||||
}
|
||||
|
||||
// 검색어
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('description', 'like', "%{$search}%")
|
||||
->orWhere('vendor', 'like', "%{$search}%")
|
||||
->orWhere('memo', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$maintenances = $query->orderBy('date', 'desc')->get();
|
||||
|
||||
// 응답 포맷팅
|
||||
$data = $maintenances->map(function ($m) {
|
||||
return [
|
||||
'id' => $m->id,
|
||||
'date' => $m->date->format('Y-m-d'),
|
||||
'vehicleId' => $m->vehicle_id,
|
||||
'plateNumber' => $m->vehicle?->plate_number,
|
||||
'model' => $m->vehicle?->model,
|
||||
'category' => $m->category,
|
||||
'description' => $m->description,
|
||||
'amount' => $m->amount,
|
||||
'mileage' => $m->mileage,
|
||||
'vendor' => $m->vendor,
|
||||
'memo' => $m->memo,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정비 이력 등록
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|exists:corporate_vehicles,id',
|
||||
'date' => 'required|date',
|
||||
'category' => 'required|string|max:20',
|
||||
'amount' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$maintenance = VehicleMaintenance::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'vehicle_id' => $request->vehicle_id,
|
||||
'date' => $request->date,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'amount' => $request->amount ?? 0,
|
||||
'mileage' => $request->mileage,
|
||||
'vendor' => $request->vendor,
|
||||
'memo' => $request->memo,
|
||||
]);
|
||||
|
||||
// 차량 주행거리 업데이트
|
||||
if ($request->filled('mileage')) {
|
||||
CorporateVehicle::where('id', $request->vehicle_id)
|
||||
->where('tenant_id', $tenantId)
|
||||
->update(['mileage' => $request->mileage]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '정비 이력이 등록되었습니다.',
|
||||
'data' => $maintenance,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정비 이력 수정
|
||||
*/
|
||||
public function update(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$maintenance = VehicleMaintenance::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'vehicle_id' => 'required|exists:corporate_vehicles,id',
|
||||
'date' => 'required|date',
|
||||
'category' => 'required|string|max:20',
|
||||
'amount' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$maintenance->update([
|
||||
'vehicle_id' => $request->vehicle_id,
|
||||
'date' => $request->date,
|
||||
'category' => $request->category,
|
||||
'description' => $request->description,
|
||||
'amount' => $request->amount ?? 0,
|
||||
'mileage' => $request->mileage,
|
||||
'vendor' => $request->vendor,
|
||||
'memo' => $request->memo,
|
||||
]);
|
||||
|
||||
// 차량 주행거리 업데이트
|
||||
if ($request->filled('mileage')) {
|
||||
CorporateVehicle::where('id', $request->vehicle_id)
|
||||
->where('tenant_id', $tenantId)
|
||||
->update(['mileage' => $request->mileage]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '정비 이력이 수정되었습니다.',
|
||||
'data' => $maintenance,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정비 이력 삭제
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
$tenantId = session('tenant_id', 1);
|
||||
|
||||
$maintenance = VehicleMaintenance::where('tenant_id', $tenantId)->findOrFail($id);
|
||||
$maintenance->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '정비 이력이 삭제되었습니다.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -108,8 +108,9 @@ private function getIndexData(Request $request): array
|
||||
});
|
||||
}
|
||||
|
||||
// 상태 필터
|
||||
if (!empty($filters['status'])) {
|
||||
// 상태 필터 (progress_complete는 계산값 기반이므로 별도 처리)
|
||||
$isProgressCompleteFilter = ($filters['status'] === 'progress_complete');
|
||||
if (!empty($filters['status']) && !$isProgressCompleteFilter) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
@@ -118,32 +119,87 @@ private function getIndexData(Request $request): array
|
||||
$query->where('registered_by', $filters['registered_by']);
|
||||
}
|
||||
|
||||
$prospects = $query->orderByDesc('created_at')->paginate(20);
|
||||
// progress_complete 필터: 전체 조회 후 PHP에서 필터링
|
||||
if ($isProgressCompleteFilter) {
|
||||
$allProspects = $query->orderByDesc('created_at')->get();
|
||||
|
||||
// 각 가망고객의 진행률 계산 및 상태 자동 전환
|
||||
foreach ($prospects as $prospect) {
|
||||
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
||||
$prospect->sales_progress = $progress['sales']['percentage'];
|
||||
$prospect->manager_progress = $progress['manager']['percentage'];
|
||||
// 진행률 계산 및 부가정보 세팅
|
||||
foreach ($allProspects as $prospect) {
|
||||
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
||||
$prospect->sales_progress = $progress['sales']['percentage'];
|
||||
$prospect->manager_progress = $progress['manager']['percentage'];
|
||||
|
||||
// 진행률 100% 시 상태 자동 전환 체크
|
||||
if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) {
|
||||
SalesScenarioChecklist::checkAndConvertProspectStatus($prospect->id);
|
||||
$prospect->refresh();
|
||||
if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) {
|
||||
SalesScenarioChecklist::checkAndConvertProspectStatus($prospect->id);
|
||||
$prospect->refresh();
|
||||
}
|
||||
|
||||
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
||||
$prospect->hq_status = $management?->hq_status ?? 'pending';
|
||||
$prospect->hq_status_label = $management?->hq_status_label ?? '대기';
|
||||
$prospect->manager_user = $management?->manager;
|
||||
|
||||
if ($management) {
|
||||
$commission = SalesCommission::where('management_id', $management->id)->first();
|
||||
$prospect->commission = $commission;
|
||||
} else {
|
||||
$prospect->commission = null;
|
||||
}
|
||||
}
|
||||
|
||||
// management 정보
|
||||
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
||||
$prospect->hq_status = $management?->hq_status ?? 'pending';
|
||||
$prospect->hq_status_label = $management?->hq_status_label ?? '대기';
|
||||
$prospect->manager_user = $management?->manager;
|
||||
// 두 시나리오 모두 100%인 것만 필터링
|
||||
$filtered = $allProspects->filter(function ($prospect) {
|
||||
return $prospect->sales_progress === 100 && $prospect->manager_progress === 100;
|
||||
});
|
||||
|
||||
// 수당 정보 (management가 있는 경우)
|
||||
if ($management) {
|
||||
$commission = SalesCommission::where('management_id', $management->id)->first();
|
||||
$prospect->commission = $commission;
|
||||
} else {
|
||||
$prospect->commission = null;
|
||||
// 수동 페이지네이션
|
||||
$page = request()->get('page', 1);
|
||||
$perPage = 20;
|
||||
$prospects = new \Illuminate\Pagination\LengthAwarePaginator(
|
||||
$filtered->forPage($page, $perPage)->values(),
|
||||
$filtered->count(),
|
||||
$perPage,
|
||||
$page,
|
||||
['path' => request()->url(), 'query' => request()->query()]
|
||||
);
|
||||
} else {
|
||||
$prospects = $query->orderByDesc('created_at')->paginate(20);
|
||||
|
||||
// 각 가망고객의 진행률 계산 및 상태 자동 전환
|
||||
foreach ($prospects as $prospect) {
|
||||
$progress = SalesScenarioChecklist::getProspectProgress($prospect->id);
|
||||
$prospect->sales_progress = $progress['sales']['percentage'];
|
||||
$prospect->manager_progress = $progress['manager']['percentage'];
|
||||
|
||||
// 진행률 100% 시 상태 자동 전환 체크
|
||||
if ($progress['sales']['percentage'] === 100 && $progress['manager']['percentage'] === 100) {
|
||||
SalesScenarioChecklist::checkAndConvertProspectStatus($prospect->id);
|
||||
$prospect->refresh();
|
||||
}
|
||||
|
||||
// management 정보
|
||||
$management = SalesTenantManagement::where('tenant_prospect_id', $prospect->id)->first();
|
||||
$prospect->hq_status = $management?->hq_status ?? 'pending';
|
||||
$prospect->hq_status_label = $management?->hq_status_label ?? '대기';
|
||||
$prospect->manager_user = $management?->manager;
|
||||
|
||||
// 수당 정보 (management가 있는 경우)
|
||||
if ($management) {
|
||||
$commission = SalesCommission::where('management_id', $management->id)->first();
|
||||
$prospect->commission = $commission;
|
||||
} else {
|
||||
$prospect->commission = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 진행완료 건수 계산 (전체 prospect 중 두 시나리오 모두 100%인 건수)
|
||||
$progressCompleteCount = 0;
|
||||
$allForStats = TenantProspect::all();
|
||||
foreach ($allForStats as $p) {
|
||||
$prog = SalesScenarioChecklist::getProspectProgress($p->id);
|
||||
if ($prog['sales']['percentage'] === 100 && $prog['manager']['percentage'] === 100) {
|
||||
$progressCompleteCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +209,7 @@ private function getIndexData(Request $request): array
|
||||
'active' => TenantProspect::where('status', TenantProspect::STATUS_ACTIVE)->count(),
|
||||
'expired' => TenantProspect::where('status', TenantProspect::STATUS_EXPIRED)->count(),
|
||||
'converted' => TenantProspect::where('status', TenantProspect::STATUS_CONVERTED)->count(),
|
||||
'progress_complete' => $progressCompleteCount,
|
||||
];
|
||||
|
||||
// 영업파트너별 통계
|
||||
|
||||
@@ -181,7 +181,8 @@ private function getDashboardData(Request $request): array
|
||||
$totalMembershipFee += $handoverTotalRegFee;
|
||||
$totalCommission = $partnerCommissionTotal + $managerCommissionTotal;
|
||||
|
||||
// 역할별 수당 업데이트
|
||||
// 역할별 수당 업데이트 (실제 지급된 수당 기준)
|
||||
// 참고: 예상 수당은 나중에 $totalExpectedCommission으로 별도 계산됨
|
||||
$commissionByRole[0]['amount'] = $partnerCommissionTotal;
|
||||
$commissionByRole[1]['amount'] = $managerCommissionTotal;
|
||||
|
||||
@@ -294,6 +295,10 @@ private function getDashboardData(Request $request): array
|
||||
$paidCommission
|
||||
);
|
||||
|
||||
// 역할별 수당을 예상 수당 기준으로 재설정 (1차+2차 수당과 일치하도록)
|
||||
// 판매자 예상 수당 = 개발비 × 10% (개발 진행 중 + 인계 완료 미지급)
|
||||
$commissionByRole[0]['amount'] = $totalExpectedCommission;
|
||||
|
||||
// 전환된 테넌트만 조회 (최신순, 페이지네이션)
|
||||
$tenants = Tenant::whereIn('id', $convertedTenantIds)
|
||||
->orderBy('created_at', 'desc')
|
||||
@@ -653,11 +658,49 @@ private function calculatePartnerSummaryStats(array $partnerIds, int $currentUse
|
||||
// 최종 예상 수당 (확정 + 예상 중 큰 값)
|
||||
$expectedCommission = max($confirmedCommission, $expectedFromFee);
|
||||
|
||||
// 지급 완료된 매니저 수당
|
||||
$paidManagerCommission = SalesCommission::where('manager_user_id', $currentUserId)
|
||||
->whereHas('partner', function ($query) use ($partnerIds) {
|
||||
$query->whereIn('user_id', $partnerIds);
|
||||
})
|
||||
->where('status', SalesCommission::STATUS_PAID)
|
||||
->sum('manager_commission');
|
||||
|
||||
// 지급예정 (승인됨)
|
||||
$scheduledManagerCommission = SalesCommission::where('manager_user_id', $currentUserId)
|
||||
->whereHas('partner', function ($query) use ($partnerIds) {
|
||||
$query->whereIn('user_id', $partnerIds);
|
||||
})
|
||||
->where('status', SalesCommission::STATUS_APPROVED)
|
||||
->sum('manager_commission');
|
||||
|
||||
// 입금대기 = 총 예상 - 지급완료 - 지급예정
|
||||
$pendingManagerCommission = max(0, $expectedCommission - $paidManagerCommission - $scheduledManagerCommission);
|
||||
|
||||
// 1차/2차 분할 (각 50%)
|
||||
$halfExpected = $expectedCommission / 2;
|
||||
$halfPending = $pendingManagerCommission / 2;
|
||||
$halfScheduled = $scheduledManagerCommission / 2;
|
||||
$halfPaid = $paidManagerCommission / 2;
|
||||
|
||||
return [
|
||||
'partner_count' => $partnerCount,
|
||||
'total_prospects' => $totalProspects,
|
||||
'total_conversions' => $totalConversions,
|
||||
'expected_commission' => $expectedCommission,
|
||||
'paid_commission' => $paidManagerCommission,
|
||||
'first_commission' => [
|
||||
'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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -891,6 +934,16 @@ public function helpGuide(): View
|
||||
return view('sales.dashboard.partials.help-modal', compact('htmlContent'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 가망고객 개별 행 반환 (HTMX 동적 업데이트용)
|
||||
*/
|
||||
public function getProspectRow(int $prospectId): View
|
||||
{
|
||||
$prospect = TenantProspect::findOrFail($prospectId);
|
||||
|
||||
return view('sales.dashboard.partials.prospect-row', compact('prospect'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 예상 수당 요약 계산 (개발 진행중 + 인계완료 미지급)
|
||||
*/
|
||||
@@ -905,7 +958,7 @@ private function calculateExpectedCommissionSummary($commissions, int $totalExpe
|
||||
->whereIn('status', [SalesCommission::STATUS_PENDING, SalesCommission::STATUS_APPROVED])
|
||||
->sum('partner_commission');
|
||||
|
||||
// 납입대기 금액 = 총 예상 수당 - 지급예정 - 지급완료
|
||||
// 입금대기 금액 = 총 예상 수당 - 지급예정 - 지급완료
|
||||
$pendingAmount = max(0, $totalExpectedCommission - $scheduledAmount - $paidCommission);
|
||||
|
||||
// 1차/2차 분할 (각 50%)
|
||||
|
||||
@@ -56,7 +56,11 @@ public function switch(Request $request)
|
||||
$tenantId = $request->input('tenant_id');
|
||||
|
||||
if ($tenantId === 'all') {
|
||||
$request->session()->forget('selected_tenant_id');
|
||||
// "전체 보기" 대신 사용자의 HQ 테넌트로 설정
|
||||
$hqTenant = auth()->user()->getHQTenant();
|
||||
if ($hqTenant) {
|
||||
$request->session()->put('selected_tenant_id', $hqTenant->id);
|
||||
}
|
||||
} else {
|
||||
$request->session()->put('selected_tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user