Files
sam-manage/app/Http/Controllers/Finance/SalesCommissionController.php
2026-02-25 11:45:01 +09:00

433 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Finance;
use App\Http\Controllers\Controller;
use App\Models\Sales\SalesCommission;
use App\Models\Sales\SalesPartner;
use App\Models\Sales\SalesTenantManagement;
use App\Services\SalesCommissionService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\View\View;
/**
* 영업수수료 정산 컨트롤러
*/
class SalesCommissionController extends Controller
{
public function __construct(
private SalesCommissionService $service
) {}
/**
* 정산 목록
*/
public function index(Request $request): View|Response
{
// HTMX 요청 시 전체 페이지로 리다이렉트 (JavaScript 필요)
if ($request->header('HX-Request') && ! $request->header('HX-Boosted')) {
return response('', 200)->header('HX-Redirect', route('finance.sales-commissions.index'));
}
// 필터 파라미터
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
$filters = [
'scheduled_year' => $year,
'scheduled_month' => $month,
'status' => $request->input('status'),
'payment_type' => $request->input('payment_type'),
'partner_id' => $request->input('partner_id'),
'search' => $request->input('search'),
];
// 정산 목록
$commissions = $this->service->getCommissions($filters);
// 통계
$stats = $this->service->getSettlementStats($year, $month);
// 영업파트너 목록 (필터용)
$partners = SalesPartner::with('user')
->active()
->orderBy('partner_code')
->get();
// 입금 대기 테넌트 목록
$pendingTenants = $this->service->getPendingPaymentTenants();
return view('finance.sales-commission.index', compact(
'commissions',
'stats',
'partners',
'pendingTenants',
'year',
'month',
'filters'
));
}
/**
* 정산 상세 조회
*/
public function show(int $id): JsonResponse
{
$commission = $this->service->getCommissionById($id);
if (! $commission) {
return response()->json([
'success' => false,
'message' => '정산 정보를 찾을 수 없습니다.',
], 404);
}
return response()->json([
'success' => true,
'data' => $commission,
]);
}
/**
* 정산 상세 모달 (HTMX)
*/
public function detail(int $id): View
{
$commission = $this->service->getCommissionById($id);
return view('finance.sales-commission.partials.detail-modal', compact('commission'));
}
/**
* 입금 등록 (수당 생성)
*/
public function registerPayment(Request $request): JsonResponse
{
$validated = $request->validate([
'management_id' => 'required|integer|exists:sales_tenant_managements,id',
'payment_type' => 'required|in:deposit,balance',
'payment_amount' => 'required|numeric|min:0',
'payment_date' => 'required|date',
]);
try {
$commission = $this->service->createCommission(
$validated['management_id'],
$validated['payment_type'],
$validated['payment_amount'],
$validated['payment_date']
);
return response()->json([
'success' => true,
'message' => '입금이 등록되었습니다.',
'data' => $commission,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 승인 처리
*/
public function approve(int $id): JsonResponse
{
try {
$commission = $this->service->approve($id, auth()->id());
return response()->json([
'success' => true,
'message' => '승인되었습니다.',
'data' => $commission,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 일괄 승인
*/
public function bulkApprove(Request $request): JsonResponse
{
$validated = $request->validate([
'ids' => 'required|array|min:1',
'ids.*' => 'integer|exists:sales_commissions,id',
]);
try {
$count = $this->service->bulkApprove($validated['ids'], auth()->id());
return response()->json([
'success' => true,
'message' => "{$count}건이 승인되었습니다.",
'count' => $count,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 지급완료 처리
*/
public function markPaid(int $id, Request $request): JsonResponse
{
$validated = $request->validate([
'bank_reference' => 'nullable|string|max:100',
]);
try {
$commission = $this->service->markAsPaid($id, $validated['bank_reference'] ?? null);
return response()->json([
'success' => true,
'message' => '지급완료 처리되었습니다.',
'data' => $commission,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 일괄 지급완료
*/
public function bulkMarkPaid(Request $request): JsonResponse
{
$validated = $request->validate([
'ids' => 'required|array|min:1',
'ids.*' => 'integer|exists:sales_commissions,id',
'bank_reference' => 'nullable|string|max:100',
]);
try {
$count = $this->service->bulkMarkAsPaid($validated['ids'], $validated['bank_reference'] ?? null);
return response()->json([
'success' => true,
'message' => "{$count}건이 지급완료 처리되었습니다.",
'count' => $count,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 승인취소 처리
*/
public function unapprove(int $id): JsonResponse
{
try {
$commission = $this->service->unapprove($id);
return response()->json([
'success' => true,
'message' => '승인이 취소되었습니다.',
'data' => $commission,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 취소 처리
*/
public function cancel(int $id): JsonResponse
{
try {
$commission = $this->service->cancel($id);
return response()->json([
'success' => true,
'message' => '취소되었습니다.',
'data' => $commission,
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage(),
], 400);
}
}
/**
* 수당지급일/협업지원금 인라인 수정
*/
public function updateCommissionDate(int $id, Request $request): JsonResponse
{
$validated = $request->validate([
'field' => 'required|in:first_partner_paid_at,second_partner_paid_at,manager_paid_at,referrer_commission',
'value' => 'nullable',
]);
$commission = SalesCommission::findOrFail($id);
// 수당지급일은 인계 상태일 때만 변경 가능
$paidFields = ['first_partner_paid_at', 'second_partner_paid_at', 'manager_paid_at'];
if (in_array($validated['field'], $paidFields) && $commission->management?->hq_status !== 'handover') {
return response()->json(['success' => false, 'message' => '인계 상태일 때만 수당지급일 설정 가능'], 422);
}
$value = $validated['value'];
if ($validated['field'] === 'referrer_commission') {
$value = (int) ($value ?: 0);
} else {
$value = $value ?: null;
}
$commission->update([$validated['field'] => $value]);
return response()->json(['success' => true]);
}
/**
* 정산 테이블 부분 새로고침 (HTMX)
*/
public function table(Request $request): View
{
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
$filters = [
'scheduled_year' => $year,
'scheduled_month' => $month,
'status' => $request->input('status'),
'payment_type' => $request->input('payment_type'),
'partner_id' => $request->input('partner_id'),
'search' => $request->input('search'),
];
$commissions = $this->service->getCommissions($filters);
return view('finance.sales-commission.partials.commission-table', compact('commissions'));
}
/**
* 통계 카드 부분 새로고침 (HTMX)
*/
public function stats(Request $request): View
{
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
$stats = $this->service->getSettlementStats($year, $month);
return view('finance.sales-commission.partials.stats-cards', compact('stats', 'year', 'month'));
}
/**
* 입금 등록 폼 (HTMX 모달)
*/
public function paymentForm(Request $request): View
{
$managementId = $request->input('management_id');
$management = null;
if ($managementId) {
$management = SalesTenantManagement::with(['tenant', 'salesPartner.user', 'contractProducts.product'])
->find($managementId);
}
// 입금 대기 테넌트 목록
$pendingTenants = $this->service->getPendingPaymentTenants();
return view('finance.sales-commission.partials.payment-form', compact('management', 'pendingTenants'));
}
/**
* 엑셀 다운로드
*/
public function export(Request $request)
{
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
$filters = [
'scheduled_year' => $year,
'scheduled_month' => $month,
'status' => $request->input('status'),
'payment_type' => $request->input('payment_type'),
'partner_id' => $request->input('partner_id'),
];
// 전체 데이터 조회 (페이지네이션 없이)
$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']))
->forScheduledMonth($year, $month)
->orderBy('scheduled_payment_date')
->get();
// CSV 생성
$filename = "sales_commission_{$year}_{$month}.csv";
$headers = [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
];
$callback = function () use ($commissions) {
$file = fopen('php://output', 'w');
// BOM for UTF-8
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
// 헤더
fputcsv($file, [
'번호', '테넌트', '입금구분', '입금액', '입금일',
'기준액', '영업파트너', '파트너수당', '매니저', '매니저수당',
'지급예정일', '상태', '실제지급일',
]);
// 데이터
foreach ($commissions as $commission) {
fputcsv($file, [
$commission->id,
$commission->tenant->name ?? $commission->tenant->company_name,
$commission->payment_type_label,
number_format($commission->payment_amount),
$commission->payment_date->format('Y-m-d'),
number_format($commission->base_amount),
$commission->partner?->user?->name ?? '-',
number_format($commission->partner_commission),
$commission->manager?->name ?? '-',
number_format($commission->manager_commission),
$commission->scheduled_payment_date->format('Y-m-d'),
$commission->status_label,
$commission->actual_payment_date?->format('Y-m-d') ?? '-',
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}