- BarobillPricingPolicy 모델 추가 - BarobillPricingPolicySeeder 추가 (초기 정책 데이터) - 과금관리 페이지에 정책 관리 탭 추가 (본사 전용) - 정책 수정 모달 및 API 엔드포인트 추가 - BarobillUsageService에서 DB 정책 사용하도록 수정 정책 항목: - 법인카드 등록: 기본 3장, 추가 1장당 5,000원 - 계산서 발행: 기본 100건, 추가 50건당 5,000원 - 계좌조회 수집: 기본 1계좌, 추가 1계좌당 10,000원 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
518 lines
23 KiB
PHP
518 lines
23 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '과금관리')
|
|
|
|
@section('content')
|
|
<!-- 현재 테넌트 정보 카드 -->
|
|
@if($currentTenant)
|
|
<div class="rounded-xl shadow-lg p-5 mb-6" style="background: linear-gradient(to right, #059669, #10b981); color: white;">
|
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
|
<div class="flex items-center gap-4">
|
|
<div class="p-3 rounded-xl" style="background: rgba(255,255,255,0.2);">
|
|
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="px-2 py-0.5 rounded-full text-xs font-bold" style="background: rgba(255,255,255,0.2);">T-ID: {{ $currentTenant->id }}</span>
|
|
@if($isHeadquarters ?? false)
|
|
<span class="px-2 py-0.5 rounded-full text-xs font-bold" style="background: #facc15; color: #713f12;">파트너사</span>
|
|
@endif
|
|
</div>
|
|
<h2 class="text-xl font-bold">{{ $currentTenant->company_name }}</h2>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 lg:grid-cols-3 gap-3 text-sm">
|
|
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
|
<p class="text-xs" style="color: rgba(255,255,255,0.6);">조회 기준월</p>
|
|
<p class="font-medium" id="displayBillingMonth">{{ now()->format('Y년 m월') }}</p>
|
|
</div>
|
|
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
|
<p class="text-xs" style="color: rgba(255,255,255,0.6);">과금 유형</p>
|
|
<p class="font-medium">월정액 + 건별</p>
|
|
</div>
|
|
<div class="rounded-lg p-2" style="background: rgba(255,255,255,0.1);">
|
|
<p class="text-xs" style="color: rgba(255,255,255,0.6);">결제일</p>
|
|
<p class="font-medium">매월 1일</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="flex flex-col h-full">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 flex-shrink-0">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">과금관리</h1>
|
|
<p class="text-sm text-gray-500 mt-1">바로빌 월정액 구독 및 과금 현황 관리</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button
|
|
type="button"
|
|
onclick="processBilling()"
|
|
class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
과금 처리
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onclick="exportExcel()"
|
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
엑셀
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 탭 네비게이션 -->
|
|
<div class="flex border-b border-gray-200 mb-6 flex-shrink-0">
|
|
<button type="button" onclick="switchTab('billing')" id="tab-billing" class="px-6 py-3 text-sm font-medium border-b-2 border-blue-600 text-blue-600">
|
|
과금 현황
|
|
</button>
|
|
<button type="button" onclick="switchTab('subscription')" id="tab-subscription" class="px-6 py-3 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
구독 관리
|
|
</button>
|
|
@if($isHeadquarters ?? false)
|
|
<button type="button" onclick="switchTab('policy')" id="tab-policy" class="px-6 py-3 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700">
|
|
과금 정책
|
|
</button>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- 통계 카드 -->
|
|
<div id="stats-container"
|
|
hx-get="/api/admin/barobill/billing/stats"
|
|
hx-trigger="load, billingUpdated from:body"
|
|
hx-include="#billingFilterForm"
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
|
class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6 flex-shrink-0">
|
|
@include('barobill.billing.partials.stats-skeleton')
|
|
</div>
|
|
|
|
<!-- 필터 영역 -->
|
|
<div class="flex-shrink-0">
|
|
<x-filter-collapsible id="billingFilter">
|
|
<form id="billingFilterForm" class="flex flex-wrap gap-2 sm:gap-4 items-center">
|
|
<!-- 전체 테넌트 보기 토글 -->
|
|
@if($isHeadquarters ?? false)
|
|
<label class="flex items-center gap-2 px-3 py-2 bg-purple-50 border border-purple-200 rounded-lg cursor-pointer hover:bg-purple-100 transition-colors">
|
|
<input type="checkbox"
|
|
name="all_tenants"
|
|
value="1"
|
|
id="allTenantsToggle"
|
|
checked
|
|
class="w-4 h-4 rounded border-purple-300 text-purple-600 focus:ring-purple-500">
|
|
<span class="text-sm font-medium text-purple-700">전체 테넌트</span>
|
|
</label>
|
|
@endif
|
|
|
|
<!-- 기준월 -->
|
|
<div class="flex items-center gap-2">
|
|
<label for="billingMonth" class="text-sm font-medium text-gray-700 whitespace-nowrap">기준월</label>
|
|
<input type="month"
|
|
name="billing_month"
|
|
id="billingMonth"
|
|
value="{{ now()->format('Y-m') }}"
|
|
class="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm">
|
|
</div>
|
|
|
|
<!-- 빠른 선택 -->
|
|
<div class="flex gap-1">
|
|
<button type="button" onclick="setQuickMonth('thisMonth')" class="px-3 py-2 text-xs font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition">
|
|
이번달
|
|
</button>
|
|
<button type="button" onclick="setQuickMonth('lastMonth')" class="px-3 py-2 text-xs font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition">
|
|
지난달
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 조회 버튼 -->
|
|
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition flex items-center gap-2">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
</svg>
|
|
조회
|
|
</button>
|
|
</form>
|
|
</x-filter-collapsible>
|
|
</div>
|
|
|
|
<!-- 과금 현황 탭 -->
|
|
<div id="content-billing" class="flex-1 flex flex-col min-h-0">
|
|
<div id="billing-table"
|
|
hx-get="/api/admin/barobill/billing/list"
|
|
hx-trigger="load, billingUpdated from:body"
|
|
hx-include="#billingFilterForm"
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
|
class="bg-white rounded-lg shadow-sm overflow-hidden flex-1 flex flex-col min-h-0">
|
|
<div class="flex justify-center items-center p-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 구독 관리 탭 -->
|
|
<div id="content-subscription" class="hidden flex-1 flex flex-col min-h-0">
|
|
<div id="subscription-table"
|
|
hx-get="/api/admin/barobill/billing/subscriptions"
|
|
hx-trigger="subscriptionUpdated from:body"
|
|
hx-include="#billingFilterForm"
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
|
class="bg-white rounded-lg shadow-sm overflow-hidden flex-1 flex flex-col min-h-0">
|
|
<div class="flex justify-center items-center p-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 과금 정책 탭 (본사 전용) -->
|
|
@if($isHeadquarters ?? false)
|
|
<div id="content-policy" class="hidden flex-1 flex flex-col min-h-0">
|
|
<div id="policy-table"
|
|
hx-get="/api/admin/barobill/billing/pricing-policies"
|
|
hx-trigger="policyUpdated from:body"
|
|
hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
|
|
class="bg-white rounded-lg shadow-sm overflow-hidden flex-1 flex flex-col min-h-0">
|
|
<div class="flex justify-center items-center p-12">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- 상세 모달 -->
|
|
<div id="detailModal" class="fixed inset-0 z-50 hidden">
|
|
<div class="fixed inset-0 bg-black/50" onclick="closeDetailModal()"></div>
|
|
<div class="fixed inset-0 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto" onclick="event.stopPropagation()">
|
|
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between sticky top-0 bg-white">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-800">과금 상세</h3>
|
|
<p class="text-sm text-gray-500" id="detailModalTitle"></p>
|
|
</div>
|
|
<button type="button" onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div id="detailModalContent" class="p-6"></div>
|
|
<div class="px-6 py-4 border-t border-gray-100">
|
|
<button type="button" onclick="closeDetailModal()" class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
|
닫기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 정책 수정 모달 -->
|
|
@if($isHeadquarters ?? false)
|
|
<div id="policyModal" class="fixed inset-0 z-50 hidden">
|
|
<div class="fixed inset-0 bg-black/50" onclick="closePolicyModal()"></div>
|
|
<div class="fixed inset-0 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-xl shadow-xl w-full max-w-lg" onclick="event.stopPropagation()">
|
|
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-gray-800">과금 정책 수정</h3>
|
|
<button type="button" onclick="closePolicyModal()" class="text-gray-400 hover:text-gray-600">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<form id="policyForm" onsubmit="savePolicy(event)" class="p-6 space-y-4">
|
|
<input type="hidden" id="policyId" name="id">
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">서비스</label>
|
|
<input type="text" id="policyName" class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50" readonly>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">기본 제공량</label>
|
|
<input type="number" id="policyFreeQuota" name="free_quota" min="0" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">단위</label>
|
|
<input type="text" id="policyFreeQuotaUnit" name="free_quota_unit" maxlength="20" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">추가 과금 단위</label>
|
|
<input type="number" id="policyAdditionalUnit" name="additional_unit" min="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">단위 라벨</label>
|
|
<input type="text" id="policyAdditionalUnitLabel" name="additional_unit_label" maxlength="20" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">추가 과금 금액 (원)</label>
|
|
<input type="number" id="policyAdditionalPrice" name="additional_price" min="0" step="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">설명</label>
|
|
<input type="text" id="policyDescription" name="description" maxlength="255" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<input type="checkbox" id="policyIsActive" name="is_active" value="1" class="w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
|
<label for="policyIsActive" class="text-sm text-gray-700">활성화</label>
|
|
</div>
|
|
</form>
|
|
<div class="px-6 py-4 border-t border-gray-100 flex gap-2">
|
|
<button type="button" onclick="closePolicyModal()" class="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition">
|
|
취소
|
|
</button>
|
|
<button type="submit" form="policyForm" class="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
|
|
저장
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- 토스트 -->
|
|
<div id="toast" class="fixed bottom-4 right-4 px-4 py-3 rounded-lg shadow-lg transform translate-y-full opacity-0 transition-all duration-300 z-50"></div>
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
// 현재 활성 탭
|
|
let currentTab = 'billing';
|
|
|
|
// 탭 전환
|
|
function switchTab(tab) {
|
|
currentTab = tab;
|
|
|
|
const tabs = ['billing', 'subscription', 'policy'];
|
|
|
|
tabs.forEach(t => {
|
|
const tabBtn = document.getElementById(`tab-${t}`);
|
|
const content = document.getElementById(`content-${t}`);
|
|
|
|
if (tabBtn) {
|
|
tabBtn.classList.toggle('border-blue-600', t === tab);
|
|
tabBtn.classList.toggle('text-blue-600', t === tab);
|
|
tabBtn.classList.toggle('border-transparent', t !== tab);
|
|
tabBtn.classList.toggle('text-gray-500', t !== tab);
|
|
}
|
|
|
|
if (content) {
|
|
content.classList.toggle('hidden', t !== tab);
|
|
}
|
|
});
|
|
|
|
// 탭 전환 시 데이터 로드
|
|
if (tab === 'subscription') {
|
|
htmx.trigger(document.body, 'subscriptionUpdated');
|
|
} else if (tab === 'policy') {
|
|
htmx.trigger(document.body, 'policyUpdated');
|
|
}
|
|
}
|
|
|
|
// 폼 제출
|
|
document.getElementById('billingFilterForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
updateDisplayMonth();
|
|
htmx.trigger(document.body, 'billingUpdated');
|
|
});
|
|
|
|
// 기준월 표시 업데이트
|
|
function updateDisplayMonth() {
|
|
const month = document.getElementById('billingMonth').value;
|
|
const [year, mon] = month.split('-');
|
|
document.getElementById('displayBillingMonth').textContent = `${year}년 ${mon}월`;
|
|
}
|
|
|
|
// 빠른 월 선택
|
|
function setQuickMonth(period) {
|
|
const today = new Date();
|
|
let targetMonth;
|
|
|
|
if (period === 'thisMonth') {
|
|
targetMonth = today;
|
|
} else {
|
|
targetMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
|
}
|
|
|
|
const year = targetMonth.getFullYear();
|
|
const month = String(targetMonth.getMonth() + 1).padStart(2, '0');
|
|
document.getElementById('billingMonth').value = `${year}-${month}`;
|
|
|
|
updateDisplayMonth();
|
|
htmx.trigger(document.body, 'billingUpdated');
|
|
}
|
|
|
|
// 과금 처리
|
|
async function processBilling() {
|
|
if (!confirm('선택한 월의 과금을 처리하시겠습니까?')) return;
|
|
|
|
const billingMonth = document.getElementById('billingMonth').value;
|
|
|
|
try {
|
|
const res = await fetch('/api/admin/barobill/billing/process', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
},
|
|
body: JSON.stringify({ billing_month: billingMonth }),
|
|
});
|
|
|
|
const result = await res.json();
|
|
|
|
if (result.success) {
|
|
showToast(result.message, 'success');
|
|
htmx.trigger(document.body, 'billingUpdated');
|
|
} else {
|
|
showToast(result.message || '처리 실패', 'error');
|
|
}
|
|
} catch (error) {
|
|
showToast('오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 엑셀 다운로드
|
|
function exportExcel() {
|
|
const form = document.getElementById('billingFilterForm');
|
|
const formData = new FormData(form);
|
|
const params = new URLSearchParams(formData);
|
|
window.location.href = `/api/admin/barobill/billing/export?${params.toString()}`;
|
|
}
|
|
|
|
// 상세 모달
|
|
async function showDetailModal(memberId, memberName) {
|
|
document.getElementById('detailModalTitle').textContent = memberName;
|
|
document.getElementById('detailModalContent').innerHTML = `
|
|
<div class="flex justify-center py-8">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
`;
|
|
document.getElementById('detailModal').classList.remove('hidden');
|
|
|
|
try {
|
|
const billingMonth = document.getElementById('billingMonth').value;
|
|
const res = await fetch(`/api/admin/barobill/billing/member/${memberId}?billing_month=${billingMonth}`, {
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'HX-Request': 'true',
|
|
},
|
|
});
|
|
|
|
if (res.ok) {
|
|
const html = await res.text();
|
|
document.getElementById('detailModalContent').innerHTML = html;
|
|
}
|
|
} catch (error) {
|
|
document.getElementById('detailModalContent').innerHTML = `
|
|
<div class="text-center text-red-500 py-8">조회 실패</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function closeDetailModal() {
|
|
document.getElementById('detailModal').classList.add('hidden');
|
|
}
|
|
|
|
// 토스트
|
|
function showToast(message, type = 'success') {
|
|
const toast = document.getElementById('toast');
|
|
toast.textContent = message;
|
|
toast.className = 'fixed bottom-4 right-4 px-4 py-3 rounded-lg shadow-lg transform transition-all duration-300 z-50';
|
|
toast.classList.add(type === 'success' ? 'bg-green-600' : 'bg-red-600', 'text-white');
|
|
toast.classList.remove('translate-y-full', 'opacity-0');
|
|
|
|
setTimeout(() => {
|
|
toast.classList.add('translate-y-full', 'opacity-0');
|
|
}, 3000);
|
|
}
|
|
|
|
// ESC 키
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeDetailModal();
|
|
closePolicyModal();
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// 정책 관리
|
|
// ========================================
|
|
|
|
function editPolicy(id, policy) {
|
|
document.getElementById('policyId').value = id;
|
|
document.getElementById('policyName').value = policy.name;
|
|
document.getElementById('policyFreeQuota').value = policy.free_quota;
|
|
document.getElementById('policyFreeQuotaUnit').value = policy.free_quota_unit;
|
|
document.getElementById('policyAdditionalUnit').value = policy.additional_unit;
|
|
document.getElementById('policyAdditionalUnitLabel').value = policy.additional_unit_label;
|
|
document.getElementById('policyAdditionalPrice').value = policy.additional_price;
|
|
document.getElementById('policyDescription').value = policy.description || '';
|
|
document.getElementById('policyIsActive').checked = policy.is_active;
|
|
|
|
document.getElementById('policyModal').classList.remove('hidden');
|
|
}
|
|
|
|
function closePolicyModal() {
|
|
const modal = document.getElementById('policyModal');
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
async function savePolicy(e) {
|
|
e.preventDefault();
|
|
|
|
const id = document.getElementById('policyId').value;
|
|
const data = {
|
|
free_quota: parseInt(document.getElementById('policyFreeQuota').value),
|
|
free_quota_unit: document.getElementById('policyFreeQuotaUnit').value,
|
|
additional_unit: parseInt(document.getElementById('policyAdditionalUnit').value),
|
|
additional_unit_label: document.getElementById('policyAdditionalUnitLabel').value,
|
|
additional_price: parseInt(document.getElementById('policyAdditionalPrice').value),
|
|
description: document.getElementById('policyDescription').value,
|
|
is_active: document.getElementById('policyIsActive').checked,
|
|
};
|
|
|
|
try {
|
|
const res = await fetch(`/api/admin/barobill/billing/pricing-policies/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
|
|
const result = await res.json();
|
|
|
|
if (result.success) {
|
|
showToast(result.message, 'success');
|
|
closePolicyModal();
|
|
htmx.trigger(document.body, 'policyUpdated');
|
|
} else {
|
|
showToast(result.message || '저장 실패', 'error');
|
|
}
|
|
} catch (error) {
|
|
showToast('오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
@endpush
|