Spaces:
Running
Running
Delete easypay
Browse files- easypay/DUPLICATE_PAYMENT_ENHANCEMENT.md +0 -115
- easypay/README.md +0 -204
- easypay/ajax_handlers.php +0 -122
- easypay/db_config.php +0 -27
- easypay/index.php +0 -789
- easypay/process_payment.php +0 -720
easypay/DUPLICATE_PAYMENT_ENHANCEMENT.md
DELETED
|
@@ -1,115 +0,0 @@
|
|
| 1 |
-
# Payment Processing Enhancement - Duplicate Payment Handling
|
| 2 |
-
|
| 3 |
-
## Overview
|
| 4 |
-
Enhanced the payment processing system to intelligently handle duplicate payments on the same date for the same student.
|
| 5 |
-
|
| 6 |
-
## Changes Made
|
| 7 |
-
|
| 8 |
-
### 1. **process_payment.php - STEP 1 Enhancement** (Lines 92-147)
|
| 9 |
-
|
| 10 |
-
#### Previous Behavior:
|
| 11 |
-
- Simply threw an exception if any payment existed for the student on the same date
|
| 12 |
-
- No differentiation between same-term and different-term payments
|
| 13 |
-
|
| 14 |
-
#### New Behavior:
|
| 15 |
-
- **Checks for existing payment** and retrieves full details (id, session, term, total_paid)
|
| 16 |
-
- **Different Term Detection**: If existing payment is for a different term/session, throws exception:
|
| 17 |
-
```
|
| 18 |
-
"A payment for this student has already been registered on this date in a different term (Session: X, Term: Y)"
|
| 19 |
-
```
|
| 20 |
-
- **Same Term Handling**: If existing payment is for the same term:
|
| 21 |
-
- Returns JSON response with `status: 'confirmation_required'`
|
| 22 |
-
- Includes detailed message showing existing payment amount and new amount to be added
|
| 23 |
-
- Waits for user confirmation via `confirm_update` POST parameter
|
| 24 |
-
- If user confirms (`confirm_update === 'yes'`), sets `$isUpdateMode = true` and stores existing record details
|
| 25 |
-
|
| 26 |
-
### 2. **process_payment.php - STEP 3b Enhancement** (Lines 202-244)
|
| 27 |
-
|
| 28 |
-
#### Previous Behavior:
|
| 29 |
-
- Always inserted a new record into `tb_account_school_fee_sum_payments`
|
| 30 |
-
|
| 31 |
-
#### New Behavior:
|
| 32 |
-
- **Update Mode** (when `$isUpdateMode === true`):
|
| 33 |
-
```sql
|
| 34 |
-
UPDATE tb_account_school_fee_sum_payments
|
| 35 |
-
SET total_paid = total_paid + :additional_amount,
|
| 36 |
-
registered_on = NOW()
|
| 37 |
-
WHERE id = :existing_record_id
|
| 38 |
-
```
|
| 39 |
-
- Adds new payment amount to existing total
|
| 40 |
-
- Updates `registered_on` timestamp to current time
|
| 41 |
-
|
| 42 |
-
- **Insert Mode** (when `$isUpdateMode === false`):
|
| 43 |
-
- Proceeds with normal INSERT operation as before
|
| 44 |
-
|
| 45 |
-
### 3. **index.php - Form Submission Handler** (Lines 708-786)
|
| 46 |
-
|
| 47 |
-
#### New AJAX-Based Submission:
|
| 48 |
-
- Intercepts form submission to handle confirmation flow
|
| 49 |
-
- Detects JSON responses with `status: 'confirmation_required'`
|
| 50 |
-
- Shows browser confirmation dialog with payment details
|
| 51 |
-
- **If User Confirms**:
|
| 52 |
-
- Appends `confirm_update=yes` to form data
|
| 53 |
-
- Resubmits request to process_payment.php
|
| 54 |
-
- Displays final success/error page
|
| 55 |
-
- **If User Cancels**:
|
| 56 |
-
- Shows "Payment cancelled by user" message
|
| 57 |
-
- Re-enables form for new attempt
|
| 58 |
-
|
| 59 |
-
## User Flow
|
| 60 |
-
|
| 61 |
-
### Scenario 1: Different Term Payment Exists
|
| 62 |
-
1. User attempts to process payment for Student X on Date Y
|
| 63 |
-
2. System finds existing payment for different term
|
| 64 |
-
3. **Exception thrown immediately**: "A payment for this student has already been registered on this date in a different term (Session: 2024, Term: 2)"
|
| 65 |
-
4. Payment process stops
|
| 66 |
-
|
| 67 |
-
### Scenario 2: Same Term Payment Exists
|
| 68 |
-
1. User attempts to process payment for Student X on Date Y
|
| 69 |
-
2. System finds existing payment for same term/session
|
| 70 |
-
3. **Popup appears**: "A payment for this student already exists on 2026-01-06 for Session 2024, Term 1. Current total: ₦50,000.00. Do you want to update this record by adding ₦25,000.00 to the existing amount?"
|
| 71 |
-
4. **User clicks OK**:
|
| 72 |
-
- System updates existing record
|
| 73 |
-
- `total_paid` becomes ₦75,000.00
|
| 74 |
-
- `registered_on` updated to current timestamp
|
| 75 |
-
- All other STEP 3 operations (3a, 3c, 3d, 3e) proceed normally
|
| 76 |
-
5. **User clicks Cancel**:
|
| 77 |
-
- Alert: "Payment cancelled by user"
|
| 78 |
-
- No database changes
|
| 79 |
-
- Form remains open for retry
|
| 80 |
-
|
| 81 |
-
### Scenario 3: No Existing Payment
|
| 82 |
-
1. User processes payment normally
|
| 83 |
-
2. No duplicate detected
|
| 84 |
-
3. System proceeds with standard INSERT operation
|
| 85 |
-
4. Success page displayed
|
| 86 |
-
|
| 87 |
-
## Database Impact
|
| 88 |
-
|
| 89 |
-
### Tables Modified:
|
| 90 |
-
- **tb_account_school_fee_sum_payments**:
|
| 91 |
-
- UPDATE operation added for duplicate same-term scenarios
|
| 92 |
-
- `total_paid` incremented
|
| 93 |
-
- `registered_on` timestamp updated
|
| 94 |
-
|
| 95 |
-
### Tables Unaffected by Update Mode:
|
| 96 |
-
- **tb_account_school_fee_payments**: Always receives new records (3a)
|
| 97 |
-
- **tb_account_student_payments**: Always receives new records (3c)
|
| 98 |
-
- **tb_account_payment_registers**: Always receives new records (3d)
|
| 99 |
-
- **tb_student_logistics**: Always updated with new payment amounts (3e)
|
| 100 |
-
|
| 101 |
-
## Technical Notes
|
| 102 |
-
|
| 103 |
-
1. **Transaction Safety**: All operations remain within the existing database transaction, ensuring atomicity
|
| 104 |
-
2. **Backward Compatibility**: Normal payment flows (no duplicates) work exactly as before
|
| 105 |
-
3. **Error Handling**: Maintains existing exception handling and rollback mechanisms
|
| 106 |
-
4. **User Experience**: Confirmation dialog provides clear information about existing payment and proposed update
|
| 107 |
-
|
| 108 |
-
## Testing Recommendations
|
| 109 |
-
|
| 110 |
-
1. Test different-term duplicate detection
|
| 111 |
-
2. Test same-term duplicate with user confirmation
|
| 112 |
-
3. Test same-term duplicate with user cancellation
|
| 113 |
-
4. Verify database updates are correct when user confirms
|
| 114 |
-
5. Verify no database changes when user cancels
|
| 115 |
-
6. Test normal payment flow (no duplicates) still works
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
easypay/README.md
DELETED
|
@@ -1,204 +0,0 @@
|
|
| 1 |
-
# Student Fee Payment Registration System
|
| 2 |
-
|
| 3 |
-
A streamlined 2-page PHP application that simplifies the school fee payment process by automatically allocating payments across outstanding fees from oldest to newest.
|
| 4 |
-
|
| 5 |
-
## Features
|
| 6 |
-
|
| 7 |
-
- **AJAX-powered student search** - Find students quickly by name or student code
|
| 8 |
-
- **Outstanding fees display** - View all unpaid fees sorted from oldest to newest
|
| 9 |
-
- **Automatic payment allocation** - Payments are automatically distributed across selected fees in chronological order
|
| 10 |
-
- **Teller validation** - Automatic lookup of bank statements with unreconciled amount calculation
|
| 11 |
-
- **Duplicate prevention** - Prevents multiple payments for the same student on the same date
|
| 12 |
-
- **Transactional integrity** - All database operations wrapped in transactions with automatic rollback on error
|
| 13 |
-
- **Comprehensive receipts** - Detailed payment confirmation with all settled fees
|
| 14 |
-
|
| 15 |
-
## Database Tables Used
|
| 16 |
-
|
| 17 |
-
The application interacts with the following tables in the `one_arps_aci` database:
|
| 18 |
-
|
| 19 |
-
- `tb_student_registrations` - Student information
|
| 20 |
-
- `tb_academic_levels` - Academic level details
|
| 21 |
-
- `tb_account_school_fees` - Fee definitions
|
| 22 |
-
- `tb_account_receivables` - Billed fees (invoices)
|
| 23 |
-
- `tb_account_bank_statements` - Bank transaction data
|
| 24 |
-
- `tb_account_school_fee_payments` - Individual fee payment records
|
| 25 |
-
- `tb_account_school_fee_sum_payments` - Aggregated payment records
|
| 26 |
-
- `tb_account_student_payments` - Cumulative payment tracking
|
| 27 |
-
- `tb_account_payment_registers` - Receipt records
|
| 28 |
-
- `tb_student_logistics` - Student outstanding balance tracking
|
| 29 |
-
|
| 30 |
-
## Installation
|
| 31 |
-
|
| 32 |
-
### Prerequisites
|
| 33 |
-
|
| 34 |
-
- PHP 7.4 or higher
|
| 35 |
-
- MySQL 5.7 or higher
|
| 36 |
-
- Web server (Apache, Nginx, or PHP built-in server)
|
| 37 |
-
- Existing `one_arps_aci` database with required tables
|
| 38 |
-
|
| 39 |
-
### Setup Steps
|
| 40 |
-
|
| 41 |
-
1. **Copy files to your web directory**
|
| 42 |
-
```
|
| 43 |
-
c:\ESD\ARPS EasyPayments\
|
| 44 |
-
├── db_config.php
|
| 45 |
-
├── index.php
|
| 46 |
-
├── process_payment.php
|
| 47 |
-
└── ajax_handlers.php
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
2. **Configure database connection**
|
| 51 |
-
|
| 52 |
-
Edit `db_config.php` and update the following constants:
|
| 53 |
-
```php
|
| 54 |
-
define('DB_HOST', 'localhost'); // Your MySQL host
|
| 55 |
-
define('DB_USER', 'root'); // Your MySQL username
|
| 56 |
-
define('DB_PASS', ''); // Your MySQL password
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
3. **Set proper permissions**
|
| 60 |
-
|
| 61 |
-
Ensure the web server has read access to all PHP files.
|
| 62 |
-
|
| 63 |
-
4. **Access the application**
|
| 64 |
-
|
| 65 |
-
Navigate to `http://localhost/ARPS%20EasyPayments/index.php` in your browser.
|
| 66 |
-
|
| 67 |
-
## Usage Guide
|
| 68 |
-
|
| 69 |
-
### Step 1: Search for a Student
|
| 70 |
-
|
| 71 |
-
1. Type the student's name or student code in the search box
|
| 72 |
-
2. Select the student from the dropdown results
|
| 73 |
-
3. The page will reload showing student details and outstanding fees
|
| 74 |
-
|
| 75 |
-
### Step 2: Review Outstanding Fees
|
| 76 |
-
|
| 77 |
-
- All outstanding fees are displayed in a table, sorted from oldest to newest
|
| 78 |
-
- Fees are pre-checked by default
|
| 79 |
-
- You can uncheck any fees you don't want to settle
|
| 80 |
-
- The table shows:
|
| 81 |
-
- Fee description
|
| 82 |
-
- Academic session and term
|
| 83 |
-
- Billed amount
|
| 84 |
-
- Amount already paid
|
| 85 |
-
- Outstanding balance
|
| 86 |
-
|
| 87 |
-
### Step 3: Process Payment
|
| 88 |
-
|
| 89 |
-
1. Click the **"Process Payment"** button
|
| 90 |
-
2. Enter the **Teller Number** from the bank statement
|
| 91 |
-
3. The system will automatically:
|
| 92 |
-
- Look up the bank statement
|
| 93 |
-
- Fill in the bank narration
|
| 94 |
-
- Calculate the unreconciled amount on the teller
|
| 95 |
-
4. Enter the **Amount to Use for Fees** (must not exceed unreconciled amount)
|
| 96 |
-
5. Click **"OK PROCEED!"**
|
| 97 |
-
|
| 98 |
-
### Step 4: View Confirmation
|
| 99 |
-
|
| 100 |
-
- On success, you'll see a detailed receipt showing:
|
| 101 |
-
- Student name and receipt number
|
| 102 |
-
- Payment date and teller information
|
| 103 |
-
- All fees that were settled
|
| 104 |
-
- Remaining unreconciled amount on the teller
|
| 105 |
-
- On failure, you'll see an error message (no database changes are made)
|
| 106 |
-
|
| 107 |
-
## Payment Allocation Logic
|
| 108 |
-
|
| 109 |
-
The system automatically allocates payments using the following rules:
|
| 110 |
-
|
| 111 |
-
1. **Oldest First** - Fees are sorted by academic session (ASC), then term (ASC)
|
| 112 |
-
2. **Full Settlement Priority** - Each fee is fully settled before moving to the next
|
| 113 |
-
3. **Partial Settlement** - If the payment amount runs out, the last fee is partially settled
|
| 114 |
-
4. **Automatic Calculation** - No manual allocation required
|
| 115 |
-
|
| 116 |
-
### Example
|
| 117 |
-
|
| 118 |
-
If a student has these outstanding fees:
|
| 119 |
-
- 2024 Term 1: ₦10,000
|
| 120 |
-
- 2024 Term 2: ₦15,000
|
| 121 |
-
- 2024 Term 3: ₦12,000
|
| 122 |
-
|
| 123 |
-
And you process a payment of ₦30,000:
|
| 124 |
-
- 2024 Term 1: Fully settled (₦10,000)
|
| 125 |
-
- 2024 Term 2: Fully settled (₦15,000)
|
| 126 |
-
- 2024 Term 3: Partially settled (₦5,000)
|
| 127 |
-
|
| 128 |
-
## ID Generation Rules
|
| 129 |
-
|
| 130 |
-
The application follows strict ID generation rules as per the database schema:
|
| 131 |
-
|
| 132 |
-
- **transaction_id**: `student_id + academic_session + payment_date`
|
| 133 |
-
- **school_fee_payment_id**: `student_code + fee_id + payment_date`
|
| 134 |
-
- **receipt_no**: `student_code + payment_date`
|
| 135 |
-
- **Date format**: Always `YYYY-MM-DD`
|
| 136 |
-
|
| 137 |
-
## Error Handling
|
| 138 |
-
|
| 139 |
-
The application includes comprehensive error handling:
|
| 140 |
-
|
| 141 |
-
- **Database connection failures** - Clear error message displayed
|
| 142 |
-
- **Invalid teller numbers** - Validation before processing
|
| 143 |
-
- **Duplicate payments** - Prevented at database level
|
| 144 |
-
- **Insufficient unreconciled amount** - Client and server-side validation
|
| 145 |
-
- **Transaction failures** - Automatic rollback with error details
|
| 146 |
-
|
| 147 |
-
## Security Features
|
| 148 |
-
|
| 149 |
-
- **PDO with prepared statements** - Protection against SQL injection
|
| 150 |
-
- **Input validation** - Server-side validation of all inputs
|
| 151 |
-
- **Output sanitization** - All user data is escaped before display
|
| 152 |
-
- **Transaction integrity** - All-or-nothing database operations
|
| 153 |
-
|
| 154 |
-
## Browser Compatibility
|
| 155 |
-
|
| 156 |
-
- Chrome (recommended)
|
| 157 |
-
- Firefox
|
| 158 |
-
- Edge
|
| 159 |
-
- Safari
|
| 160 |
-
|
| 161 |
-
## Troubleshooting
|
| 162 |
-
|
| 163 |
-
### "Database connection failed"
|
| 164 |
-
- Check your database credentials in `db_config.php`
|
| 165 |
-
- Ensure MySQL server is running
|
| 166 |
-
- Verify the database name is `one_arps_aci`
|
| 167 |
-
|
| 168 |
-
### "Teller number not found"
|
| 169 |
-
- Verify the teller number exists in `tb_account_bank_statements`
|
| 170 |
-
- Check that the teller number is the last token in the description field
|
| 171 |
-
- Ensure there's unreconciled amount available
|
| 172 |
-
|
| 173 |
-
### "A payment for this student has already been registered on this date"
|
| 174 |
-
- This is a duplicate prevention feature
|
| 175 |
-
- You cannot process multiple payments for the same student on the same date
|
| 176 |
-
- Use a different payment date or check existing records
|
| 177 |
-
|
| 178 |
-
## Technical Notes
|
| 179 |
-
|
| 180 |
-
### Teller Number Extraction
|
| 181 |
-
|
| 182 |
-
The system extracts teller information from `tb_account_bank_statements.description` using this logic:
|
| 183 |
-
- **Teller Number**: Last token (rightmost word) after splitting by spaces
|
| 184 |
-
- **Teller Name**: All text before the last space
|
| 185 |
-
|
| 186 |
-
Example: `"SCHOOL FEES PAYMENT 1234567890"`
|
| 187 |
-
- Teller Number: `1234567890`
|
| 188 |
-
- Teller Name: `SCHOOL FEES PAYMENT`
|
| 189 |
-
|
| 190 |
-
### Unreconciled Amount Calculation
|
| 191 |
-
|
| 192 |
-
```sql
|
| 193 |
-
unreconciled_amount = bank_statement.amount_paid - SUM(school_fee_payments.amount_paid)
|
| 194 |
-
```
|
| 195 |
-
|
| 196 |
-
This shows how much of the bank deposit has not yet been allocated to student fees.
|
| 197 |
-
|
| 198 |
-
## Support
|
| 199 |
-
|
| 200 |
-
For issues or questions, please contact the system administrator.
|
| 201 |
-
|
| 202 |
-
## License
|
| 203 |
-
|
| 204 |
-
Internal use only - ARPS School Management System
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
easypay/ajax_handlers.php
DELETED
|
@@ -1,122 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
/**
|
| 3 |
-
* AJAX Handlers
|
| 4 |
-
* Endpoints for student search and teller lookup
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
require_once 'db_config.php';
|
| 8 |
-
|
| 9 |
-
header('Content-Type: application/json');
|
| 10 |
-
|
| 11 |
-
$action = $_GET['action'] ?? '';
|
| 12 |
-
|
| 13 |
-
try {
|
| 14 |
-
switch ($action) {
|
| 15 |
-
case 'search_students':
|
| 16 |
-
searchStudents($pdo);
|
| 17 |
-
break;
|
| 18 |
-
|
| 19 |
-
case 'lookup_teller':
|
| 20 |
-
lookupTeller($pdo);
|
| 21 |
-
break;
|
| 22 |
-
|
| 23 |
-
default:
|
| 24 |
-
echo json_encode(['error' => 'Invalid action']);
|
| 25 |
-
}
|
| 26 |
-
} catch (Exception $e) {
|
| 27 |
-
echo json_encode(['error' => $e->getMessage()]);
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
/**
|
| 31 |
-
* Search for students by name or student code
|
| 32 |
-
*/
|
| 33 |
-
function searchStudents($pdo)
|
| 34 |
-
{
|
| 35 |
-
$search = $_GET['search'] ?? '';
|
| 36 |
-
|
| 37 |
-
if (strlen($search) < 2) {
|
| 38 |
-
echo json_encode([]);
|
| 39 |
-
return;
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
$sql = "SELECT
|
| 43 |
-
id,
|
| 44 |
-
student_code,
|
| 45 |
-
CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) AS full_name
|
| 46 |
-
FROM tb_student_registrations
|
| 47 |
-
WHERE
|
| 48 |
-
admission_status = 'Active' -- New condition added here
|
| 49 |
-
AND (
|
| 50 |
-
student_code LIKE :search1
|
| 51 |
-
OR last_name LIKE :search2
|
| 52 |
-
OR first_name LIKE :search3
|
| 53 |
-
OR other_name LIKE :search4
|
| 54 |
-
OR CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) LIKE :search5
|
| 55 |
-
)
|
| 56 |
-
ORDER BY last_name, first_name
|
| 57 |
-
LIMIT 20";
|
| 58 |
-
|
| 59 |
-
$stmt = $pdo->prepare($sql);
|
| 60 |
-
$searchParam = '%' . $search . '%';
|
| 61 |
-
$stmt->execute([
|
| 62 |
-
'search1' => $searchParam,
|
| 63 |
-
'search2' => $searchParam,
|
| 64 |
-
'search3' => $searchParam,
|
| 65 |
-
'search4' => $searchParam,
|
| 66 |
-
'search5' => $searchParam
|
| 67 |
-
]);
|
| 68 |
-
|
| 69 |
-
$results = $stmt->fetchAll();
|
| 70 |
-
echo json_encode($results);
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
/**
|
| 74 |
-
* Lookup bank statement by teller number
|
| 75 |
-
*/
|
| 76 |
-
function lookupTeller($pdo)
|
| 77 |
-
{
|
| 78 |
-
$tellerNumber = $_GET['teller_number'] ?? '';
|
| 79 |
-
|
| 80 |
-
if (empty($tellerNumber)) {
|
| 81 |
-
echo json_encode(['error' => 'Teller number is required']);
|
| 82 |
-
return;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
// Query to find bank statement and calculate unreconciled amount
|
| 86 |
-
$sql = "SELECT
|
| 87 |
-
bs.id,
|
| 88 |
-
bs.description,
|
| 89 |
-
bs.amount_paid,
|
| 90 |
-
bs.payment_date,
|
| 91 |
-
COALESCE(fp.total_registered_fee, 0.00) AS registered_amount,
|
| 92 |
-
(bs.amount_paid - COALESCE(fp.total_registered_fee, 0.00)) AS unreconciled_amount
|
| 93 |
-
FROM tb_account_bank_statements bs
|
| 94 |
-
LEFT JOIN (
|
| 95 |
-
SELECT teller_no, SUM(amount_paid) AS total_registered_fee
|
| 96 |
-
FROM tb_account_school_fee_payments
|
| 97 |
-
GROUP BY teller_no
|
| 98 |
-
) fp ON SUBSTRING_INDEX(bs.description, ' ', -1) = fp.teller_no
|
| 99 |
-
WHERE SUBSTRING_INDEX(bs.description, ' ', -1) = :teller_number
|
| 100 |
-
HAVING unreconciled_amount >= 0
|
| 101 |
-
LIMIT 1";
|
| 102 |
-
|
| 103 |
-
$stmt = $pdo->prepare($sql);
|
| 104 |
-
$stmt->execute(['teller_number' => $tellerNumber]);
|
| 105 |
-
|
| 106 |
-
$result = $stmt->fetch();
|
| 107 |
-
|
| 108 |
-
if (!$result) {
|
| 109 |
-
echo json_encode(['error' => 'Teller number not found or fully reconciled']);
|
| 110 |
-
return;
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
// Extract teller name (description without last token)
|
| 114 |
-
$descParts = explode(' ', $result['description']);
|
| 115 |
-
array_pop($descParts); // Remove teller number
|
| 116 |
-
$tellerName = implode(' ', $descParts);
|
| 117 |
-
|
| 118 |
-
$result['teller_name'] = $tellerName;
|
| 119 |
-
$result['teller_no'] = $tellerNumber;
|
| 120 |
-
|
| 121 |
-
echo json_encode($result);
|
| 122 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
easypay/db_config.php
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
/**
|
| 3 |
-
* Database Configuration
|
| 4 |
-
* PDO connection for one_arps_aci database
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
// Database credentials
|
| 8 |
-
define('DB_HOST', 'o2.service.oyster.cloud76.cc:3306');
|
| 9 |
-
define('DB_NAME', 'one_arps_aci');
|
| 10 |
-
define('DB_USER', 'cs_admin');
|
| 11 |
-
define('DB_PASS', 'D55-system-2-beasts-jungle-sky-birth');
|
| 12 |
-
define('DB_CHARSET', 'utf8mb4');
|
| 13 |
-
|
| 14 |
-
// PDO options
|
| 15 |
-
$options = [
|
| 16 |
-
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
| 17 |
-
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
| 18 |
-
PDO::ATTR_EMULATE_PREPARES => false,
|
| 19 |
-
];
|
| 20 |
-
|
| 21 |
-
// Create PDO instance
|
| 22 |
-
try {
|
| 23 |
-
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
| 24 |
-
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
|
| 25 |
-
} catch (PDOException $e) {
|
| 26 |
-
die("Database connection failed: " . $e->getMessage());
|
| 27 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
easypay/index.php
DELETED
|
@@ -1,789 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
/**
|
| 3 |
-
* Student Fee Payment Registration - Main Page
|
| 4 |
-
* Search for students and display outstanding fees
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
require_once 'db_config.php';
|
| 8 |
-
|
| 9 |
-
$studentData = null;
|
| 10 |
-
$outstandingFees = [];
|
| 11 |
-
$studentId = $_GET['student_id'] ?? '';
|
| 12 |
-
|
| 13 |
-
// If student is selected, fetch their data and outstanding fees
|
| 14 |
-
if (!empty($studentId)) {
|
| 15 |
-
try {
|
| 16 |
-
// Fetch student details
|
| 17 |
-
$sql = "SELECT
|
| 18 |
-
sr.id,
|
| 19 |
-
sr.student_code,
|
| 20 |
-
CONCAT(sr.last_name, ' ', sr.first_name, ' ', COALESCE(sr.other_name, '')) AS full_name,
|
| 21 |
-
al.level_name
|
| 22 |
-
FROM tb_student_registrations sr
|
| 23 |
-
LEFT JOIN tb_academic_levels al ON al.id = sr.level_id
|
| 24 |
-
WHERE sr.id = :student_id";
|
| 25 |
-
|
| 26 |
-
$stmt = $pdo->prepare($sql);
|
| 27 |
-
$stmt->execute(['student_id' => $studentId]);
|
| 28 |
-
$studentData = $stmt->fetch();
|
| 29 |
-
|
| 30 |
-
if ($studentData) {
|
| 31 |
-
// Fetch outstanding fees per fee_id
|
| 32 |
-
$sql = "SELECT
|
| 33 |
-
ar.id AS receivable_id,
|
| 34 |
-
ar.fee_id,
|
| 35 |
-
asf.description AS fee_description,
|
| 36 |
-
ar.academic_session,
|
| 37 |
-
ar.term_of_session,
|
| 38 |
-
ar.actual_value AS billed_amount,
|
| 39 |
-
COALESCE(asp.total_paid_for_period, 0) AS total_paid, -- Renamed column for clarity
|
| 40 |
-
(ar.actual_value - COALESCE(asp.total_paid_for_period, 0)) AS outstanding_amount,
|
| 41 |
-
ar.created_on
|
| 42 |
-
FROM
|
| 43 |
-
tb_account_receivables ar
|
| 44 |
-
JOIN
|
| 45 |
-
tb_account_school_fees asf ON asf.id = ar.fee_id
|
| 46 |
-
LEFT JOIN (
|
| 47 |
-
-- Subquery now calculates total payments specific to a session, term, and fee
|
| 48 |
-
SELECT
|
| 49 |
-
fee_id,
|
| 50 |
-
student_id,
|
| 51 |
-
academic_session,
|
| 52 |
-
term_of_session,
|
| 53 |
-
SUM(payment_to_date) AS total_paid_for_period
|
| 54 |
-
FROM
|
| 55 |
-
tb_account_student_payments
|
| 56 |
-
GROUP BY
|
| 57 |
-
fee_id,
|
| 58 |
-
student_id,
|
| 59 |
-
academic_session,
|
| 60 |
-
term_of_session
|
| 61 |
-
) asp ON asp.fee_id = ar.fee_id
|
| 62 |
-
AND asp.student_id = ar.student_id
|
| 63 |
-
AND asp.academic_session = ar.academic_session
|
| 64 |
-
AND asp.term_of_session = ar.term_of_session
|
| 65 |
-
WHERE
|
| 66 |
-
ar.student_id = :student_id
|
| 67 |
-
AND ar.academic_session > 2023
|
| 68 |
-
-- Only show records where the calculated outstanding amount is greater than zero
|
| 69 |
-
AND (ar.actual_value - COALESCE(asp.total_paid_for_period, 0)) > 0
|
| 70 |
-
ORDER BY
|
| 71 |
-
ar.academic_session ASC, ar.term_of_session ASC, ar.created_on ASC";
|
| 72 |
-
|
| 73 |
-
$stmt = $pdo->prepare($sql);
|
| 74 |
-
$stmt->execute(['student_id' => $studentId]);
|
| 75 |
-
$outstandingFees = $stmt->fetchAll();
|
| 76 |
-
}
|
| 77 |
-
} catch (PDOException $e) {
|
| 78 |
-
$error = "Error fetching student data: " . $e->getMessage();
|
| 79 |
-
}
|
| 80 |
-
}
|
| 81 |
-
?>
|
| 82 |
-
<!DOCTYPE html>
|
| 83 |
-
<html lang="en">
|
| 84 |
-
|
| 85 |
-
<head>
|
| 86 |
-
<meta charset="UTF-8">
|
| 87 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 88 |
-
<title>Student Fee Payment Registration</title>
|
| 89 |
-
<style>
|
| 90 |
-
* {
|
| 91 |
-
margin: 0;
|
| 92 |
-
padding: 0;
|
| 93 |
-
box-sizing: border-box;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
body {
|
| 97 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 98 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 99 |
-
min-height: 100vh;
|
| 100 |
-
padding: 20px;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.container {
|
| 104 |
-
max-width: 1200px;
|
| 105 |
-
margin: 0 auto;
|
| 106 |
-
background: white;
|
| 107 |
-
border-radius: 12px;
|
| 108 |
-
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
| 109 |
-
padding: 30px;
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
h1 {
|
| 113 |
-
color: #333;
|
| 114 |
-
margin-bottom: 30px;
|
| 115 |
-
text-align: center;
|
| 116 |
-
font-size: 28px;
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
.search-section {
|
| 120 |
-
margin-bottom: 30px;
|
| 121 |
-
padding: 20px;
|
| 122 |
-
background: #f8f9fa;
|
| 123 |
-
border-radius: 8px;
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
.search-box {
|
| 127 |
-
position: relative;
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
.search-box label {
|
| 131 |
-
display: block;
|
| 132 |
-
margin-bottom: 8px;
|
| 133 |
-
font-weight: 600;
|
| 134 |
-
color: #555;
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
.search-box input {
|
| 138 |
-
width: 100%;
|
| 139 |
-
padding: 12px 15px;
|
| 140 |
-
border: 2px solid #ddd;
|
| 141 |
-
border-radius: 6px;
|
| 142 |
-
font-size: 16px;
|
| 143 |
-
transition: border-color 0.3s;
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
.search-box input:focus {
|
| 147 |
-
outline: none;
|
| 148 |
-
border-color: #667eea;
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
.search-results {
|
| 152 |
-
position: absolute;
|
| 153 |
-
top: 100%;
|
| 154 |
-
left: 0;
|
| 155 |
-
right: 0;
|
| 156 |
-
background: white;
|
| 157 |
-
border: 2px solid #667eea;
|
| 158 |
-
border-top: none;
|
| 159 |
-
border-radius: 0 0 6px 6px;
|
| 160 |
-
max-height: 300px;
|
| 161 |
-
overflow-y: auto;
|
| 162 |
-
display: none;
|
| 163 |
-
z-index: 1000;
|
| 164 |
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
.search-results.active {
|
| 168 |
-
display: block;
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
.search-result-item {
|
| 172 |
-
padding: 12px 15px;
|
| 173 |
-
cursor: pointer;
|
| 174 |
-
border-bottom: 1px solid #eee;
|
| 175 |
-
transition: background-color 0.2s;
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
.search-result-item:hover {
|
| 179 |
-
background-color: #f0f0f0;
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
.search-result-item:last-child {
|
| 183 |
-
border-bottom: none;
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
.student-code {
|
| 187 |
-
color: #667eea;
|
| 188 |
-
font-weight: 600;
|
| 189 |
-
margin-right: 10px;
|
| 190 |
-
}
|
| 191 |
-
|
| 192 |
-
.student-details {
|
| 193 |
-
margin-bottom: 30px;
|
| 194 |
-
padding: 20px;
|
| 195 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 196 |
-
color: white;
|
| 197 |
-
border-radius: 8px;
|
| 198 |
-
}
|
| 199 |
-
|
| 200 |
-
.student-details h2 {
|
| 201 |
-
margin-bottom: 10px;
|
| 202 |
-
font-size: 24px;
|
| 203 |
-
}
|
| 204 |
-
|
| 205 |
-
.student-details p {
|
| 206 |
-
font-size: 16px;
|
| 207 |
-
margin-bottom: 5px;
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
-
.fees-section {
|
| 211 |
-
margin-bottom: 30px;
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
.fees-section h3 {
|
| 215 |
-
margin-bottom: 15px;
|
| 216 |
-
color: #333;
|
| 217 |
-
font-size: 20px;
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
table {
|
| 221 |
-
width: 100%;
|
| 222 |
-
border-collapse: collapse;
|
| 223 |
-
margin-bottom: 20px;
|
| 224 |
-
}
|
| 225 |
-
|
| 226 |
-
th,
|
| 227 |
-
td {
|
| 228 |
-
padding: 12px;
|
| 229 |
-
text-align: left;
|
| 230 |
-
border-bottom: 1px solid #ddd;
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
th {
|
| 234 |
-
background-color: #667eea;
|
| 235 |
-
color: white;
|
| 236 |
-
font-weight: 600;
|
| 237 |
-
}
|
| 238 |
-
|
| 239 |
-
tr:hover {
|
| 240 |
-
background-color: #f8f9fa;
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.amount {
|
| 244 |
-
text-align: right;
|
| 245 |
-
font-family: 'Courier New', monospace;
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
.btn {
|
| 249 |
-
padding: 12px 30px;
|
| 250 |
-
border: none;
|
| 251 |
-
border-radius: 6px;
|
| 252 |
-
font-size: 16px;
|
| 253 |
-
font-weight: 600;
|
| 254 |
-
cursor: pointer;
|
| 255 |
-
transition: all 0.3s;
|
| 256 |
-
}
|
| 257 |
-
|
| 258 |
-
.btn-primary {
|
| 259 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 260 |
-
color: white;
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
.btn-primary:hover {
|
| 264 |
-
transform: translateY(-2px);
|
| 265 |
-
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 266 |
-
}
|
| 267 |
-
|
| 268 |
-
.btn-primary:disabled {
|
| 269 |
-
background: #ccc;
|
| 270 |
-
cursor: not-allowed;
|
| 271 |
-
transform: none;
|
| 272 |
-
}
|
| 273 |
-
|
| 274 |
-
.modal {
|
| 275 |
-
display: none;
|
| 276 |
-
position: fixed;
|
| 277 |
-
top: 0;
|
| 278 |
-
left: 0;
|
| 279 |
-
width: 100%;
|
| 280 |
-
height: 100%;
|
| 281 |
-
background: rgba(0, 0, 0, 0.5);
|
| 282 |
-
z-index: 2000;
|
| 283 |
-
align-items: center;
|
| 284 |
-
justify-content: center;
|
| 285 |
-
}
|
| 286 |
-
|
| 287 |
-
.modal.active {
|
| 288 |
-
display: flex;
|
| 289 |
-
}
|
| 290 |
-
|
| 291 |
-
.modal-content {
|
| 292 |
-
background: white;
|
| 293 |
-
padding: 30px;
|
| 294 |
-
border-radius: 12px;
|
| 295 |
-
max-width: 600px;
|
| 296 |
-
width: 90%;
|
| 297 |
-
max-height: 90vh;
|
| 298 |
-
overflow-y: auto;
|
| 299 |
-
}
|
| 300 |
-
|
| 301 |
-
.modal-header {
|
| 302 |
-
margin-bottom: 20px;
|
| 303 |
-
}
|
| 304 |
-
|
| 305 |
-
.modal-header h2 {
|
| 306 |
-
color: #333;
|
| 307 |
-
font-size: 22px;
|
| 308 |
-
}
|
| 309 |
-
|
| 310 |
-
.form-group {
|
| 311 |
-
margin-bottom: 20px;
|
| 312 |
-
}
|
| 313 |
-
|
| 314 |
-
.form-group label {
|
| 315 |
-
display: block;
|
| 316 |
-
margin-bottom: 8px;
|
| 317 |
-
font-weight: 600;
|
| 318 |
-
color: #555;
|
| 319 |
-
}
|
| 320 |
-
|
| 321 |
-
.form-group input,
|
| 322 |
-
.form-group textarea {
|
| 323 |
-
width: 100%;
|
| 324 |
-
padding: 10px 12px;
|
| 325 |
-
border: 2px solid #ddd;
|
| 326 |
-
border-radius: 6px;
|
| 327 |
-
font-size: 14px;
|
| 328 |
-
font-family: inherit;
|
| 329 |
-
}
|
| 330 |
-
|
| 331 |
-
.form-group input:focus,
|
| 332 |
-
.form-group textarea:focus {
|
| 333 |
-
outline: none;
|
| 334 |
-
border-color: #667eea;
|
| 335 |
-
}
|
| 336 |
-
|
| 337 |
-
.form-group input[readonly] {
|
| 338 |
-
background-color: #f0f0f0;
|
| 339 |
-
cursor: not-allowed;
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
.modal-actions {
|
| 343 |
-
display: flex;
|
| 344 |
-
gap: 10px;
|
| 345 |
-
justify-content: flex-end;
|
| 346 |
-
margin-top: 25px;
|
| 347 |
-
}
|
| 348 |
-
|
| 349 |
-
.btn-secondary {
|
| 350 |
-
background: #6c757d;
|
| 351 |
-
color: white;
|
| 352 |
-
}
|
| 353 |
-
|
| 354 |
-
.btn-secondary:hover {
|
| 355 |
-
background: #5a6268;
|
| 356 |
-
}
|
| 357 |
-
|
| 358 |
-
.error {
|
| 359 |
-
color: #dc3545;
|
| 360 |
-
font-size: 14px;
|
| 361 |
-
margin-top: 5px;
|
| 362 |
-
}
|
| 363 |
-
|
| 364 |
-
.success {
|
| 365 |
-
color: #28a745;
|
| 366 |
-
font-size: 14px;
|
| 367 |
-
margin-top: 5px;
|
| 368 |
-
}
|
| 369 |
-
|
| 370 |
-
.alert {
|
| 371 |
-
padding: 15px;
|
| 372 |
-
border-radius: 6px;
|
| 373 |
-
margin-bottom: 20px;
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
.alert-error {
|
| 377 |
-
background-color: #f8d7da;
|
| 378 |
-
color: #721c24;
|
| 379 |
-
border: 1px solid #f5c6cb;
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
.total-row {
|
| 383 |
-
font-weight: bold;
|
| 384 |
-
background-color: #f0f0f0;
|
| 385 |
-
}
|
| 386 |
-
|
| 387 |
-
.loading {
|
| 388 |
-
display: inline-block;
|
| 389 |
-
width: 16px;
|
| 390 |
-
height: 16px;
|
| 391 |
-
border: 3px solid #f3f3f3;
|
| 392 |
-
border-top: 3px solid #667eea;
|
| 393 |
-
border-radius: 50%;
|
| 394 |
-
animation: spin 1s linear infinite;
|
| 395 |
-
margin-left: 10px;
|
| 396 |
-
}
|
| 397 |
-
|
| 398 |
-
@keyframes spin {
|
| 399 |
-
0% {
|
| 400 |
-
transform: rotate(0deg);
|
| 401 |
-
}
|
| 402 |
-
|
| 403 |
-
100% {
|
| 404 |
-
transform: rotate(360deg);
|
| 405 |
-
}
|
| 406 |
-
}
|
| 407 |
-
</style>
|
| 408 |
-
</head>
|
| 409 |
-
|
| 410 |
-
<body>
|
| 411 |
-
<div class="container">
|
| 412 |
-
<h1>Student Fee Payment Registration</h1>
|
| 413 |
-
|
| 414 |
-
<?php if (isset($error)): ?>
|
| 415 |
-
<div class="alert alert-error"><?php echo htmlspecialchars($error); ?></div>
|
| 416 |
-
<?php endif; ?>
|
| 417 |
-
|
| 418 |
-
<!-- Student Search Section -->
|
| 419 |
-
<div class="search-section">
|
| 420 |
-
<div class="search-box">
|
| 421 |
-
<label for="studentSearch">Search Student (by name or student code)</label>
|
| 422 |
-
<input type="text" id="studentSearch" placeholder="Type to search..." autocomplete="off">
|
| 423 |
-
<div class="search-results" id="searchResults"></div>
|
| 424 |
-
</div>
|
| 425 |
-
</div>
|
| 426 |
-
|
| 427 |
-
<?php if ($studentData): ?>
|
| 428 |
-
<!-- Student Details -->
|
| 429 |
-
<div class="student-details">
|
| 430 |
-
<h2><?php echo htmlspecialchars($studentData['full_name']); ?></h2>
|
| 431 |
-
<p><strong>Student Code:</strong> <?php echo htmlspecialchars($studentData['student_code']); ?></p>
|
| 432 |
-
<p><strong>Academic Level:</strong> <?php echo htmlspecialchars($studentData['level_name'] ?? 'N/A'); ?></p>
|
| 433 |
-
</div>
|
| 434 |
-
|
| 435 |
-
<?php if (count($outstandingFees) > 0): ?>
|
| 436 |
-
<!-- Outstanding Fees Section -->
|
| 437 |
-
<div class="fees-section">
|
| 438 |
-
<h3>Outstanding Fees</h3>
|
| 439 |
-
<form id="paymentForm">
|
| 440 |
-
<input type="hidden" name="student_id" value="<?php echo htmlspecialchars($studentData['id']); ?>">
|
| 441 |
-
<input type="hidden" name="student_code"
|
| 442 |
-
value="<?php echo htmlspecialchars($studentData['student_code']); ?>">
|
| 443 |
-
|
| 444 |
-
<table>
|
| 445 |
-
<thead>
|
| 446 |
-
<tr>
|
| 447 |
-
<th width="50">Select</th>
|
| 448 |
-
<th>Fee Description</th>
|
| 449 |
-
<th width="100">Session</th>
|
| 450 |
-
<th width="80">Term</th>
|
| 451 |
-
<th width="120" class="amount">Billed</th>
|
| 452 |
-
<th width="120" class="amount">Paid</th>
|
| 453 |
-
<th width="120" class="amount">Outstanding</th>
|
| 454 |
-
</tr>
|
| 455 |
-
</thead>
|
| 456 |
-
<tbody>
|
| 457 |
-
<?php
|
| 458 |
-
$totalOutstanding = 0;
|
| 459 |
-
foreach ($outstandingFees as $fee):
|
| 460 |
-
$totalOutstanding += $fee['outstanding_amount'];
|
| 461 |
-
?>
|
| 462 |
-
<tr>
|
| 463 |
-
<td>
|
| 464 |
-
<input type="checkbox" class="fee-checkbox" name="selected_fees[]" value="<?php echo htmlspecialchars(json_encode([
|
| 465 |
-
'receivable_id' => $fee['receivable_id'],
|
| 466 |
-
'fee_id' => $fee['fee_id'],
|
| 467 |
-
'academic_session' => $fee['academic_session'],
|
| 468 |
-
'term_of_session' => $fee['term_of_session'],
|
| 469 |
-
'outstanding_amount' => $fee['outstanding_amount']
|
| 470 |
-
])); ?>" checked>
|
| 471 |
-
</td>
|
| 472 |
-
<td><?php echo htmlspecialchars($fee['fee_description']); ?></td>
|
| 473 |
-
<td><?php echo htmlspecialchars($fee['academic_session']); ?></td>
|
| 474 |
-
<td><?php echo htmlspecialchars($fee['term_of_session']); ?></td>
|
| 475 |
-
<td class="amount">₦<?php echo number_format($fee['billed_amount'], 2); ?></td>
|
| 476 |
-
<td class="amount">₦<?php echo number_format($fee['total_paid'], 2); ?></td>
|
| 477 |
-
<td class="amount">₦<?php echo number_format($fee['outstanding_amount'], 2); ?></td>
|
| 478 |
-
</tr>
|
| 479 |
-
<?php endforeach; ?>
|
| 480 |
-
<tr class="total-row">
|
| 481 |
-
<td colspan="6" style="text-align: right;">Total Outstanding:</td>
|
| 482 |
-
<td class="amount">₦<?php echo number_format($totalOutstanding, 2); ?></td>
|
| 483 |
-
</tr>
|
| 484 |
-
</tbody>
|
| 485 |
-
</table>
|
| 486 |
-
|
| 487 |
-
<button type="button" class="btn btn-primary" id="processPaymentBtn">Process Payment</button>
|
| 488 |
-
</form>
|
| 489 |
-
</div>
|
| 490 |
-
<?php else: ?>
|
| 491 |
-
<div class="alert alert-error">No outstanding fees found for this student.</div>
|
| 492 |
-
<?php endif; ?>
|
| 493 |
-
<?php endif; ?>
|
| 494 |
-
</div>
|
| 495 |
-
|
| 496 |
-
<!-- Payment Modal -->
|
| 497 |
-
<div class="modal" id="paymentModal">
|
| 498 |
-
<div class="modal-content">
|
| 499 |
-
<div class="modal-header">
|
| 500 |
-
<h2>Process Payment</h2>
|
| 501 |
-
</div>
|
| 502 |
-
|
| 503 |
-
<form id="paymentDetailsForm" method="POST" action="process_payment.php">
|
| 504 |
-
<input type="hidden" name="student_id" id="modal_student_id">
|
| 505 |
-
<input type="hidden" name="student_code" id="modal_student_code">
|
| 506 |
-
<input type="hidden" name="selected_fees" id="modal_selected_fees">
|
| 507 |
-
<input type="hidden" name="payment_date" id="modal_payment_date">
|
| 508 |
-
<input type="hidden" name="bank_description" id="modal_bank_description">
|
| 509 |
-
|
| 510 |
-
<div class="form-group">
|
| 511 |
-
<label for="teller_number">Teller Number *</label>
|
| 512 |
-
<input type="text" id="teller_number" name="teller_number" required>
|
| 513 |
-
<span class="loading" id="tellerLoading" style="display:none;"></span>
|
| 514 |
-
<div class="error" id="tellerError"></div>
|
| 515 |
-
</div>
|
| 516 |
-
|
| 517 |
-
<div class="form-group">
|
| 518 |
-
<label for="bank_narration">Bank Narration</label>
|
| 519 |
-
<textarea id="bank_narration" name="bank_narration" rows="3" readonly></textarea>
|
| 520 |
-
</div>
|
| 521 |
-
|
| 522 |
-
<div class="form-group">
|
| 523 |
-
<label for="unreconciled_amount">Unreconciled Amount on Teller</label>
|
| 524 |
-
<input type="text" id="unreconciled_amount" readonly>
|
| 525 |
-
</div>
|
| 526 |
-
|
| 527 |
-
<div class="form-group">
|
| 528 |
-
<label for="amount_to_use">Amount to Use for Fees *</label>
|
| 529 |
-
<input type="number" id="amount_to_use" name="amount_to_use" step="0.01" min="0" required>
|
| 530 |
-
<div class="error" id="amountError"></div>
|
| 531 |
-
</div>
|
| 532 |
-
|
| 533 |
-
<div class="modal-actions">
|
| 534 |
-
<button type="button" class="btn btn-secondary" id="cancelBtn">Cancel</button>
|
| 535 |
-
<button type="submit" class="btn btn-primary" id="proceedBtn" disabled>OK PROCEED!</button>
|
| 536 |
-
</div>
|
| 537 |
-
</form>
|
| 538 |
-
</div>
|
| 539 |
-
</div>
|
| 540 |
-
|
| 541 |
-
<script>
|
| 542 |
-
// Student search functionality
|
| 543 |
-
const searchInput = document.getElementById('studentSearch');
|
| 544 |
-
const searchResults = document.getElementById('searchResults');
|
| 545 |
-
let searchTimeout;
|
| 546 |
-
|
| 547 |
-
searchInput.addEventListener('input', function () {
|
| 548 |
-
clearTimeout(searchTimeout);
|
| 549 |
-
const searchTerm = this.value.trim();
|
| 550 |
-
|
| 551 |
-
if (searchTerm.length < 2) {
|
| 552 |
-
searchResults.classList.remove('active');
|
| 553 |
-
searchResults.innerHTML = '';
|
| 554 |
-
return;
|
| 555 |
-
}
|
| 556 |
-
|
| 557 |
-
searchTimeout = setTimeout(() => {
|
| 558 |
-
fetch(`ajax_handlers.php?action=search_students&search=${encodeURIComponent(searchTerm)}`)
|
| 559 |
-
.then(response => response.json())
|
| 560 |
-
.then(data => {
|
| 561 |
-
if (data.error) {
|
| 562 |
-
searchResults.innerHTML = `<div class="search-result-item">${data.error}</div>`;
|
| 563 |
-
} else if (data.length === 0) {
|
| 564 |
-
searchResults.innerHTML = '<div class="search-result-item">No students found</div>';
|
| 565 |
-
} else {
|
| 566 |
-
searchResults.innerHTML = data.map(student =>
|
| 567 |
-
`<div class="search-result-item" data-id="${student.id}">
|
| 568 |
-
<span class="student-code">${student.student_code}</span>
|
| 569 |
-
<span>${student.full_name}</span>
|
| 570 |
-
</div>`
|
| 571 |
-
).join('');
|
| 572 |
-
|
| 573 |
-
// Add click handlers
|
| 574 |
-
document.querySelectorAll('.search-result-item').forEach(item => {
|
| 575 |
-
item.addEventListener('click', function () {
|
| 576 |
-
const studentId = this.dataset.id;
|
| 577 |
-
window.location.href = `?student_id=${studentId}`;
|
| 578 |
-
});
|
| 579 |
-
});
|
| 580 |
-
}
|
| 581 |
-
searchResults.classList.add('active');
|
| 582 |
-
})
|
| 583 |
-
.catch(error => {
|
| 584 |
-
console.error('Search error:', error);
|
| 585 |
-
searchResults.innerHTML = '<div class="search-result-item">Error searching students</div>';
|
| 586 |
-
searchResults.classList.add('active');
|
| 587 |
-
});
|
| 588 |
-
}, 300);
|
| 589 |
-
});
|
| 590 |
-
|
| 591 |
-
// Close search results when clicking outside
|
| 592 |
-
document.addEventListener('click', function (e) {
|
| 593 |
-
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
|
| 594 |
-
searchResults.classList.remove('active');
|
| 595 |
-
}
|
| 596 |
-
});
|
| 597 |
-
|
| 598 |
-
// Payment modal functionality
|
| 599 |
-
const modal = document.getElementById('paymentModal');
|
| 600 |
-
const processPaymentBtn = document.getElementById('processPaymentBtn');
|
| 601 |
-
const cancelBtn = document.getElementById('cancelBtn');
|
| 602 |
-
const tellerInput = document.getElementById('teller_number');
|
| 603 |
-
const tellerLoading = document.getElementById('tellerLoading');
|
| 604 |
-
const tellerError = document.getElementById('tellerError');
|
| 605 |
-
const amountInput = document.getElementById('amount_to_use');
|
| 606 |
-
const amountError = document.getElementById('amountError');
|
| 607 |
-
const proceedBtn = document.getElementById('proceedBtn');
|
| 608 |
-
|
| 609 |
-
let unreconciledAmount = 0;
|
| 610 |
-
|
| 611 |
-
processPaymentBtn?.addEventListener('click', function () {
|
| 612 |
-
const checkedFees = document.querySelectorAll('.fee-checkbox:checked');
|
| 613 |
-
|
| 614 |
-
if (checkedFees.length === 0) {
|
| 615 |
-
alert('Please select at least one fee to process payment.');
|
| 616 |
-
return;
|
| 617 |
-
}
|
| 618 |
-
|
| 619 |
-
// Collect selected fees
|
| 620 |
-
const selectedFees = Array.from(checkedFees).map(cb => JSON.parse(cb.value));
|
| 621 |
-
|
| 622 |
-
// Populate modal
|
| 623 |
-
document.getElementById('modal_student_id').value = document.querySelector('input[name="student_id"]').value;
|
| 624 |
-
document.getElementById('modal_student_code').value = document.querySelector('input[name="student_code"]').value;
|
| 625 |
-
document.getElementById('modal_selected_fees').value = JSON.stringify(selectedFees);
|
| 626 |
-
|
| 627 |
-
// Reset form
|
| 628 |
-
document.getElementById('paymentDetailsForm').reset();
|
| 629 |
-
tellerError.textContent = '';
|
| 630 |
-
amountError.textContent = '';
|
| 631 |
-
proceedBtn.disabled = true;
|
| 632 |
-
unreconciledAmount = 0;
|
| 633 |
-
|
| 634 |
-
modal.classList.add('active');
|
| 635 |
-
});
|
| 636 |
-
|
| 637 |
-
cancelBtn.addEventListener('click', function () {
|
| 638 |
-
modal.classList.remove('active');
|
| 639 |
-
});
|
| 640 |
-
|
| 641 |
-
// Teller lookup on blur
|
| 642 |
-
tellerInput.addEventListener('blur', function () {
|
| 643 |
-
const tellerNumber = this.value.trim();
|
| 644 |
-
|
| 645 |
-
if (!tellerNumber) {
|
| 646 |
-
return;
|
| 647 |
-
}
|
| 648 |
-
|
| 649 |
-
tellerLoading.style.display = 'inline-block';
|
| 650 |
-
tellerError.textContent = '';
|
| 651 |
-
|
| 652 |
-
fetch(`ajax_handlers.php?action=lookup_teller&teller_number=${encodeURIComponent(tellerNumber)}`)
|
| 653 |
-
.then(response => response.json())
|
| 654 |
-
.then(data => {
|
| 655 |
-
tellerLoading.style.display = 'none';
|
| 656 |
-
|
| 657 |
-
if (data.error) {
|
| 658 |
-
tellerError.textContent = data.error;
|
| 659 |
-
document.getElementById('bank_narration').value = '';
|
| 660 |
-
document.getElementById('unreconciled_amount').value = '';
|
| 661 |
-
unreconciledAmount = 0;
|
| 662 |
-
proceedBtn.disabled = true;
|
| 663 |
-
} else {
|
| 664 |
-
document.getElementById('bank_narration').value = data.teller_name;
|
| 665 |
-
document.getElementById('unreconciled_amount').value = '₦' + parseFloat(data.unreconciled_amount).toFixed(2);
|
| 666 |
-
document.getElementById('modal_payment_date').value = data.payment_date;
|
| 667 |
-
document.getElementById('modal_bank_description').value = data.description;
|
| 668 |
-
unreconciledAmount = parseFloat(data.unreconciled_amount);
|
| 669 |
-
|
| 670 |
-
// Enable proceed button if amount is valid
|
| 671 |
-
validateAmount();
|
| 672 |
-
}
|
| 673 |
-
})
|
| 674 |
-
.catch(error => {
|
| 675 |
-
tellerLoading.style.display = 'none';
|
| 676 |
-
tellerError.textContent = 'Error looking up teller number';
|
| 677 |
-
console.error('Teller lookup error:', error);
|
| 678 |
-
});
|
| 679 |
-
});
|
| 680 |
-
|
| 681 |
-
// Amount validation
|
| 682 |
-
amountInput.addEventListener('input', validateAmount);
|
| 683 |
-
|
| 684 |
-
function validateAmount() {
|
| 685 |
-
const amount = parseFloat(amountInput.value);
|
| 686 |
-
|
| 687 |
-
if (isNaN(amount) || amount <= 0) {
|
| 688 |
-
amountError.textContent = 'Amount must be greater than zero';
|
| 689 |
-
proceedBtn.disabled = true;
|
| 690 |
-
return;
|
| 691 |
-
}
|
| 692 |
-
|
| 693 |
-
if (unreconciledAmount === 0) {
|
| 694 |
-
amountError.textContent = 'Please enter a valid teller number first';
|
| 695 |
-
proceedBtn.disabled = true;
|
| 696 |
-
return;
|
| 697 |
-
}
|
| 698 |
-
|
| 699 |
-
if (amount > unreconciledAmount) {
|
| 700 |
-
amountError.textContent = `Amount cannot exceed unreconciled amount (₦${unreconciledAmount.toFixed(2)})`;
|
| 701 |
-
proceedBtn.disabled = true;
|
| 702 |
-
return;
|
| 703 |
-
}
|
| 704 |
-
|
| 705 |
-
amountError.textContent = '';
|
| 706 |
-
proceedBtn.disabled = false;
|
| 707 |
-
}
|
| 708 |
-
|
| 709 |
-
// Handle form submission with AJAX to intercept confirmation requests
|
| 710 |
-
const paymentDetailsForm = document.getElementById('paymentDetailsForm');
|
| 711 |
-
paymentDetailsForm.addEventListener('submit', function (e) {
|
| 712 |
-
e.preventDefault();
|
| 713 |
-
|
| 714 |
-
// Disable submit button to prevent double submission
|
| 715 |
-
proceedBtn.disabled = true;
|
| 716 |
-
proceedBtn.textContent = 'Processing...';
|
| 717 |
-
|
| 718 |
-
// Get form data
|
| 719 |
-
const formData = new FormData(this);
|
| 720 |
-
|
| 721 |
-
// Submit via AJAX
|
| 722 |
-
fetch('process_payment.php', {
|
| 723 |
-
method: 'POST',
|
| 724 |
-
body: formData
|
| 725 |
-
})
|
| 726 |
-
.then(response => {
|
| 727 |
-
const contentType = response.headers.get('content-type');
|
| 728 |
-
if (contentType && contentType.includes('application/json')) {
|
| 729 |
-
return response.json();
|
| 730 |
-
}
|
| 731 |
-
// If not JSON, it's the HTML success/error page
|
| 732 |
-
return response.text().then(html => ({ html: html }));
|
| 733 |
-
})
|
| 734 |
-
.then(data => {
|
| 735 |
-
if (data.status === 'confirmation_required') {
|
| 736 |
-
// Show confirmation dialog
|
| 737 |
-
const confirmed = confirm(data.message);
|
| 738 |
-
|
| 739 |
-
if (confirmed) {
|
| 740 |
-
// User confirmed - resubmit with confirmation flag
|
| 741 |
-
formData.append('confirm_update', 'yes');
|
| 742 |
-
|
| 743 |
-
// Resubmit the form
|
| 744 |
-
fetch('process_payment.php', {
|
| 745 |
-
method: 'POST',
|
| 746 |
-
body: formData
|
| 747 |
-
})
|
| 748 |
-
.then(response => response.text())
|
| 749 |
-
.then(html => {
|
| 750 |
-
// Write the response HTML to the page
|
| 751 |
-
document.open();
|
| 752 |
-
document.write(html);
|
| 753 |
-
document.close();
|
| 754 |
-
})
|
| 755 |
-
.catch(error => {
|
| 756 |
-
console.error('Error:', error);
|
| 757 |
-
alert('An error occurred while processing the payment.');
|
| 758 |
-
proceedBtn.disabled = false;
|
| 759 |
-
proceedBtn.textContent = 'OK PROCEED!';
|
| 760 |
-
});
|
| 761 |
-
} else {
|
| 762 |
-
// User cancelled
|
| 763 |
-
alert('Payment cancelled by user.');
|
| 764 |
-
proceedBtn.disabled = false;
|
| 765 |
-
proceedBtn.textContent = 'OK PROCEED!';
|
| 766 |
-
}
|
| 767 |
-
} else if (data.html) {
|
| 768 |
-
// Normal HTML response - display it
|
| 769 |
-
document.open();
|
| 770 |
-
document.write(data.html);
|
| 771 |
-
document.close();
|
| 772 |
-
} else {
|
| 773 |
-
// Unexpected response
|
| 774 |
-
alert('Unexpected response from server.');
|
| 775 |
-
proceedBtn.disabled = false;
|
| 776 |
-
proceedBtn.textContent = 'OK PROCEED!';
|
| 777 |
-
}
|
| 778 |
-
})
|
| 779 |
-
.catch(error => {
|
| 780 |
-
console.error('Error:', error);
|
| 781 |
-
alert('An error occurred while processing the payment.');
|
| 782 |
-
proceedBtn.disabled = false;
|
| 783 |
-
proceedBtn.textContent = 'OK PROCEED!';
|
| 784 |
-
});
|
| 785 |
-
});
|
| 786 |
-
</script>
|
| 787 |
-
</body>
|
| 788 |
-
|
| 789 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
easypay/process_payment.php
DELETED
|
@@ -1,720 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
/**
|
| 3 |
-
* Process Payment
|
| 4 |
-
* Handle payment allocation and database transactions
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
require_once 'db_config.php';
|
| 8 |
-
|
| 9 |
-
// Initialize response
|
| 10 |
-
$success = false;
|
| 11 |
-
$message = '';
|
| 12 |
-
$paymentDetails = [];
|
| 13 |
-
|
| 14 |
-
try {
|
| 15 |
-
// Validate POST data
|
| 16 |
-
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
| 17 |
-
throw new Exception('Invalid request method');
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
$studentId = $_POST['student_id'] ?? '';
|
| 21 |
-
$studentCode = $_POST['student_code'] ?? '';
|
| 22 |
-
$selectedFeesJson = $_POST['selected_fees'] ?? '';
|
| 23 |
-
$tellerNumber = $_POST['teller_number'] ?? '';
|
| 24 |
-
$bankDescription = $_POST['bank_description'] ?? '';
|
| 25 |
-
$paymentDate = $_POST['payment_date'] ?? '';
|
| 26 |
-
$amountToUse = floatval($_POST['amount_to_use'] ?? 0);
|
| 27 |
-
|
| 28 |
-
// Validate required fields
|
| 29 |
-
if (
|
| 30 |
-
empty($studentId) || empty($studentCode) || empty($selectedFeesJson) ||
|
| 31 |
-
empty($tellerNumber) || empty($paymentDate) || $amountToUse <= 0
|
| 32 |
-
) {
|
| 33 |
-
throw new Exception('Missing required fields');
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
// Parse selected fees
|
| 37 |
-
$selectedFees = json_decode($selectedFeesJson, true);
|
| 38 |
-
if (!is_array($selectedFees) || count($selectedFees) === 0) {
|
| 39 |
-
throw new Exception('No fees selected');
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
// Sort fees by academic_session ASC, term_of_session ASC (oldest to newest)
|
| 43 |
-
usort($selectedFees, function ($a, $b) {
|
| 44 |
-
if ($a['academic_session'] != $b['academic_session']) {
|
| 45 |
-
return $a['academic_session'] - $b['academic_session'];
|
| 46 |
-
}
|
| 47 |
-
return $a['term_of_session'] - $b['term_of_session'];
|
| 48 |
-
});
|
| 49 |
-
|
| 50 |
-
// Re-fetch bank statement to verify
|
| 51 |
-
$sql = "SELECT
|
| 52 |
-
bs.id,
|
| 53 |
-
bs.description,
|
| 54 |
-
bs.amount_paid,
|
| 55 |
-
bs.payment_date,
|
| 56 |
-
COALESCE(fp.total_registered_fee, 0.00) AS registered_amount,
|
| 57 |
-
(bs.amount_paid - COALESCE(fp.total_registered_fee, 0.00)) AS unreconciled_amount
|
| 58 |
-
FROM tb_account_bank_statements bs
|
| 59 |
-
LEFT JOIN (
|
| 60 |
-
SELECT teller_no, SUM(amount_paid) AS total_registered_fee
|
| 61 |
-
FROM tb_account_school_fee_payments
|
| 62 |
-
GROUP BY teller_no
|
| 63 |
-
) fp ON SUBSTRING_INDEX(bs.description, ' ', -1) = fp.teller_no
|
| 64 |
-
WHERE SUBSTRING_INDEX(bs.description, ' ', -1) = :teller_number
|
| 65 |
-
LIMIT 1";
|
| 66 |
-
|
| 67 |
-
$stmt = $pdo->prepare($sql);
|
| 68 |
-
$stmt->execute(['teller_number' => $tellerNumber]);
|
| 69 |
-
$bankStatement = $stmt->fetch();
|
| 70 |
-
|
| 71 |
-
if (!$bankStatement) {
|
| 72 |
-
throw new Exception('Bank statement not found for teller number: ' . $tellerNumber);
|
| 73 |
-
}
|
| 74 |
-
|
| 75 |
-
// Verify unreconciled amount
|
| 76 |
-
if ($amountToUse > $bankStatement['unreconciled_amount']) {
|
| 77 |
-
throw new Exception('Amount exceeds unreconciled amount on teller');
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
// Extract teller name and number from description
|
| 81 |
-
$descParts = explode(' ', $bankStatement['description']);
|
| 82 |
-
$tellerNo = array_pop($descParts);
|
| 83 |
-
$tellerName = implode(' ', $descParts);
|
| 84 |
-
|
| 85 |
-
// Use the oldest fee's session/term for transaction_id
|
| 86 |
-
$dominantSession = $selectedFees[0]['academic_session'];
|
| 87 |
-
$dominantTerm = $selectedFees[0]['term_of_session'];
|
| 88 |
-
|
| 89 |
-
// Generate transaction_id
|
| 90 |
-
$transactionId = $studentId . $dominantSession . $paymentDate;
|
| 91 |
-
|
| 92 |
-
// STEP 1: Guard check - prevent duplicate payments on same date
|
| 93 |
-
$sql = "SELECT id, academic_session, term_of_session, total_paid
|
| 94 |
-
FROM tb_account_school_fee_sum_payments
|
| 95 |
-
WHERE student_id = :student_id
|
| 96 |
-
AND payment_date = :payment_date
|
| 97 |
-
LIMIT 1";
|
| 98 |
-
|
| 99 |
-
$stmt = $pdo->prepare($sql);
|
| 100 |
-
$stmt->execute([
|
| 101 |
-
'student_id' => $studentId,
|
| 102 |
-
'payment_date' => $paymentDate
|
| 103 |
-
]);
|
| 104 |
-
|
| 105 |
-
$existingPayment = $stmt->fetch();
|
| 106 |
-
$isUpdateMode = false;
|
| 107 |
-
$existingRecordId = null;
|
| 108 |
-
$existingTotalPaid = 0;
|
| 109 |
-
|
| 110 |
-
if ($existingPayment) {
|
| 111 |
-
// Check if the existing payment is for the same term
|
| 112 |
-
if (
|
| 113 |
-
$existingPayment['term_of_session'] != $dominantTerm ||
|
| 114 |
-
$existingPayment['academic_session'] != $dominantSession
|
| 115 |
-
) {
|
| 116 |
-
throw new Exception('A payment for this student has already been registered on this date in a different term (Session: ' .
|
| 117 |
-
$existingPayment['academic_session'] . ', Term: ' . $existingPayment['term_of_session'] . ')');
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
// Same term - need user confirmation to update
|
| 121 |
-
// This will be handled by JavaScript popup on the client side
|
| 122 |
-
// For now, check if user has confirmed via POST parameter
|
| 123 |
-
$userConfirmed = isset($_POST['confirm_update']) && $_POST['confirm_update'] === 'yes';
|
| 124 |
-
|
| 125 |
-
if (!$userConfirmed) {
|
| 126 |
-
// Return a special response that triggers the popup
|
| 127 |
-
header('Content-Type: application/json');
|
| 128 |
-
echo json_encode([
|
| 129 |
-
'status' => 'confirmation_required',
|
| 130 |
-
'message' => 'A payment for this student already exists on ' . $paymentDate . ' for Session ' .
|
| 131 |
-
$existingPayment['academic_session'] . ', Term ' . $existingPayment['term_of_session'] .
|
| 132 |
-
'. Current total: ₦' . number_format($existingPayment['total_paid'], 2) .
|
| 133 |
-
'. Do you want to update this record by adding ₦' . number_format($totalPaid, 2) . ' to the existing amount?',
|
| 134 |
-
'existing_payment' => [
|
| 135 |
-
'id' => $existingPayment['id'],
|
| 136 |
-
'session' => $existingPayment['academic_session'],
|
| 137 |
-
'term' => $existingPayment['term_of_session'],
|
| 138 |
-
'total_paid' => $existingPayment['total_paid']
|
| 139 |
-
]
|
| 140 |
-
]);
|
| 141 |
-
exit;
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
// User confirmed - set update mode
|
| 145 |
-
$isUpdateMode = true;
|
| 146 |
-
$existingRecordId = $existingPayment['id'];
|
| 147 |
-
$existingTotalPaid = floatval($existingPayment['total_paid']);
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
// STEP 2: Allocate payment across fees (oldest to newest)
|
| 151 |
-
$feeAllocations = [];
|
| 152 |
-
$remainingAmount = $amountToUse;
|
| 153 |
-
|
| 154 |
-
foreach ($selectedFees as $fee) {
|
| 155 |
-
if ($remainingAmount <= 0) {
|
| 156 |
-
break;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
$outstandingAmount = floatval($fee['outstanding_amount']);
|
| 160 |
-
|
| 161 |
-
if ($remainingAmount >= $outstandingAmount) {
|
| 162 |
-
// Fully settle this fee
|
| 163 |
-
$amountForThisFee = $outstandingAmount;
|
| 164 |
-
$remainingAmount -= $outstandingAmount;
|
| 165 |
-
} else {
|
| 166 |
-
// Partially settle this fee
|
| 167 |
-
$amountForThisFee = $remainingAmount;
|
| 168 |
-
$remainingAmount = 0;
|
| 169 |
-
}
|
| 170 |
-
|
| 171 |
-
$feeAllocations[] = [
|
| 172 |
-
'fee_id' => $fee['fee_id'],
|
| 173 |
-
'academic_session' => $fee['academic_session'],
|
| 174 |
-
'term_of_session' => $fee['term_of_session'],
|
| 175 |
-
'amount' => $amountForThisFee
|
| 176 |
-
];
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
if (count($feeAllocations) === 0) {
|
| 180 |
-
throw new Exception('No fees could be allocated');
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
// Calculate total paid
|
| 184 |
-
$totalPaid = array_sum(array_column($feeAllocations, 'amount'));
|
| 185 |
-
|
| 186 |
-
// STEP 3: Begin database transaction
|
| 187 |
-
$pdo->beginTransaction();
|
| 188 |
-
|
| 189 |
-
try {
|
| 190 |
-
// Constants
|
| 191 |
-
$creditBankId = '000001373634585148';
|
| 192 |
-
$debitBankId = '514297805530965017';
|
| 193 |
-
$paymodeId = '000001373901891416';
|
| 194 |
-
$recipientId = 'SS0011441283890434';
|
| 195 |
-
$paymentBy = 'SS0011441283890434';
|
| 196 |
-
$createdBy = 'SS0011441283890434';
|
| 197 |
-
$createdAs = 'school';
|
| 198 |
-
$paymentStatus = 'Approved';
|
| 199 |
-
$paymodeCategory = 'BANK';
|
| 200 |
-
|
| 201 |
-
// Generate receipt_no (used across all fee records)
|
| 202 |
-
$receiptNo = $studentCode . $paymentDate;
|
| 203 |
-
|
| 204 |
-
// 3a) INSERT into tb_account_school_fee_payments (per fee)
|
| 205 |
-
foreach ($feeAllocations as $allocation) {
|
| 206 |
-
$schoolFeePaymentId = $studentCode . $allocation['fee_id'] . $paymentDate; //original code
|
| 207 |
-
|
| 208 |
-
//$schoolFeePaymentId = $allocation['fee_id'] . $paymentDate; //Is used to reduce length of id to solve duplicate entry error.
|
| 209 |
-
|
| 210 |
-
$sql = "INSERT INTO tb_account_school_fee_payments (
|
| 211 |
-
id, fee_id, student_id, transaction_id, amount_paid,
|
| 212 |
-
teller_no, teller_name, credit_bank_id, debit_bank_id,
|
| 213 |
-
paymode_id, payment_status, payment_date, recipient_id,
|
| 214 |
-
academic_session, term_of_session, payment_by, payment_on
|
| 215 |
-
) VALUES (
|
| 216 |
-
:id, :fee_id, :student_id, :transaction_id, :amount_paid,
|
| 217 |
-
:teller_no, :teller_name, :credit_bank_id, :debit_bank_id,
|
| 218 |
-
:paymode_id, :payment_status, :payment_date, :recipient_id,
|
| 219 |
-
:academic_session, :term_of_session, :payment_by, NOW()
|
| 220 |
-
)";
|
| 221 |
-
|
| 222 |
-
$stmt = $pdo->prepare($sql);
|
| 223 |
-
$stmt->execute([
|
| 224 |
-
'id' => $schoolFeePaymentId,
|
| 225 |
-
'fee_id' => $allocation['fee_id'],
|
| 226 |
-
'student_id' => $studentId,
|
| 227 |
-
'transaction_id' => $transactionId,
|
| 228 |
-
'amount_paid' => $allocation['amount'],
|
| 229 |
-
'teller_no' => $tellerNo,
|
| 230 |
-
'teller_name' => $tellerName,
|
| 231 |
-
'credit_bank_id' => $creditBankId,
|
| 232 |
-
'debit_bank_id' => $debitBankId,
|
| 233 |
-
'paymode_id' => $paymodeId,
|
| 234 |
-
'payment_status' => $paymentStatus,
|
| 235 |
-
'payment_date' => $paymentDate,
|
| 236 |
-
'recipient_id' => $recipientId,
|
| 237 |
-
'academic_session' => $allocation['academic_session'],
|
| 238 |
-
'term_of_session' => $allocation['term_of_session'],
|
| 239 |
-
'payment_by' => $paymentBy
|
| 240 |
-
]);
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
// 3b) INSERT or UPDATE tb_account_school_fee_sum_payments (single record)
|
| 244 |
-
if ($isUpdateMode) {
|
| 245 |
-
// Update existing record
|
| 246 |
-
$sql = "UPDATE tb_account_school_fee_sum_payments
|
| 247 |
-
SET total_paid = total_paid + :additional_amount,
|
| 248 |
-
registered_on = NOW()
|
| 249 |
-
WHERE id = :id";
|
| 250 |
-
|
| 251 |
-
$stmt = $pdo->prepare($sql);
|
| 252 |
-
$stmt->execute([
|
| 253 |
-
'additional_amount' => $totalPaid,
|
| 254 |
-
'id' => $existingRecordId
|
| 255 |
-
]);
|
| 256 |
-
} else {
|
| 257 |
-
// Insert new record
|
| 258 |
-
$sumPaymentsId = $studentId . $dominantSession . $paymentDate;
|
| 259 |
-
|
| 260 |
-
$sql = "INSERT INTO tb_account_school_fee_sum_payments (
|
| 261 |
-
id, student_id, total_paid, paymode_id, payment_date,
|
| 262 |
-
credit_bank_id, debit_bank_id, transaction_id, status,
|
| 263 |
-
academic_session, term_of_session, registered_by, registered_on
|
| 264 |
-
) VALUES (
|
| 265 |
-
:id, :student_id, :total_paid, :paymode_id, :payment_date,
|
| 266 |
-
:credit_bank_id, :debit_bank_id, :transaction_id, :status,
|
| 267 |
-
:academic_session, :term_of_session, :registered_by, NOW()
|
| 268 |
-
)";
|
| 269 |
-
|
| 270 |
-
$stmt = $pdo->prepare($sql);
|
| 271 |
-
$stmt->execute([
|
| 272 |
-
'id' => $sumPaymentsId,
|
| 273 |
-
'student_id' => $studentId,
|
| 274 |
-
'total_paid' => $totalPaid,
|
| 275 |
-
'paymode_id' => $paymodeId,
|
| 276 |
-
'payment_date' => $paymentDate,
|
| 277 |
-
'credit_bank_id' => $creditBankId,
|
| 278 |
-
'debit_bank_id' => $debitBankId,
|
| 279 |
-
'transaction_id' => $transactionId,
|
| 280 |
-
'status' => $paymentStatus,
|
| 281 |
-
'academic_session' => $dominantSession,
|
| 282 |
-
'term_of_session' => $dominantTerm,
|
| 283 |
-
'registered_by' => $createdBy
|
| 284 |
-
]);
|
| 285 |
-
}
|
| 286 |
-
|
| 287 |
-
// 3c) INSERT into tb_account_student_payments (per fee)
|
| 288 |
-
foreach ($feeAllocations as $allocation) {
|
| 289 |
-
// Get current payment_to_date (sum of all previous payments for this fee/session/term)
|
| 290 |
-
$sql = "SELECT SUM(payment_to_date) AS current_total
|
| 291 |
-
FROM tb_account_student_payments
|
| 292 |
-
WHERE student_id = :student_id
|
| 293 |
-
AND fee_id = :fee_id
|
| 294 |
-
AND academic_session = :academic_session
|
| 295 |
-
AND term_of_session = :term_of_session";
|
| 296 |
-
|
| 297 |
-
$stmt = $pdo->prepare($sql);
|
| 298 |
-
$stmt->execute([
|
| 299 |
-
'student_id' => $studentId,
|
| 300 |
-
'fee_id' => $allocation['fee_id'],
|
| 301 |
-
'academic_session' => $allocation['academic_session'],
|
| 302 |
-
'term_of_session' => $allocation['term_of_session']
|
| 303 |
-
]);
|
| 304 |
-
|
| 305 |
-
$currentPayment = $stmt->fetch();
|
| 306 |
-
$currentTotal = floatval($currentPayment['current_total'] ?? 0);
|
| 307 |
-
$newPaymentToDate = $allocation['amount'];
|
| 308 |
-
//$newPaymentToDate = $currentTotal + $allocation['amount']; --Old code that added new payment amount to current total (total before payment)
|
| 309 |
-
|
| 310 |
-
$studentPaymentId = $studentCode . $allocation['fee_id'] . $paymentDate;
|
| 311 |
-
|
| 312 |
-
$sql = "INSERT INTO tb_account_student_payments (
|
| 313 |
-
id, fee_id, student_id, payment_to_date, transaction_id,
|
| 314 |
-
academic_session, term_of_session, created_by, created_as, created_on
|
| 315 |
-
) VALUES (
|
| 316 |
-
:id, :fee_id, :student_id, :payment_to_date, :transaction_id,
|
| 317 |
-
:academic_session, :term_of_session, :created_by, :created_as, NOW()
|
| 318 |
-
)";
|
| 319 |
-
|
| 320 |
-
$stmt = $pdo->prepare($sql);
|
| 321 |
-
$stmt->execute([
|
| 322 |
-
'id' => $studentPaymentId,
|
| 323 |
-
'fee_id' => $allocation['fee_id'],
|
| 324 |
-
'student_id' => $studentId,
|
| 325 |
-
'payment_to_date' => $newPaymentToDate,
|
| 326 |
-
'transaction_id' => $transactionId,
|
| 327 |
-
'academic_session' => $allocation['academic_session'],
|
| 328 |
-
'term_of_session' => $allocation['term_of_session'],
|
| 329 |
-
'created_by' => $createdBy,
|
| 330 |
-
'created_as' => $createdAs
|
| 331 |
-
]);
|
| 332 |
-
}
|
| 333 |
-
|
| 334 |
-
// 3d) INSERT into tb_account_payment_registers (per fee)
|
| 335 |
-
foreach ($feeAllocations as $allocation) {
|
| 336 |
-
$paymentRegisterId = $studentCode . $allocation['fee_id'] . $paymentDate;
|
| 337 |
-
|
| 338 |
-
$sql = "INSERT INTO tb_account_payment_registers (
|
| 339 |
-
id, fee_id, student_id, amount_paid, amount_due,
|
| 340 |
-
receipt_no, recipient_id, payment_date, paymode_category,
|
| 341 |
-
transaction_id, academic_session, term_of_session,
|
| 342 |
-
created_by, created_as, created_on
|
| 343 |
-
) VALUES (
|
| 344 |
-
:id, :fee_id, :student_id, :amount_paid, :amount_due,
|
| 345 |
-
:receipt_no, :recipient_id, :payment_date, :paymode_category,
|
| 346 |
-
:transaction_id, :academic_session, :term_of_session,
|
| 347 |
-
:created_by, :created_as, NOW()
|
| 348 |
-
)";
|
| 349 |
-
|
| 350 |
-
$stmt = $pdo->prepare($sql);
|
| 351 |
-
$stmt->execute([
|
| 352 |
-
'id' => $paymentRegisterId,
|
| 353 |
-
'fee_id' => $allocation['fee_id'],
|
| 354 |
-
'student_id' => $studentId,
|
| 355 |
-
'amount_paid' => $allocation['amount'],
|
| 356 |
-
'amount_due' => $allocation['amount'],
|
| 357 |
-
'receipt_no' => $receiptNo,
|
| 358 |
-
'recipient_id' => $recipientId,
|
| 359 |
-
'payment_date' => $paymentDate,
|
| 360 |
-
'paymode_category' => $paymodeCategory,
|
| 361 |
-
'transaction_id' => $transactionId,
|
| 362 |
-
'academic_session' => $allocation['academic_session'],
|
| 363 |
-
'term_of_session' => $allocation['term_of_session'],
|
| 364 |
-
'created_by' => $createdBy,
|
| 365 |
-
'created_as' => $createdAs
|
| 366 |
-
]);
|
| 367 |
-
}
|
| 368 |
-
|
| 369 |
-
// 3e) UPDATE tb_student_logistics
|
| 370 |
-
// Group allocations by session/term to update specific records
|
| 371 |
-
$sessionTermTotals = [];
|
| 372 |
-
foreach ($feeAllocations as $allocation) {
|
| 373 |
-
$key = $allocation['academic_session'] . '-' . $allocation['term_of_session'];
|
| 374 |
-
if (!isset($sessionTermTotals[$key])) {
|
| 375 |
-
$sessionTermTotals[$key] = [
|
| 376 |
-
'academic_session' => $allocation['academic_session'],
|
| 377 |
-
'term_of_session' => $allocation['term_of_session'],
|
| 378 |
-
'total' => 0
|
| 379 |
-
];
|
| 380 |
-
}
|
| 381 |
-
$sessionTermTotals[$key]['total'] += $allocation['amount'];
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
-
// Update each session/term record
|
| 385 |
-
foreach ($sessionTermTotals as $st) {
|
| 386 |
-
$sql = "UPDATE tb_student_logistics
|
| 387 |
-
SET fees_outstanding = GREATEST(0, fees_outstanding - :total_paid)
|
| 388 |
-
WHERE student_id = :student_id
|
| 389 |
-
AND academic_session = :academic_session
|
| 390 |
-
AND term_of_session = :term_of_session";
|
| 391 |
-
|
| 392 |
-
$stmt = $pdo->prepare($sql);
|
| 393 |
-
$stmt->execute([
|
| 394 |
-
'total_paid' => $st['total'],
|
| 395 |
-
'student_id' => $studentId,
|
| 396 |
-
'academic_session' => $st['academic_session'],
|
| 397 |
-
'term_of_session' => $st['term_of_session']
|
| 398 |
-
]);
|
| 399 |
-
}
|
| 400 |
-
|
| 401 |
-
// Commit transaction
|
| 402 |
-
$pdo->commit();
|
| 403 |
-
|
| 404 |
-
$success = true;
|
| 405 |
-
$message = 'Payment processed successfully!';
|
| 406 |
-
|
| 407 |
-
// Fetch fee descriptions for display
|
| 408 |
-
$feeIds = array_column($feeAllocations, 'fee_id');
|
| 409 |
-
$placeholders = implode(',', array_fill(0, count($feeIds), '?'));
|
| 410 |
-
|
| 411 |
-
$sql = "SELECT id, description FROM tb_account_school_fees WHERE id IN ($placeholders)";
|
| 412 |
-
$stmt = $pdo->prepare($sql);
|
| 413 |
-
$stmt->execute($feeIds);
|
| 414 |
-
$feeDescriptions = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
| 415 |
-
|
| 416 |
-
// Prepare payment details for display
|
| 417 |
-
foreach ($feeAllocations as $key => $allocation) {
|
| 418 |
-
$feeAllocations[$key]['description'] = $feeDescriptions[$allocation['fee_id']] ?? 'Unknown Fee';
|
| 419 |
-
}
|
| 420 |
-
$paymentDetails = [
|
| 421 |
-
'student_code' => $studentCode,
|
| 422 |
-
'payment_date' => $paymentDate,
|
| 423 |
-
'teller_no' => $tellerNo,
|
| 424 |
-
'teller_name' => $tellerName,
|
| 425 |
-
'total_paid' => $totalPaid,
|
| 426 |
-
'receipt_no' => $receiptNo,
|
| 427 |
-
'transaction_id' => $transactionId,
|
| 428 |
-
'allocations' => $feeAllocations,
|
| 429 |
-
'remaining_unreconciled' => $bankStatement['unreconciled_amount'] - $totalPaid
|
| 430 |
-
];
|
| 431 |
-
|
| 432 |
-
} catch (Exception $e) {
|
| 433 |
-
$pdo->rollBack();
|
| 434 |
-
throw new Exception('Transaction failed: ' . $e->getMessage());
|
| 435 |
-
}
|
| 436 |
-
|
| 437 |
-
} catch (Exception $e) {
|
| 438 |
-
$success = false;
|
| 439 |
-
$message = $e->getMessage();
|
| 440 |
-
}
|
| 441 |
-
|
| 442 |
-
// Fetch student name for display
|
| 443 |
-
$studentName = '';
|
| 444 |
-
if (!empty($studentId)) {
|
| 445 |
-
try {
|
| 446 |
-
$sql = "SELECT CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) AS full_name
|
| 447 |
-
FROM tb_student_registrations
|
| 448 |
-
WHERE id = :student_id";
|
| 449 |
-
$stmt = $pdo->prepare($sql);
|
| 450 |
-
$stmt->execute(['student_id' => $studentId]);
|
| 451 |
-
$result = $stmt->fetch();
|
| 452 |
-
$studentName = $result['full_name'] ?? 'Unknown Student';
|
| 453 |
-
} catch (Exception $e) {
|
| 454 |
-
$studentName = 'Unknown Student';
|
| 455 |
-
}
|
| 456 |
-
}
|
| 457 |
-
?>
|
| 458 |
-
<!DOCTYPE html>
|
| 459 |
-
<html lang="en">
|
| 460 |
-
|
| 461 |
-
<head>
|
| 462 |
-
<meta charset="UTF-8">
|
| 463 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 464 |
-
<title>Payment Processing Result</title>
|
| 465 |
-
<style>
|
| 466 |
-
* {
|
| 467 |
-
margin: 0;
|
| 468 |
-
padding: 0;
|
| 469 |
-
box-sizing: border-box;
|
| 470 |
-
}
|
| 471 |
-
|
| 472 |
-
body {
|
| 473 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 474 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 475 |
-
min-height: 100vh;
|
| 476 |
-
padding: 20px;
|
| 477 |
-
display: flex;
|
| 478 |
-
align-items: center;
|
| 479 |
-
justify-content: center;
|
| 480 |
-
}
|
| 481 |
-
|
| 482 |
-
.container {
|
| 483 |
-
max-width: 800px;
|
| 484 |
-
width: 100%;
|
| 485 |
-
background: white;
|
| 486 |
-
border-radius: 12px;
|
| 487 |
-
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
| 488 |
-
padding: 40px;
|
| 489 |
-
}
|
| 490 |
-
|
| 491 |
-
.success-icon {
|
| 492 |
-
width: 80px;
|
| 493 |
-
height: 80px;
|
| 494 |
-
margin: 0 auto 20px;
|
| 495 |
-
background: #28a745;
|
| 496 |
-
border-radius: 50%;
|
| 497 |
-
display: flex;
|
| 498 |
-
align-items: center;
|
| 499 |
-
justify-content: center;
|
| 500 |
-
color: white;
|
| 501 |
-
font-size: 48px;
|
| 502 |
-
}
|
| 503 |
-
|
| 504 |
-
.error-icon {
|
| 505 |
-
width: 80px;
|
| 506 |
-
height: 80px;
|
| 507 |
-
margin: 0 auto 20px;
|
| 508 |
-
background: #dc3545;
|
| 509 |
-
border-radius: 50%;
|
| 510 |
-
display: flex;
|
| 511 |
-
align-items: center;
|
| 512 |
-
justify-content: center;
|
| 513 |
-
color: white;
|
| 514 |
-
font-size: 48px;
|
| 515 |
-
}
|
| 516 |
-
|
| 517 |
-
h1 {
|
| 518 |
-
text-align: center;
|
| 519 |
-
color: #333;
|
| 520 |
-
margin-bottom: 10px;
|
| 521 |
-
font-size: 28px;
|
| 522 |
-
}
|
| 523 |
-
|
| 524 |
-
.message {
|
| 525 |
-
text-align: center;
|
| 526 |
-
color: #666;
|
| 527 |
-
margin-bottom: 30px;
|
| 528 |
-
font-size: 16px;
|
| 529 |
-
}
|
| 530 |
-
|
| 531 |
-
.details-section {
|
| 532 |
-
background: #f8f9fa;
|
| 533 |
-
padding: 20px;
|
| 534 |
-
border-radius: 8px;
|
| 535 |
-
margin-bottom: 20px;
|
| 536 |
-
}
|
| 537 |
-
|
| 538 |
-
.details-section h2 {
|
| 539 |
-
color: #333;
|
| 540 |
-
margin-bottom: 15px;
|
| 541 |
-
font-size: 20px;
|
| 542 |
-
}
|
| 543 |
-
|
| 544 |
-
.detail-row {
|
| 545 |
-
display: flex;
|
| 546 |
-
justify-content: space-between;
|
| 547 |
-
padding: 10px 0;
|
| 548 |
-
border-bottom: 1px solid #ddd;
|
| 549 |
-
}
|
| 550 |
-
|
| 551 |
-
.detail-row:last-child {
|
| 552 |
-
border-bottom: none;
|
| 553 |
-
}
|
| 554 |
-
|
| 555 |
-
.detail-label {
|
| 556 |
-
font-weight: 600;
|
| 557 |
-
color: #555;
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
.detail-value {
|
| 561 |
-
color: #333;
|
| 562 |
-
text-align: right;
|
| 563 |
-
}
|
| 564 |
-
|
| 565 |
-
table {
|
| 566 |
-
width: 100%;
|
| 567 |
-
border-collapse: collapse;
|
| 568 |
-
margin-top: 15px;
|
| 569 |
-
}
|
| 570 |
-
|
| 571 |
-
th,
|
| 572 |
-
td {
|
| 573 |
-
padding: 12px;
|
| 574 |
-
text-align: left;
|
| 575 |
-
border-bottom: 1px solid #ddd;
|
| 576 |
-
}
|
| 577 |
-
|
| 578 |
-
th {
|
| 579 |
-
background-color: #667eea;
|
| 580 |
-
color: white;
|
| 581 |
-
font-weight: 600;
|
| 582 |
-
}
|
| 583 |
-
|
| 584 |
-
.amount {
|
| 585 |
-
text-align: right;
|
| 586 |
-
font-family: 'Courier New', monospace;
|
| 587 |
-
}
|
| 588 |
-
|
| 589 |
-
.total-row {
|
| 590 |
-
font-weight: bold;
|
| 591 |
-
background-color: #f0f0f0;
|
| 592 |
-
}
|
| 593 |
-
|
| 594 |
-
.btn {
|
| 595 |
-
display: inline-block;
|
| 596 |
-
padding: 12px 30px;
|
| 597 |
-
border: none;
|
| 598 |
-
border-radius: 6px;
|
| 599 |
-
font-size: 16px;
|
| 600 |
-
font-weight: 600;
|
| 601 |
-
cursor: pointer;
|
| 602 |
-
text-decoration: none;
|
| 603 |
-
transition: all 0.3s;
|
| 604 |
-
text-align: center;
|
| 605 |
-
}
|
| 606 |
-
|
| 607 |
-
.btn-primary {
|
| 608 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 609 |
-
color: white;
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
.btn-primary:hover {
|
| 613 |
-
transform: translateY(-2px);
|
| 614 |
-
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 615 |
-
}
|
| 616 |
-
|
| 617 |
-
.actions {
|
| 618 |
-
text-align: center;
|
| 619 |
-
margin-top: 30px;
|
| 620 |
-
}
|
| 621 |
-
|
| 622 |
-
.highlight {
|
| 623 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 624 |
-
color: white;
|
| 625 |
-
padding: 15px;
|
| 626 |
-
border-radius: 8px;
|
| 627 |
-
margin-bottom: 20px;
|
| 628 |
-
}
|
| 629 |
-
|
| 630 |
-
.highlight p {
|
| 631 |
-
margin: 5px 0;
|
| 632 |
-
}
|
| 633 |
-
</style>
|
| 634 |
-
</head>
|
| 635 |
-
|
| 636 |
-
<body>
|
| 637 |
-
<div class="container">
|
| 638 |
-
<?php if ($success): ?>
|
| 639 |
-
<div class="success-icon">✓</div>
|
| 640 |
-
<h1>Payment Processed Successfully!</h1>
|
| 641 |
-
<p class="message"><?php echo htmlspecialchars($message); ?></p>
|
| 642 |
-
|
| 643 |
-
<div class="highlight">
|
| 644 |
-
<p><strong>Student:</strong> <?php echo htmlspecialchars($studentName); ?></p>
|
| 645 |
-
<p><strong>Receipt No:</strong> <?php echo htmlspecialchars($paymentDetails['receipt_no']); ?></p>
|
| 646 |
-
<p><strong>Transaction ID:</strong> <?php echo htmlspecialchars($paymentDetails['transaction_id']); ?></p>
|
| 647 |
-
</div>
|
| 648 |
-
|
| 649 |
-
<div class="details-section">
|
| 650 |
-
<h2>Payment Details</h2>
|
| 651 |
-
<div class="detail-row">
|
| 652 |
-
<span class="detail-label">Payment Date:</span>
|
| 653 |
-
<span class="detail-value"><?php echo htmlspecialchars($paymentDetails['payment_date']); ?></span>
|
| 654 |
-
</div>
|
| 655 |
-
<div class="detail-row">
|
| 656 |
-
<span class="detail-label">Teller Number:</span>
|
| 657 |
-
<span class="detail-value"><?php echo htmlspecialchars($paymentDetails['teller_no']); ?></span>
|
| 658 |
-
</div>
|
| 659 |
-
<div class="detail-row">
|
| 660 |
-
<span class="detail-label">Bank Narration:</span>
|
| 661 |
-
<span class="detail-value"><?php echo htmlspecialchars($paymentDetails['teller_name']); ?></span>
|
| 662 |
-
</div>
|
| 663 |
-
<div class="detail-row">
|
| 664 |
-
<span class="detail-label">Total Amount Used:</span>
|
| 665 |
-
<span class="detail-value">₦<?php echo number_format($paymentDetails['total_paid'], 2); ?></span>
|
| 666 |
-
</div>
|
| 667 |
-
<div class="detail-row">
|
| 668 |
-
<span class="detail-label">Remaining Unreconciled on Teller:</span>
|
| 669 |
-
<span
|
| 670 |
-
class="detail-value">₦<?php echo number_format($paymentDetails['remaining_unreconciled'], 2); ?></span>
|
| 671 |
-
</div>
|
| 672 |
-
</div>
|
| 673 |
-
|
| 674 |
-
<div class="details-section">
|
| 675 |
-
<h2>Fees Settled</h2>
|
| 676 |
-
<table>
|
| 677 |
-
<thead>
|
| 678 |
-
<tr>
|
| 679 |
-
<th>Fee Description</th>
|
| 680 |
-
<th width="100">Session</th>
|
| 681 |
-
<th width="80">Term</th>
|
| 682 |
-
<th width="120" class="amount">Amount Paid</th>
|
| 683 |
-
</tr>
|
| 684 |
-
</thead>
|
| 685 |
-
<tbody>
|
| 686 |
-
<?php foreach ($paymentDetails['allocations'] as $allocation): ?>
|
| 687 |
-
<tr>
|
| 688 |
-
<td><?php echo htmlspecialchars($allocation['description']); ?></td>
|
| 689 |
-
<td><?php echo htmlspecialchars($allocation['academic_session']); ?></td>
|
| 690 |
-
<td><?php echo htmlspecialchars($allocation['term_of_session']); ?></td>
|
| 691 |
-
<td class="amount">₦<?php echo number_format($allocation['amount'], 2); ?></td>
|
| 692 |
-
</tr>
|
| 693 |
-
<?php endforeach; ?>
|
| 694 |
-
<tr class="total-row">
|
| 695 |
-
<td colspan="3" style="text-align: right;">Total:</td>
|
| 696 |
-
<td class="amount">₦<?php echo number_format($paymentDetails['total_paid'], 2); ?></td>
|
| 697 |
-
</tr>
|
| 698 |
-
</tbody>
|
| 699 |
-
</table>
|
| 700 |
-
</div>
|
| 701 |
-
|
| 702 |
-
<?php else: ?>
|
| 703 |
-
<div class="error-icon">✗</div>
|
| 704 |
-
<h1>Payment Processing Failed</h1>
|
| 705 |
-
<p class="message" style="color: #dc3545;"><?php echo htmlspecialchars($message); ?></p>
|
| 706 |
-
|
| 707 |
-
<div class="details-section">
|
| 708 |
-
<h2>Error Details</h2>
|
| 709 |
-
<p>The payment could not be processed. No changes were made to the database.</p>
|
| 710 |
-
<p style="margin-top: 10px;"><strong>Error:</strong> <?php echo htmlspecialchars($message); ?></p>
|
| 711 |
-
</div>
|
| 712 |
-
<?php endif; ?>
|
| 713 |
-
|
| 714 |
-
<div class="actions">
|
| 715 |
-
<a href="index.php" class="btn btn-primary">Return to Main Page</a>
|
| 716 |
-
</div>
|
| 717 |
-
</div>
|
| 718 |
-
</body>
|
| 719 |
-
|
| 720 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|