- 요약카드 4개→6개 확장 (등록카드, 총한도, 매월결제일, 사용금액, 선불결제, 잔여한도) - 매월결제일: 휴일/주말 시 다음 영업일로 자동 조정 표시 - 사용금액: barobill_card_transactions 기반 청구기간 실거래 합산 - 선불결제: 수정 모달로 테넌트 단위 월별 금액 관리 - 잔여한도: (총한도 - 사용금액 + 선불결제) 계산 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
369 lines
13 KiB
PHP
369 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Finance;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Barobill\CardTransaction;
|
|
use App\Models\Barobill\CardTransactionHide;
|
|
use App\Models\Finance\CorporateCard;
|
|
use App\Models\Finance\CorporateCardPrepayment;
|
|
use App\Models\System\Holiday;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
|
|
class CorporateCardController extends Controller
|
|
{
|
|
/**
|
|
* 카드 목록 조회
|
|
*/
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
|
|
$cards = CorporateCard::forTenant($tenantId)
|
|
->orderBy('created_at', 'desc')
|
|
->get()
|
|
->map(function ($card) {
|
|
return [
|
|
'id' => $card->id,
|
|
'cardName' => $card->card_name,
|
|
'cardCompany' => $card->card_company,
|
|
'cardNumber' => $card->card_number,
|
|
'cardType' => $card->card_type,
|
|
'paymentDay' => $card->payment_day,
|
|
'creditLimit' => (float) $card->credit_limit,
|
|
'currentUsage' => (float) $card->current_usage,
|
|
'cardHolderName' => $card->card_holder_name,
|
|
'actualUser' => $card->actual_user,
|
|
'expiryDate' => $card->expiry_date,
|
|
'cvc' => $card->cvc,
|
|
'status' => $card->status,
|
|
'memo' => $card->memo,
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $cards,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 카드 등록
|
|
*/
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'cardName' => 'required|string|max:100',
|
|
'cardCompany' => 'required|string|max:50',
|
|
'cardNumber' => 'required|string|max:30',
|
|
'cardType' => 'required|in:credit,debit',
|
|
'cardHolderName' => 'required|string|max:100',
|
|
'actualUser' => 'required|string|max:100',
|
|
]);
|
|
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
|
|
$card = CorporateCard::create([
|
|
'tenant_id' => $tenantId,
|
|
'card_name' => $request->input('cardName'),
|
|
'card_company' => $request->input('cardCompany'),
|
|
'card_number' => $request->input('cardNumber'),
|
|
'card_type' => $request->input('cardType'),
|
|
'payment_day' => $request->input('paymentDay', 15),
|
|
'credit_limit' => $request->input('creditLimit', 0),
|
|
'current_usage' => 0,
|
|
'card_holder_name' => $request->input('cardHolderName'),
|
|
'actual_user' => $request->input('actualUser'),
|
|
'expiry_date' => $request->input('expiryDate'),
|
|
'cvc' => $request->input('cvc'),
|
|
'status' => $request->input('status', 'active'),
|
|
'memo' => $request->input('memo'),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '카드가 등록되었습니다.',
|
|
'data' => [
|
|
'id' => $card->id,
|
|
'cardName' => $card->card_name,
|
|
'cardCompany' => $card->card_company,
|
|
'cardNumber' => $card->card_number,
|
|
'cardType' => $card->card_type,
|
|
'paymentDay' => $card->payment_day,
|
|
'creditLimit' => (float) $card->credit_limit,
|
|
'currentUsage' => (float) $card->current_usage,
|
|
'cardHolderName' => $card->card_holder_name,
|
|
'actualUser' => $card->actual_user,
|
|
'expiryDate' => $card->expiry_date,
|
|
'cvc' => $card->cvc,
|
|
'status' => $card->status,
|
|
'memo' => $card->memo,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 카드 수정
|
|
*/
|
|
public function update(Request $request, int $id): JsonResponse
|
|
{
|
|
$card = CorporateCard::findOrFail($id);
|
|
|
|
$request->validate([
|
|
'cardName' => 'required|string|max:100',
|
|
'cardCompany' => 'required|string|max:50',
|
|
'cardNumber' => 'required|string|max:30',
|
|
'cardType' => 'required|in:credit,debit',
|
|
'cardHolderName' => 'required|string|max:100',
|
|
'actualUser' => 'required|string|max:100',
|
|
]);
|
|
|
|
$card->update([
|
|
'card_name' => $request->input('cardName'),
|
|
'card_company' => $request->input('cardCompany'),
|
|
'card_number' => $request->input('cardNumber'),
|
|
'card_type' => $request->input('cardType'),
|
|
'payment_day' => $request->input('paymentDay', 15),
|
|
'credit_limit' => $request->input('creditLimit', 0),
|
|
'card_holder_name' => $request->input('cardHolderName'),
|
|
'actual_user' => $request->input('actualUser'),
|
|
'expiry_date' => $request->input('expiryDate'),
|
|
'cvc' => $request->input('cvc'),
|
|
'status' => $request->input('status', 'active'),
|
|
'memo' => $request->input('memo'),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '카드가 수정되었습니다.',
|
|
'data' => [
|
|
'id' => $card->id,
|
|
'cardName' => $card->card_name,
|
|
'cardCompany' => $card->card_company,
|
|
'cardNumber' => $card->card_number,
|
|
'cardType' => $card->card_type,
|
|
'paymentDay' => $card->payment_day,
|
|
'creditLimit' => (float) $card->credit_limit,
|
|
'currentUsage' => (float) $card->current_usage,
|
|
'cardHolderName' => $card->card_holder_name,
|
|
'actualUser' => $card->actual_user,
|
|
'expiryDate' => $card->expiry_date,
|
|
'cvc' => $card->cvc,
|
|
'status' => $card->status,
|
|
'memo' => $card->memo,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 카드 비활성화
|
|
*/
|
|
public function deactivate(int $id): JsonResponse
|
|
{
|
|
$card = CorporateCard::findOrFail($id);
|
|
$card->update(['status' => 'inactive']);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '카드가 비활성화되었습니다.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 카드 영구삭제
|
|
*/
|
|
public function destroy(int $id): JsonResponse
|
|
{
|
|
$card = CorporateCard::findOrFail($id);
|
|
$card->forceDelete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '카드가 영구 삭제되었습니다.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 요약 데이터 (결제일, 사용금액, 선불결제)
|
|
*/
|
|
public function summary(): JsonResponse
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
$now = Carbon::now();
|
|
|
|
// 활성 카드 조회
|
|
$activeCards = CorporateCard::forTenant($tenantId)->active()->get();
|
|
|
|
if ($activeCards->isEmpty()) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'paymentDate' => null,
|
|
'paymentDay' => 15,
|
|
'originalDate' => null,
|
|
'isAdjusted' => false,
|
|
'billingPeriod' => null,
|
|
'billingUsage' => 0,
|
|
'prepaidAmount' => 0,
|
|
'prepaidMemo' => '',
|
|
],
|
|
]);
|
|
}
|
|
|
|
// 대표 결제일 (첫 번째 활성 신용카드 기준)
|
|
$creditCard = $activeCards->firstWhere('card_type', 'credit');
|
|
$paymentDay = $creditCard ? $creditCard->payment_day : 15;
|
|
|
|
// 휴일 조정 결제일 계산
|
|
$originalDate = $this->createPaymentDate($now->year, $now->month, $paymentDay);
|
|
$adjustedDate = $this->getAdjustedPaymentDate($tenantId, $now->year, $now->month, $paymentDay);
|
|
$isAdjusted = !$originalDate->isSameDay($adjustedDate);
|
|
|
|
// 청구기간: 전월 1일 ~ 당월 결제일(조정후)
|
|
$billingStart = $now->copy()->subMonth()->startOfMonth();
|
|
$billingEnd = $adjustedDate->copy();
|
|
|
|
// 카드번호 목록
|
|
$cardNumbers = $activeCards->pluck('card_number')->toArray();
|
|
|
|
// 사용금액 계산
|
|
$billingUsage = $this->calculateBillingUsage(
|
|
$tenantId,
|
|
$billingStart->format('Y-m-d'),
|
|
$billingEnd->format('Y-m-d'),
|
|
$cardNumbers
|
|
);
|
|
|
|
// 선불결제 금액
|
|
$yearMonth = $now->format('Y-m');
|
|
$prepayment = CorporateCardPrepayment::getOrCreate($tenantId, $yearMonth);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'paymentDate' => $adjustedDate->format('Y-m-d'),
|
|
'paymentDay' => $paymentDay,
|
|
'originalDate' => $originalDate->format('Y-m-d'),
|
|
'isAdjusted' => $isAdjusted,
|
|
'billingPeriod' => [
|
|
'start' => $billingStart->format('Y-m-d'),
|
|
'end' => $billingEnd->format('Y-m-d'),
|
|
],
|
|
'billingUsage' => $billingUsage,
|
|
'prepaidAmount' => (int) $prepayment->amount,
|
|
'prepaidMemo' => $prepayment->memo ?? '',
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 선불결제 금액 수정
|
|
*/
|
|
public function updatePrepayment(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'amount' => 'required|integer|min:0',
|
|
'memo' => 'nullable|string|max:200',
|
|
]);
|
|
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
$yearMonth = Carbon::now()->format('Y-m');
|
|
|
|
$prepayment = CorporateCardPrepayment::updateOrCreate(
|
|
['tenant_id' => $tenantId, 'year_month' => $yearMonth],
|
|
['amount' => $request->input('amount'), 'memo' => $request->input('memo')]
|
|
);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '선불결제 금액이 저장되었습니다.',
|
|
'data' => [
|
|
'amount' => (int) $prepayment->amount,
|
|
'memo' => $prepayment->memo ?? '',
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 결제일 날짜 생성 (월 말일 초과 방지)
|
|
*/
|
|
private function createPaymentDate(int $year, int $month, int $day): Carbon
|
|
{
|
|
$maxDay = Carbon::create($year, $month)->daysInMonth;
|
|
return Carbon::create($year, $month, min($day, $maxDay));
|
|
}
|
|
|
|
/**
|
|
* 휴일/주말 조정된 결제일 계산
|
|
*/
|
|
private function getAdjustedPaymentDate(int $tenantId, int $year, int $month, int $paymentDay): Carbon
|
|
{
|
|
$date = $this->createPaymentDate($year, $month, $paymentDay);
|
|
|
|
// 해당 기간의 휴일 조회
|
|
$holidays = Holiday::forTenant($tenantId)
|
|
->where('start_date', '<=', $date->copy()->addDays(10)->format('Y-m-d'))
|
|
->where('end_date', '>=', $date->format('Y-m-d'))
|
|
->get();
|
|
|
|
$holidayDates = [];
|
|
foreach ($holidays as $h) {
|
|
$current = $h->start_date->copy();
|
|
while ($current <= $h->end_date) {
|
|
$holidayDates[] = $current->format('Y-m-d');
|
|
$current->addDay();
|
|
}
|
|
}
|
|
|
|
// 토/일/공휴일이면 다음 영업일로 이동
|
|
while ($date->isWeekend() || in_array($date->format('Y-m-d'), $holidayDates)) {
|
|
$date->addDay();
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* 청구기간 사용금액 계산 (바로빌 카드거래 합산)
|
|
*/
|
|
private function calculateBillingUsage(int $tenantId, string $startDate, string $endDate, array $cardNumbers): int
|
|
{
|
|
// 카드번호 정규화 (하이픈 제거)
|
|
$normalizedNums = array_map(fn($num) => str_replace('-', '', $num), $cardNumbers);
|
|
|
|
if (empty($normalizedNums)) {
|
|
return 0;
|
|
}
|
|
|
|
// use_date는 YYYYMMDD 형식
|
|
$startYmd = str_replace('-', '', $startDate);
|
|
$endYmd = str_replace('-', '', $endDate);
|
|
|
|
// 숨긴 거래 키 조회
|
|
$hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startYmd, $endYmd);
|
|
|
|
// 바로빌 카드거래 조회
|
|
$transactions = CardTransaction::where('tenant_id', $tenantId)
|
|
->whereIn('card_num', $normalizedNums)
|
|
->whereBetween('use_date', [$startYmd, $endYmd])
|
|
->get();
|
|
|
|
$total = 0;
|
|
foreach ($transactions as $tx) {
|
|
if ($hiddenKeys->contains($tx->unique_key)) {
|
|
continue;
|
|
}
|
|
|
|
if ($tx->approval_type === '1') {
|
|
$total += (int) $tx->approval_amount; // 승인
|
|
} elseif ($tx->approval_type === '2') {
|
|
$total -= (int) $tx->approval_amount; // 취소
|
|
}
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
}
|