nhance/app/Views/invoice_policy_mapping_add.php
venba-Inspriron-3558 a1b59313af FIX_Minor Issues
2025-11-26 15:48:58 +05:30

609 lines
28 KiB
PHP
Executable File

<style>
/* ---- CSS cleaned/optimized ---- */
.table th, .table td { padding: 8px; }
table.dataTable tbody td { padding: 4px 4px !important; }
.col-12 { max-width: 98% !important; }
.dataTables_filter { position: absolute; }
.column-header { margin-right: 10px; }
.filter-inline { display: flex; align-items: center; gap: 10px; }
.filter-inline input[type="date"] { padding: 8px 12px; border:1px solid #ddd; border-radius:4px; font-size:13px; background:white; cursor:pointer; }
.filter-inline input[type="date"]:focus { outline:none; border-color:#00a9a3; }
.icon-btn { width:36px; height:36px; border:1px solid #ddd; background:white; border-radius:4px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; }
.icon-btn:hover { background:#f5f5f5; border-color:#00a9a3; }
.icon-btn svg { width:18px; height:18px; fill:#666; }
.icon-btn:hover svg { fill:#00a9a3; }
.policy-list { border:1px solid #e0e0e0; border-radius:4px; overflow:hidden; margin-top:0; }
.policy-header { background:#f8f9fa; padding:12px 15px; font-weight:600; border-bottom:1px solid #e0e0e0; display:flex; align-items:center; font-size:14px; }
.policy-header input[type="checkbox"] { width:18px; height:18px; margin-right:10px; cursor:pointer; }
.policy-item { border-bottom:1px solid #e0e0e0; padding:12px 15px; display:flex; align-items:center; background:white; transition:background 0.2s; }
.policy-item:hover { background:#f9f9f9; }
.policy-item:last-child { border-bottom:none; }
.policy-item.selected { background:#e8f5f4; }
.policy-checkbox { width:18px; height:18px; margin-right:15px; cursor:pointer; }
.policy-info { flex:1; display:flex; justify-content:space-between; align-items:center; }
.policy-details { flex:1; }
.policy-number { font-weight:600; color:#333; margin-bottom:4px; font-size:14px; }
.policy-meta { color:#666; font-size:13px; }
.policy-amount { font-size:16px; font-weight:600; color:#00a9a3; margin-left:20px; }
.badge { display:inline-block; padding:4px 10px; border-radius:12px; font-size:12px; font-weight:600; margin-left:10px; }
.badge-selected { background:#00a9a3; color:white; }
.summary-bar { position:fixed; bottom:0; left:0; right:0; width:100%; background:white; border-top:2px solid #00a9a3; padding:15px 30px; display:none; box-shadow:0 -2px 10px rgba(0,0,0,0.1); z-index:100; }
.summary-bar.show { display:flex; justify-content:flex-start; align-items:center; }
.summary-info { margin-left:auto; margin-right:0; display:flex; gap:40px; align-items:center; }
.summary-item { display:flex; flex-direction:column; }
.summary-label { font-size:12px; color:#666; margin-bottom:2px; }
.summary-value { font-size:18px; font-weight:600; color:#00a9a3; }
.summary-actions { display:flex; gap:10px; margin-left:auto; }
.btn-icon { background:#008b8b; border:none; border-radius:50%; width:32px; height:32px; display:flex; align-items:center; justify-content:center; cursor:pointer; }
.btn-icon i { color:#fff !important; font-size:18px; }
.btn-icon:hover { background:#00a3a3; }
/* Responsive */
@media (max-width:1024px){ .filter-inline{flex-wrap:wrap;} .filter-inline input[type="date"]{font-size:12px;padding:6px 8px;} }
@media (max-width:768px){
.section-header{flex-direction:column;gap:10px;align-items:flex-start;}
.header-right{width:100%;justify-content:space-between;}
.filter-inline{flex:1;}
.summary-bar{left:0;flex-direction:column;gap:15px;padding:15px;}
.summary-info{width:100%;justify-content:space-around;}
.summary-actions{width:100%;}
}
.dataTables_length label {height: 21px !important;}
</style>
<!-- ---- HTML structure remains mostly same ---- -->
<div class="row" id="invoices_details">
<div class="col-12">
<div id="invoices_accordion" class="ml-3">
<div class="card mb-1">
<h4 class="m-1">
<a href="#" onclick="history.back(); return false;">
<i style="font-size: 18px;" class="mdi mdi-chevron-left" title="Back"></i>
</a>
<span>Invoice Details</span>
<a id="toggleIcon" class="text-dark float-right" data-toggle="collapse" href="#collapseOne"
aria-expanded="true">
<i id="icon" class="mdi mdi-chevron-down mr-1 text-primary" style="font-size: 28px;"></i>
</a>
</h4>
<div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#invoices_accordion">
<div class="card-body" style="padding-bottom: unset;">
<div class="form-row">
<div class="form-group col-md-3">
<label>Invoice Number </label>
<input class="form-control" type="text" id="invoiceNo" placeholder="Auto-generated" readonly>
</div>
<div class="form-group col-md-3">
<label>Invoice Date <span class="text-danger"></span></label>
<input class="form-control" type="date" id="invoiceDate" required value="" >
</div>
<div class="form-group col-md-3">
<label for="agents"> Agents <span class="text-danger"></span></label>
<select class="form-control" id="agentSelect" name="agents_id" onchange="loadPolicies(); generateInvoiceNumberAjaxCall();" required autocomplete="off">
<option value="">Select Agent</option>
<?php
if(isset($agents) && count($agents)) {
foreach($agents as $agent): ?>
<option value="<?= $agent['id'] ?>">
<?= $agent['name'] ?> - <?= $agent['agent_code'] ?>
</option>
<?php endforeach;
}
?>
</select>
</div>
<div class="form-group col-md-3">
<label>Policy Till Date<span class="text-danger"></span></label>
<input class="form-control" type="date" id="policyTillDate" required onchange="loadPolicies()" max="<?= date('Y-m-d') ?>" >
</div>
<div class="form-group col-md-3">
<input class="form-control" type="hidden" id="invoiceID" required value="<?= isset($invoice['id']) ? $invoice['id'] : '' ?>" >
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" id="payouts_details">
<div class="col-12">
<div id="policy_accordion" class="ml-3">
<div class="card mb-1">
<div class="row align-items-center m-1" id="policy_filter">
<div class="col d-flex align-items-center">
<h4 class="mb-0">
Policy Selection
<span id="selectedCount" class="badge badge-selected" style="display:none;">0 selected</span>
</h4>
</div>
<div class="col-auto d-flex align-items-center">
<!-- <input type="date" class="form-control mr-2" id="fromDate" placeholder="From Date" title="From Date" style="width:170px;" autocomplete="off">
<input type="date" class="form-control mr-2" id="toDate" placeholder="To Date" title="To Date" style="width:170px;" autocomplete="off">
<button type="button" class="btn-icon mr-1" title="Reset Filters" onclick="resetFilters()"><i class="mdi mdi-refresh"></i></button>
<button type="button" class="btn-icon mr-1" title="Search" onclick="searchPolicies()"><i class="mdi mdi-magnify"></i></button> -->
</div>
<a id="toggleIcon" class="text-dark float-right" data-toggle="collapse" href="#collapseTwo" aria-expanded="true">
<i id="icon" class="mdi mdi-chevron-down mr-1 text-primary" style="font-size: 28px;"></i>
</a>
</div>
<div id="collapseTwo" class="collapse show" aria-labelledby="headingTwo" data-parent="#policy_accordion">
<div class="card-body" style="padding-top: unset !important; border: white !important; background: unset !important;">
<div>
<div>
<table data-custom-table-css="table" class="table table-striped mb-0 nowrap" cellspacing="0" id="tickets-table">
<thead class="bg-light">
<tr>
<th style="text-align:center;"><input type="checkbox" class="CB" id="selectAll" onchange="toggleSelectAll(this)"></th>
<th><div class="column-header">Policy No</div></th> <!-- 2 -->
<th><div class="column-header">Customer</div></th> <!-- 3 -->
<th><div class="column-header">Premium</div></th> <!-- 4 -->
<th><div class="column-header">Policy Issues Date</div></th> <!-- 5 -->
<th><div class="column-header">Commission Amount</div></th> <!-- 6 -->
</tr>
</thead>
<tbody id="ticketListBody">
</tbody>
</table>
</div>
<div id="summaryBar" class="summary-bar">
<div class="summary-info">
<div class="summary-item">
<span class="summary-label">Selected Policies</span>
<span class="summary-value" id="totalPolicies">0</span>
</div>
<div class="summary-item">
<span class="summary-label">Total Commission</span>
<span class="summary-value" id="totalAmount">₹0.00</span>
</div>
</div>
<div class="summary-actions">
<button type="button" class="btn app-btn-outline-primary C" onclick="clearSelection()">Clear</button>
<button type="button" class="btn app-btn-secondary S" onclick="saveInvoice()">Submit</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- end col -->
</div>
<script>
let selectedPolicies = new Set();
let filteredPolicies = [];
let allPolicies = <?php echo json_encode($payouts); ?> || [];
// Helper
function getEl(id) { return document.getElementById(id); }
$(document).ready(function(){
// Initialize Select2
if ($('#agentSelect').select2) $('#agentSelect').select2();
// Clear fields without triggering change
$("#invoiceNo").val("");
$("#agentSelect").val(""); // do NOT trigger change
// Set max dates for date fields
const today = new Date().toISOString().split("T")[0];
if (getEl('invoiceDate')) getEl('invoiceDate').max = today;
if (getEl('policyTillDate')) getEl('policyTillDate').max = today;
if (getEl('invoiceDate')) getEl('invoiceDate').valueAsDate = new Date();
if (getEl('policyTillDate')) getEl('policyTillDate').valueAsDate = new Date();
// Initial empty DataTable so buttons/search are available even before loading policies
if ($.fn.DataTable.isDataTable('#tickets-table')) {
$('#tickets-table').DataTable().clear().destroy();
}
$('#tickets-table').DataTable({
dom:
"<'row'<'col-sm-6'f><'col-sm-6 text-right'B>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
buttons: [
{
extend: 'collection',
text: '<span class=" btn-custom"> Export </span><i class="mdi mdi-menu-down"></i>',
className: 'btn app-btn-secondary ',
buttons: [
{
extend: 'csv',
text: '<i class="mdi mdi-file-delimited"></i><span class="btn-custom"> CSV </span>',
className: 'app-btn-primary',
title: getExportFileName(),
exportOptions: {columns: ':not(:first-child)'}
},
{
extend: 'excel',
text: '<i class="mdi mdi-file-excel"></i><span class="btn-custom"> EXCEL </span>',
className: 'app-btn-primary',
title: getExportFileName(),
filename: getExportFileName(),
exportOptions: {columns: ':not(:first-child)'}
}
]
}
],
language: {
search: `
<div class="datatable-search-wrapper" style="position:relative; display:inline-block;">
_INPUT_
<i class="mdi mdi-magnify datatable-search-icon"
style="position:absolute; right:10px; top:50%; transform:translateY(-50%); color:#666;"></i>
<i class="mdi mdi-close-circle datatable-clear-icon"
style="position:absolute; right:10px; top:50%; transform:translateY(-50%); color:#666; display:none;"></i>
</div>`,
searchPlaceholder: "Search",
emptyTable: '<div class="text-center text-muted">No Data found</div>'
},
paging: false,
ordering: false,
});
});
// MAIN: load and render policies for selected agent / date
function loadPolicies() {
let agentEl = getEl('agentSelect');
let policyTillDateEl = getEl('policyTillDate');
let list = getEl('ticketListBody');
if (!list) return;
let agentId = agentEl ? agentEl.value : '';
let policyTillDate = policyTillDateEl ? policyTillDateEl.value : '';
console.log('policyTillDate', policyTillDate);
selectedPolicies.clear();
if (!agentId || agentId === '' || agentId === '0') {
// Destroy DataTable so table returns to normal
if ($.fn.DataTable.isDataTable('#tickets-table')) {
$('#tickets-table').DataTable().clear().destroy();
}
list.innerHTML = `<tr><td colspan="6" class="text-center text-danger">Please select an agent</td></tr>`;
reinitDataTable();
updateSummary();
return;
}
filteredPolicies = allPolicies.filter(p => String(p.agentId) === String(agentId));
// policy Till Date filter
filteredPolicies = allPolicies.filter(p => {
if (String(p.agentId) !== String(agentId)) return false;
if (policyTillDate && p.date_db > policyTillDate) return false;
return true;
});
let rows = '';
filteredPolicies.forEach(p => {
const idStr = String(p.id);
const checked = selectedPolicies.has(idStr) ? 'checked' : '';
rows += `
<tr>
<td style="text-align:center;">
<input type="checkbox" class="policy-checkbox CB"
value="${idStr}" ${checked}
onchange="togglePolicy(this.value, this.checked)">
</td>
<td>${p.policyNo || ''}</td>
<td>${p.customer || ''}</td>
<td>₹${Number(p.premium || 0).toFixed(2)}</td>
<td>${p.date || ''}</td>
<td>₹${Number(p.commission || 0).toFixed(2)}</td>
</tr>
`;
});
// Destroy DataTable BEFORE inserting rows
if ($.fn.DataTable.isDataTable('#tickets-table')) {
$('#tickets-table').DataTable().clear().destroy();
}
// Insert rows
list.innerHTML = rows;
// Reinitialize AFTER writing rows
reinitDataTable();
updateSelectAllCheckbox();
updateSummary();
}
// DataTable re-init helper (destroy + create)
function reinitDataTable() {
if ($.fn.DataTable.isDataTable('#tickets-table')) {
try {
$('#tickets-table').DataTable().clear().destroy();
} catch (e) {
// ignore destroy errors
}
}
$('#tickets-table').DataTable({
dom:
"<'row'<'col-sm-6'f><'col-sm-6 text-right'B>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
buttons: [
{
extend: 'collection',
text: '<span class=" btn-custom"> Export </span><i class="mdi mdi-menu-down"></i>',
className: 'btn app-btn-secondary ',
buttons: [
{
extend: 'csv',
text: '<i class="mdi mdi-file-delimited"></i><span class="btn-custom"> CSV </span>',
className: 'app-btn-primary',
title: getExportFileName(),
exportOptions: {columns: ':not(:first-child)'}
},
{
extend: 'excel',
text: '<i class="mdi mdi-file-excel"></i><span class="btn-custom"> EXCEL </span>',
className: 'app-btn-primary',
title: getExportFileName(),
filename: getExportFileName(),
exportOptions: {columns: ':not(:first-child)'}
}
]
}
],
language: {
search: `
<div class="datatable-search-wrapper" style="position:relative; display:inline-block;">
_INPUT_
<i class="mdi mdi-magnify datatable-search-icon"
style="position:absolute; right:10px; top:50%; transform:translateY(-50%); color:#666;"></i>
<i class="mdi mdi-close-circle datatable-clear-icon"
style="position:absolute; right:10px; top:50%; transform:translateY(-50%); color:#666; display:none;"></i>
</div>`,
searchPlaceholder: "Search",
emptyTable: '<div class="text-center text-muted">No Data found</div>'
},
paging: false,
ordering: false,
// After init, align first column
initComplete: function() {
$('#tickets-table tbody tr td:first-child').css('text-align', 'center');
}
});
}
// Toggle single policy selection (called from checkbox onchange)
function togglePolicy(policyId, checked) {
policyId = String(policyId);
if (checked === undefined) {
// if called without the checked param, toggle based on presence
if (selectedPolicies.has(policyId)) selectedPolicies.delete(policyId);
else selectedPolicies.add(policyId);
} else {
if (checked) selectedPolicies.add(policyId);
else selectedPolicies.delete(policyId);
}
// update UI elements that reflect selection
updateSelectAllCheckbox();
updateSummary();
}
// Select / deselect all currently filtered policies
function toggleSelectAll(sourceCheckbox) {
if (!filteredPolicies || filteredPolicies.length === 0) return;
if (sourceCheckbox.checked) {
filteredPolicies.forEach(p => selectedPolicies.add(String(p.id)));
} else {
filteredPolicies.forEach(p => selectedPolicies.delete(String(p.id)));
}
// update all visible checkboxes to match selection
document.querySelectorAll('#ticketListBody .policy-checkbox').forEach(cb => {
cb.checked = selectedPolicies.has(cb.value);
});
updateSummary();
}
// Update header select-all checkbox state
function updateSelectAllCheckbox() {
const selectAllCheckbox = getEl('selectAll');
if (!selectAllCheckbox) return;
const allSelected = filteredPolicies.length > 0 && filteredPolicies.every(p => selectedPolicies.has(String(p.id)));
selectAllCheckbox.checked = !!allSelected;
}
// Update summary UI (footer/summaryBar)
function updateSummary() {
const summaryBar = getEl('summaryBar');
const selectedCountBadge = getEl('selectedCount');
const totalPoliciesEl = getEl('totalPolicies');
const totalAmountEl = getEl('totalAmount');
if (!totalPoliciesEl || !totalAmountEl) {
return;
}
if (selectedPolicies.size === 0) {
if (summaryBar) summaryBar.classList && summaryBar.classList.remove('show');
if (selectedCountBadge) selectedCountBadge.style.display = 'none';
totalPoliciesEl.textContent = '0';
totalAmountEl.textContent = '₹0.00';
return;
}
if (selectedCountBadge) {
selectedCountBadge.style.display = 'inline-block';
selectedCountBadge.textContent = `${selectedPolicies.size} selected`;
}
if (summaryBar) summaryBar.classList && summaryBar.classList.add('show');
const totalAmount = filteredPolicies
.filter(p => selectedPolicies.has(String(p.id)))
.reduce((sum, p) => sum + Number(p.commission || 0), 0);
totalPoliciesEl.textContent = String(selectedPolicies.size);
totalAmountEl.textContent = `₹${totalAmount.toFixed(2)}`;
}
// Clear selection
function clearSelection() {
if (!confirm('Are you sure you want to clear the selection?')) return;
selectedPolicies.clear();
// uncheck all checkboxes in view
document.querySelectorAll('#ticketListBody .policy-checkbox').forEach(cb => cb.checked = false);
updateSelectAllCheckbox();
updateSummary();
}
// Reset filters (keeps agent selection; clear others)
function resetFilters() {
// Keep agent selected
['fromDate','toDate','from_date','to_date'].forEach(id => {
if (getEl(id)) getEl(id).value = '';
});
}
// Save invoice (AJAX)
function saveInvoice() {
const agentId = getEl('agentSelect')?.value || '';
const invoiceNo = getEl('invoiceNo')?.value || '';
const invoiceDate = getEl('invoiceDate')?.value || '';
const policyTillDate = getEl('policyTillDate')?.value || '';
const invoiceID = '';
if (!agentId || agentId === '0') return toastr.warning('Please select an agent', 'Required');
if (!invoiceDate) return toastr.warning('Please select invoice date', 'Required');
if (selectedPolicies.size === 0) return toastr.warning('Please select at least one policy', 'Required');
const selectedPolicyData = filteredPolicies.filter(p => selectedPolicies.has(String(p.id)));
console.log('selectedPolicyData');
console.log(selectedPolicyData);
let totalAmount = 0;
const policies = selectedPolicyData.map(p => {
let finalAmount = Number(p.commission || 0);
totalAmount += finalAmount;
return {
policy_id: p.id,
policy_no: p.policyNo,
commission_amount: finalAmount,
partner_policy_id : p.partner_policy_id
};
});
const invoiceData = {
invoice_id: invoiceID,
invoice_no: invoiceNo,
agent_id: agentId,
invoice_date: invoiceDate,
invoice_amount: totalAmount,
policies: policies
};
$.ajax({
url: '<?= base_url('payout/invoices/save') ?>',
type: 'POST',
data: JSON.stringify(invoiceData),
contentType: 'application/json',
success: function(response) {
// toastr.success(`Invoice created successfully!<br>Invoice No: ${invoiceNo}`, 'Success');
toastr.success(response.message, 'Success');
window.location.href = '<?= base_url('payout/list') ?>';
},
error: function() {
toastr.error(response.message, 'Invoice Error');
// toastr.error('An error occurred while creating the invoice. Please try again.','Invoice Error');
}
});
}
function getExportFileName() {
let d = new Date();
let day = String(d.getDate()).padStart(2, '0');
let month = String(d.getMonth() + 1).padStart(2, '0');
let year = d.getFullYear();
return `Payouts (${day}-${month}-${year})`;
}
function generateInvoiceNumberAjaxCall() {
let agentId = $("#agentSelect").val();
if (!agentId) {
$("#invoiceNo").val("");
toastr.error("Please Select The Agent");
return;
}
$.ajax({
url: "<?= base_url('payout/invoice/generate-number') ?>/" + agentId,
method: "GET",
dataType: "json",
success: function (res) {
if (res.status === "success") {
$("#invoiceNo").val(res.invoice_no);
} else {
toastr.error(res.message);
}
},
error: function () {
toastr.error("Error generating invoice number");
}
});
}
// function searchPolicies() {
// filterPolicies();
// }
// function filterPolicies() {
// const fromDate = document.getElementById('fromDate').value;
// const toDate = document.getElementById('toDate').value;
// const agentId = document.getElementById('agentSelect').value;
// const policyTillDate = document.getElementById('policyTillDate').value;
// if (!agentId) {
// alert('Please select an agent first');
// return;
// }
// filteredPolicies = allPolicies.filter(p => {
// if (p.agentId != agentId) return false;
// if (policyTillDate && p.date_db > policyTillDate) return false;
// if (fromDate && p.date_db < fromDate) return false;
// if (toDate && p.date_db > toDate) return false;
// return true;
// });
// loadPolicies();
// }
</script>