433 lines
14 KiB
PHP
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);
|
|
}
|
|
}
|