You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
19 KiB
525 lines
19 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}Payment Plan #{{ plan.id }} - Plutus{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
/* Background styling */
|
|
body {
|
|
background-color: #3a3a3a !important;
|
|
background-image: url("{{ url_for('static', filename='images/plutus3.JPG') }}") !important;
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-attachment: fixed;
|
|
position: relative;
|
|
}
|
|
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(58, 58, 58, 0.85);
|
|
z-index: -1;
|
|
}
|
|
|
|
/* Ensure content is visible */
|
|
.container-fluid, .container {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Ensure navbar and footer are above background */
|
|
.navbar, .footer, main {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Page title and breadcrumb styling */
|
|
h1, h2, h3, h4, h5, h6 {
|
|
color: #faf8f0 !important;
|
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
|
}
|
|
|
|
.breadcrumb-item a {
|
|
color: #d4af37 !important;
|
|
}
|
|
|
|
.breadcrumb-item.active {
|
|
color: #faf8f0 !important;
|
|
}
|
|
|
|
/* Enhanced visibility */
|
|
.card, .info-card {
|
|
background-color: rgba(250, 248, 240, 0.98) !important;
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.alert {
|
|
background-color: rgba(250, 248, 240, 0.98);
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* Status banner styling with colorful borders */
|
|
.status-banner {
|
|
border-left: 5px solid;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.status-banner.active {
|
|
border-color: #28a745;
|
|
background: linear-gradient(135deg, #f0fff4 0%, #e8f5e9 100%);
|
|
}
|
|
|
|
.status-banner.inactive {
|
|
border-color: #ffc107;
|
|
background: linear-gradient(135deg, #fffbf0 0%, #fff3cd 100%);
|
|
}
|
|
|
|
.status-banner .status-icon {
|
|
font-size: 2.5rem;
|
|
}
|
|
|
|
.status-banner .status-title {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.status-banner .status-text {
|
|
color: #6c757d;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.amount-display {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: #d4af37;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.frequency-text {
|
|
color: #6c757d;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
/* Card styling with subtle shadows */
|
|
.info-card {
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.info-card:hover {
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.card-header-custom {
|
|
background: linear-gradient(135deg, #d4af37 0%, #c5a028 100%);
|
|
color: #000;
|
|
font-weight: 600;
|
|
padding: 1rem 1.25rem;
|
|
border-radius: 0.5rem 0.5rem 0 0;
|
|
}
|
|
|
|
/* Table styling enhancements */
|
|
.table-config td:first-child {
|
|
background-color: #f8f9fa;
|
|
font-weight: 600;
|
|
width: 40%;
|
|
}
|
|
|
|
/* Payment summary badges */
|
|
.summary-badge {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.375rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Loading spinner */
|
|
.spinner-container {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.spinner-border-custom {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
color: #d4af37;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Load customer information
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('Payment Plan Detail: DOM loaded, initializing customer fetch');
|
|
|
|
const splynxId = {{ plan.Splynx_ID }};
|
|
const customerInfoDiv = document.getElementById('customerInfo');
|
|
|
|
fetch(`/api/splynx/${splynxId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data && data.id) {
|
|
displayCustomerInfo(data);
|
|
} else {
|
|
showCustomerError('Customer not found');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching customer:', error);
|
|
showCustomerError('Error loading customer details');
|
|
});
|
|
});
|
|
|
|
function displayCustomerInfo(customer) {
|
|
const customerInfoDiv = document.getElementById('customerInfo');
|
|
|
|
const infoHtml = `
|
|
<table class="table table-sm mb-0">
|
|
<tbody>
|
|
<tr>
|
|
<td style="width: 40%; background-color: #f8f9fa;"><strong>Customer ID</strong></td>
|
|
<td>
|
|
<a href="https://billing.interphone.com.au/admin/customers/view?id=${customer.id}"
|
|
target="_blank" class="badge bg-info text-decoration-none">${customer.id}</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background-color: #f8f9fa;"><strong>Name</strong></td>
|
|
<td>${customer.name || 'N/A'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background-color: #f8f9fa;"><strong>Email</strong></td>
|
|
<td>${customer.email || 'N/A'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background-color: #f8f9fa;"><strong>Phone</strong></td>
|
|
<td>${customer.phone || 'N/A'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background-color: #f8f9fa;"><strong>Status</strong></td>
|
|
<td>
|
|
${customer.status === 'active'
|
|
? '<span class="badge bg-success">Active</span>'
|
|
: `<span class="badge bg-warning">${customer.status || 'Unknown'}</span>`
|
|
}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background-color: #f8f9fa;"><strong>Address</strong></td>
|
|
<td>
|
|
${customer.street_1 || ''} ${customer.street_2 || ''}<br>
|
|
${customer.city || ''} ${customer.zip_code || ''}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
|
|
customerInfoDiv.innerHTML = infoHtml;
|
|
}
|
|
|
|
function showCustomerError(message) {
|
|
const customerInfoDiv = document.getElementById('customerInfo');
|
|
customerInfoDiv.innerHTML = `
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-exclamation-triangle fa-3x text-danger mb-3"></i>
|
|
<p class="text-danger mb-0">${message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Search functionality for associated payments
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchInput = document.getElementById('paymentsSearchInput');
|
|
const table = document.getElementById('paymentsTable');
|
|
|
|
if (!table || !searchInput) return; // No table to search
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
const searchTerm = this.value.toLowerCase();
|
|
const rows = table.querySelectorAll('tbody tr');
|
|
|
|
rows.forEach(function(row) {
|
|
const paymentId = row.dataset.paymentId;
|
|
const amount = row.dataset.amount;
|
|
const status = row.dataset.status;
|
|
const rowText = row.textContent.toLowerCase();
|
|
|
|
const matches = !searchTerm ||
|
|
paymentId.includes(searchTerm) ||
|
|
amount.includes(searchTerm) ||
|
|
status.includes(searchTerm) ||
|
|
rowText.includes(searchTerm);
|
|
|
|
row.style.display = matches ? '' : 'none';
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{{ url_for('main.payment_plans_list') }}">Payment Plans</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">Plan #{{ plan.id }}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="d-flex justify-content-between align-items-start mb-4">
|
|
<div>
|
|
<h1 class="h2 mb-1">Payment Plan #{{ plan.id }}</h1>
|
|
<p class="text-muted">Created: {{ plan.Created.strftime('%Y-%m-%d %H:%M:%S') if plan.Created else 'Unknown' }}</p>
|
|
</div>
|
|
<div class="btn-group" role="group">
|
|
<form method="POST" action="{{ url_for('main.payment_plans_toggle', plan_id=plan.id) }}" style="display: inline;">
|
|
<button class="btn {% if plan.Enabled %}btn-warning{% else %}btn-success{% endif %}"
|
|
onclick="return confirm('Are you sure you want to {% if plan.Enabled %}disable{% else %}enable{% endif %} this payment plan?')">
|
|
<i class="fas {% if plan.Enabled %}fa-pause{% else %}fa-play{% endif %}"></i>
|
|
{% if plan.Enabled %}Disable{% else %}Enable{% endif %}
|
|
</button>
|
|
</form>
|
|
<a class="btn btn-info" href="{{ url_for('main.payment_plans_edit', plan_id=plan.id) }}">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</a>
|
|
<a class="btn btn-light" href="{{ url_for('main.payment_plans_list') }}">
|
|
<i class="fas fa-arrow-left"></i> Back to List
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Payment Plan Status Banner -->
|
|
<div class="status-banner {% if plan.Enabled %}active{% else %}inactive{% endif %} mb-4">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-4">
|
|
{% if plan.Enabled %}
|
|
<i class="fas fa-calendar-check status-icon text-success"></i>
|
|
{% else %}
|
|
<i class="fas fa-calendar-times status-icon text-warning"></i>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
{% if plan.Enabled %}
|
|
<h2 class="status-title text-success">Active Payment Plan</h2>
|
|
<p class="status-text">This payment plan is currently active and processing payments.</p>
|
|
{% else %}
|
|
<h2 class="status-title text-warning">Inactive Payment Plan</h2>
|
|
<p class="status-text">This payment plan is disabled and not processing payments.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<p class="amount-display">{{ plan.Amount | currency }}</p>
|
|
<p class="frequency-text">{{ plan.Frequency }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-user me-2"></i>Customer Information
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="customerInfo" data-splynx-id="{{ plan.Splynx_ID }}">
|
|
<div class="spinner-container">
|
|
<div class="spinner-border spinner-border-custom" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-3 text-muted">Loading customer details...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-cog me-2"></i>Plan Configuration
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm table-config mb-0">
|
|
<tbody>
|
|
<tr>
|
|
<td>Plan ID</td>
|
|
<td>#{{ plan.id }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Payment Amount</td>
|
|
<td><strong class="text-success">{{ plan.Amount | currency }}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Frequency</td>
|
|
<td>
|
|
<span class="badge {% if plan.Frequency == 'Weekly' %}bg-warning{% elif plan.Frequency == 'Fortnightly' %}bg-info{% else %}bg-secondary{% endif %}">
|
|
{{ plan.Frequency }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Start Date</td>
|
|
<td>
|
|
{{ plan.Start_Date.strftime('%Y-%m-%d') if plan.Start_Date else '-' }}
|
|
{% if plan.Start_Date %}
|
|
<br><small class="text-muted">Payments occur every {{ plan.Frequency.lower() }} from this date</small>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Payment Method</td>
|
|
<td>
|
|
<code class="small">{{ plan.Stripe_Payment_Method[:20] }}{% if plan.Stripe_Payment_Method|length > 20 %}...{% endif %}</code>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Status</td>
|
|
<td>
|
|
{% if plan.Enabled %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">Inactive</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Created</td>
|
|
<td>{{ plan.Created.strftime('%Y-%m-%d %H:%M:%S') if plan.Created else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Created By</td>
|
|
<td>{{ plan.created_by or 'Unknown' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Associated Payments -->
|
|
<div class="card info-card">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h3 class="h4 mb-0">
|
|
<i class="fas fa-list me-2"></i>Associated Payments
|
|
</h3>
|
|
<div class="input-group" style="max-width: 300px;">
|
|
<span class="input-group-text"><i class="fas fa-search"></i></span>
|
|
<input class="form-control" type="text" id="paymentsSearchInput" placeholder="Search payments...">
|
|
</div>
|
|
</div>
|
|
|
|
{% if associated_payments %}
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover" id="paymentsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Payment ID</th>
|
|
<th>Amount</th>
|
|
<th>Status</th>
|
|
<th>Payment Intent</th>
|
|
<th>Processed</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for payment in associated_payments %}
|
|
<tr data-payment-id="{{ payment.id }}"
|
|
data-amount="{{ payment.Payment_Amount }}"
|
|
data-status="{{ 'successful' if payment.Success == True else 'failed' if payment.Success == False else 'pending' }}">
|
|
<td>
|
|
<a href="{{ url_for('main.payment_detail', payment_id=payment.id) }}"
|
|
class="fw-semibold text-primary">
|
|
#{{ payment.id }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<strong>{{ payment.Payment_Amount | currency }}</strong>
|
|
</td>
|
|
<td>
|
|
{% if payment.Refund == True %}
|
|
<span class="status-badge refund">
|
|
<i class="fas fa-undo"></i>
|
|
Refund
|
|
</span>
|
|
{% elif payment.Success == True %}
|
|
<span class="status-badge success">
|
|
<i class="fas fa-check"></i>
|
|
Success
|
|
</span>
|
|
{% elif payment.Success == False and payment.PI_FollowUp %}
|
|
<span class="status-badge pending">
|
|
<i class="fas fa-clock"></i>
|
|
Pending
|
|
</span>
|
|
{% elif payment.Success == False %}
|
|
<span class="status-badge failed">
|
|
<i class="fas fa-times"></i>
|
|
Failed
|
|
</span>
|
|
{% else %}
|
|
<span class="status-badge pending">
|
|
<i class="fas fa-clock"></i>
|
|
Pending
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if payment.Payment_Intent %}
|
|
<code class="small">{{ payment.Payment_Intent[:20] }}...</code>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ payment.Created.strftime('%Y-%m-%d %H:%M') if payment.Created else '-' }}</td>
|
|
<td>
|
|
<a class="btn btn-sm btn-info"
|
|
href="{{ url_for('main.payment_detail', payment_id=payment.id) }}">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Payments Summary -->
|
|
<div class="d-flex justify-content-between align-items-center mt-4 pt-3 border-top">
|
|
<div>
|
|
<p class="h6 mb-1">Payment Summary</p>
|
|
<p class="text-muted small mb-0">Total: {{ associated_payments|length }} payments</p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<span class="badge bg-success summary-badge">
|
|
{{ associated_payments|selectattr('Success', 'equalto', True)|list|length }} Successful
|
|
</span>
|
|
<span class="badge bg-danger summary-badge">
|
|
{{ associated_payments|selectattr('Success', 'equalto', False)|list|length }} Failed
|
|
</span>
|
|
<span class="badge bg-warning summary-badge">
|
|
{{ associated_payments|selectattr('Success', 'equalto', None)|list|length }} Pending
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-receipt fa-4x text-muted mb-3"></i>
|
|
<p class="h5 text-muted mb-2">No Associated Payments</p>
|
|
<p class="text-muted">This payment plan hasn't processed any payments yet.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|