« Back to History
multy_party_payment.php
|
20260721_154033.php
Initial Bulk Import
Copy Code
<?php /* ============================================================================= File: /erp/multy_party_payment.php Purpose: Save Multiple Party Bank Payments (Single Excel Auto Download) ============================================================================= */ header('X-Frame-Options: SAMEORIGIN'); require_once __DIR__ . '/modules/auth/page_acl.php'; require_once __DIR__ . '/helpers/activity_helper.php'; // Auth & Context Initialization $ctx = page_require_access('party_payment'); $u = $ctx['user'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $pdo = $ctx['pdo']; $user_id = (int)($u['id'] ?? 0); function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } $success = ''; $error = ''; // Build Export Rows with Company NEFT Limit Splitting function build_export_rows(float $company_limit, int $account_id, float $amount, string $remark): array { $export_rows = []; if ($company_limit > 0 && $amount > $company_limit) { $remaining = $amount; while ($remaining > 0) { $part = min($company_limit, $remaining); $export_rows[] = [ 'party_account_id' => $account_id, 'amount' => $part, 'remark' => $remark, ]; $remaining -= $part; } } else { $export_rows[] = [ 'party_account_id' => $account_id, 'amount' => $amount, 'remark' => $remark, ]; } return $export_rows; } // Render Hidden Form for Single Excel Download Auto-Submission function render_export_form(array $all_export_rows): void { $export_data = [ 'rows' => $all_export_rows, 'fmt' => 'xlsx', ]; ?> <form id="autoExport" method="post" action="party_payment_export.php"> <input type="hidden" name="export_json" value='<?= h(json_encode($export_data)) ?>'> </form> <script> document.getElementById('autoExport').submit(); </script> <?php exit; } /* ===================== COMPANY NEFT LIMIT ===================== */ $limit_st = $pdo->prepare(" SELECT neft_limit FROM company_bank_accounts WHERE company_id = ? LIMIT 1 "); $limit_st->execute([$company_id]); $company_limit = (float)($limit_st->fetchColumn() ?: 0); /* ===================== AJAX: Load Accounts for Selected Party ===================== */ if (isset($_GET['ajax']) && $_GET['ajax'] === 'party_accounts') { $party = trim($_GET['party_name'] ?? ''); $st = $pdo->prepare(" SELECT id, bank_name, account_no, beneficiary_name, ifsc_code FROM party_ac_detail WHERE company_id = ? AND party_name = ? AND COALESCE(is_active,1)=1 "); $st->execute([$company_id, $party]); header('Content-Type: application/json'); echo json_encode($st->fetchAll(PDO::FETCH_ASSOC)); exit; } /* ===================== MULTIPLE SAVE LOGIC ===================== */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $payments = $_POST['payments'] ?? []; $pay_date = date('Y-m-d'); if (empty($payments)) { $error = "Please add at least one payment row."; } else { $all_export_rows = []; $valid_entries = []; $has_validation_error = false; // Step 1: Server Side Validation foreach ($payments as $index => $row) { $account_id = (int)($row['party_account_id'] ?? 0); $amount = (float)($row['amount'] ?? 0); $remark = trim($row['remark'] ?? ''); if ($account_id <= 0 || $amount <= 0) { $error = "Row " . ($index + 1) . ": Please select a valid Party Account and enter an Amount greater than zero."; $has_validation_error = true; break; } // Verify Account Validity $st = $pdo->prepare(" SELECT id FROM party_ac_detail WHERE id = ? AND company_id = ? AND COALESCE(is_active,1)=1 LIMIT 1 "); $st->execute([$account_id, $company_id]); if (!$st->fetchColumn()) { $error = "Row " . ($index + 1) . ": Invalid or inactive party account selected."; $has_validation_error = true; break; } $valid_entries[] = [ 'account_id' => $account_id, 'amount' => $amount, 'remark' => $remark ]; } // Step 2: Database Transaction Execution if (!$has_validation_error) { try { $pdo->beginTransaction(); $ins = $pdo->prepare(" INSERT INTO payments (company_id, person_type, person_id, payment_type, amount, payment_date, payment_mode, remark, created_by, created_at) VALUES (?, 'party', ?, 'party_payment', ?, ?, 'BANK', ?, ?, NOW()) "); foreach ($valid_entries as $entry) { $ins->execute([ $company_id, $entry['account_id'], $entry['amount'], $pay_date, $entry['remark'], $user_id ]); $insert_id = (int)$pdo->lastInsertId(); activity_create('payment', 'party_payment', $insert_id, 'Party payment created via bulk form'); // Generate spreadsheet rows and merge into single master array $rows = build_export_rows($company_limit, $entry['account_id'], $entry['amount'], $entry['remark']); $all_export_rows = array_merge($all_export_rows, $rows); } $pdo->commit(); // Trigger single combined download activity_create('report', 'excel_export', 0, 'Bulk Export generated'); render_export_form($all_export_rows); } catch (Exception $e) { $pdo->rollBack(); $error = "Database error: " . $e->getMessage(); } } } } require_once __DIR__ . '/partials/header.php'; ?> <div class="container-fluid py-4"> <div class="row justify-content-center"> <div class="col-lg-12"> <div class="d-flex align-items-center justify-content-between mb-4"> <h4 class="mb-0 fw-bold text-primary"> <i class="bi bi-cash-stack me-2"></i>Multi Party Bank Payment </h4> <?php if($company_limit > 0): ?> <div class="badge bg-light text-dark border p-2"> Company NEFT Limit: <strong class="text-danger">₹<?= number_format($company_limit, 2) ?></strong> </div> <?php endif; ?> </div> <?php if ($error): ?> <div class="alert alert-danger shadow-sm"><?= h($error) ?></div> <?php endif; ?> <div class="card border-0 shadow-sm"> <div class="card-body p-4"> <form method="post" id="bulkPaymentForm"> <div class="table-responsive"> <table class="table table-bordered align-middle" id="paymentTable"> <thead class="table-light text-secondary small fw-bold"> <tr> <th style="width: 5%;">SR.</th> <th style="width: 25%;">SELECT PARTY *</th> <th style="width: 25%;">BANK ACCOUNT *</th> <th style="width: 15%;">AMOUNT *</th> <th style="width: 25%;">REMARK</th> <th style="width: 5%;" class="text-center">ACTION</th> </tr> </thead> <tbody id="paymentTableBody"> </tbody> </table> </div> <div class="row mt-3"> <div class="col"> <button type="button" class="btn btn-outline-secondary btn-sm px-3" id="addRowBtn"> <i class="bi bi-plus-circle me-1"></i> Add More Row </button> </div> <div class="col text-end"> <div class="h5 text-primary mb-3"> Total Amount: <strong id="grandTotalText">₹0.00</strong> </div> <button type="submit" class="btn btn-primary px-5 py-2"> <i class="bi bi-file-earmark-excel me-2"></i>Save & Download Bulk Excel </button> </div> </div> </form> </div> </div> </div> </div> </div> <?php // Pre-fetch parties list to populate dropdowns via JS safely $parties_query = $pdo->prepare(" SELECT DISTINCT party_name FROM party_ac_detail WHERE company_id = ? AND COALESCE(is_active,1)=1 ORDER BY party_name "); $parties_query->execute([$company_id]); $parties_list = $parties_query->fetchAll(PDO::FETCH_COLUMN); ?> <script> // Expose the global party list array to JS const availableParties = <?= json_encode($parties_list) ?>; let rowIndex = 0; // Add a generic blank row to the multi-payment grid function addPaymentRow() { const tbody = document.getElementById('paymentTableBody'); const tr = document.createElement('tr'); tr.id = `row_${rowIndex}`; let partyOptions = '<option value="">-- Select Party --</option>'; availableParties.forEach(p => { partyOptions += `<option value="${encodeURIComponent(p)}">${p}</option>`; }); tr.innerHTML = ` <td class="text-center fw-bold row-count"></td> <td> <select name="payments[${rowIndex}][party_name]" class="form-select party-select" required onchange="handlePartyChange(this, ${rowIndex})"> ${partyOptions} </select> </td> <td> <select name="payments[${rowIndex}][party_account_id]" id="account_select_${rowIndex}" class="form-select account-select" required onchange="handleAccountChange(this, ${rowIndex})"> <option value="">-- First Select Party --</option> </select> <div id="info_box_${rowIndex}" class="small text-muted mt-1 d-none"> <span class="badge bg-info text-dark py-1">Ben: <span id="ben_${rowIndex}"></span> | IFSC: <span id="ifsc_${rowIndex}"></span></span> </div> </td> <td> <div class="input-group"> <span class="input-group-text">₹</span> <input type="number" step="0.01" name="payments[${rowIndex}][amount]" class="form-control amount-input" required oninput="calculateGrandTotal()"> </div> </td> <td> <input type="text" name="payments[${rowIndex}][remark]" class="form-control"> </td> <td class="text-center"> <button type="button" class="btn btn-outline-danger btn-sm" onclick="removePaymentRow(${rowIndex})"> <i class="bi bi-trash"></i> </button> </td> `; tbody.appendChild(tr); rowIndex++; updateRowCounters(); } // Remove row and update calculation function removePaymentRow(idx) { const row = document.getElementById(`row_${idx}`); if (row) { row.remove(); updateRowCounters(); calculateGrandTotal(); } } // Keep serial number indexing consistent function updateRowCounters() { const counts = document.querySelectorAll('.row-count'); counts.forEach((td, i) => { td.innerText = i + 1; }); // Add row automatically if everything is deleted if(counts.length === 0) { addPaymentRow(); } } // Event handler: Trigger account load on selecting party function handlePartyChange(selectElement, index) { const partyName = selectElement.value; const accountSelect = document.getElementById(`account_select_${index}`); const infoBox = document.getElementById(`info_box_${index}`); if (!partyName) { accountSelect.innerHTML = '<option value="">-- First Select Party --</option>'; infoBox.classList.add('d-none'); return; } fetch(`multy_party_payment.php?ajax=party_accounts&party_name=${partyName}`) .then(r => r.json()) .then(list => { let opt = '<option value="">Select Account</option>'; list.forEach(a => { opt += `<option value="${a.id}" data-ben="${a.beneficiary_name}" data-ifsc="${a.ifsc_code}"> ${a.bank_name} - ${a.account_no} </option>`; }); accountSelect.innerHTML = opt; infoBox.classList.add('d-none'); }); } // Event handler: Render extra account specs on DOM function handleAccountChange(selectElement, index) { const selected = selectElement.options[selectElement.selectedIndex]; const infoBox = document.getElementById(`info_box_${index}`); if (selectElement.value) { document.getElementById(`ben_${index}`).innerText = selected.getAttribute('data-ben'); document.getElementById(`ifsc_${index}`).innerText = selected.getAttribute('data-ifsc'); infoBox.classList.remove('d-none'); } else { infoBox.classList.add('d-none'); } } // Form aggregation calculator function calculateGrandTotal() { let total = 0; const inputs = document.querySelectorAll('.amount-input'); inputs.forEach(input => { total += parseFloat(input.value || 0); }); document.getElementById('grandTotalText').innerText = '₹' + total.toFixed(2); } // Initialize interface with a starter row document.addEventListener('DOMContentLoaded', function() { document.getElementById('addRowBtn').addEventListener('click', addPaymentRow); addPaymentRow(); }); </script> <?php require_once __DIR__ . '/partials/footer.php'; ?>