# CLAUDE.md — Radius ISP Billing System

## Role

Always work as a **Senior Laravel Developer**. Write clean, production-quality code following Laravel best practices. Prioritize maintainability, security, and consistency with existing patterns in this codebase. Avoid over-engineering — solve the actual problem at hand.

## Project Overview

**Radius** is a ISP (Internet Service Provider) billing and network management platform built with Laravel 8. It manages the full lifecycle of internet subscribers — from registration and billing to network activation on MikroTik routers via RADIUS protocol.

**Timezone:** `Asia/Dhaka`  
**Locale:** `en`  
**Default URL:** `radius.yetfix.com`

---

## Tech Stack

| Layer | Technology |
|---|---|
| Framework | Laravel 8 (PHP 7.3 / 8.x) |
| Database | MySQL |
| Cache / Session | Redis |
| Queue | Database (Laravel Queue) |
| Real-time | Pusher |
| UI | AdminLTE + Blade templates |
| Datatables | Yajra Datatables + Fractal |
| Excel Export | Maatwebsite Excel |
| PDF | niklasravnsborg/laravel-pdf |
| Permissions | Spatie Laravel Permissions |
| Router API | evilfreelancer/routeros-api-php (MikroTik) |
| Telnet | meklis/telnet |
| Image | intervention/image |

---

## Architecture

### User Roles
The system has multiple roles with distinct access levels:
- **Admin** — full system access
- **Reseller Admin** — manages their own POP/client subset
- **Reseller Technician** — limited reseller access
- **Sub Reseller** — lowest reseller tier
- **Accounts Manager** — billing and payment access
- **Support Manager** — token/support access
- **Marketing User** — lead and customer acquisition

All roles use Spatie `laravel-permission`. Views use `@can('permission-name')` and `globalPermission('feature-flag')` for feature toggles.

### Query Scoping by Role
Key models have role-aware query scopes. Always use these scopes — never raw queries for listing:
- `Client::list()`, `Client::conditionlist()` — filters by reseller/pop for non-admin roles
- `Pop::list()`, `Pop::conditionalList()` — filters POPs by user role
- `Packages::list()`, `Packages::conditionalList()` — filters packages by reseller

### Route Structure
Routes are split across multiple files included from `routes/web.php`:

| File | Purpose |
|---|---|
| `routes/web.php` | Main routes + auth + payment callbacks |
| `routes/store.php` | Inventory management |
| `routes/banking.php` | Banking, deposits, fund transfers |
| `routes/payroll.php` | Employee payroll |
| `routes/report.php` | Reports and analytics |
| `routes/crm.php` | CRM (item lending/returns) |
| `routes/package.php` | Package management |
| `routes/bandwidth.php` | Bandwidth purchase/sale |
| `routes/client.php` | Client utilities |
| `routes/resellerAccount.php` | Reseller account operations |
| `routes/newLineRequest.php` | New connection requests |
| `routes/customerPortal.php` | JWT API of the React customer portal (`/api/customer-portal`) |

All admin routes are under the `auth` + `ensure.user.has.role` middleware group.

---

## Key Models

### `Client` (`clients` table)
Core customer record. Key fields:
- `userid`, `password` — RADIUS/PPPoE credentials
- `pop_id`, `package_id`, `reseller_id`
- `clients_status` — `active` | `inactive` | `closed`
- `client_approval` — `pending` | `approved`
- `expire_date`, `payment_deadline`
- `ip_address`, `mac`, `connection_type`, `isStatic`
- `technical_box_id` — links to TechnicalBox (new)
- `auto_deactive`, `free_day_to_create_customer`, `bandwidth_limit`

### `Clientsinfo` (`clientsinfo` table)
Extended client info (address, location, network position):
- `division`, `district`, `upazila`, `thana`
- `onu_serial`, `olt_no`, `olt_port`
- `map_box_id`, `connected_component_type`, `connected_component_id`

### `Pop` (`pops` table)
Point of Presence — physical network location:
- `popname`, `reseller_id`, `nas_id`
- `billable`, `bill_generate`, `subreseller`
- `logical_issue_responsible_persons`, `physical_team` (new fields)

### `Packages` (`packages` table)
Internet plans:
- `package_name`, `package_rate`, `pool_name`, `profile_name`
- `speed_up`, `speed_down`, `commission`
- `limite_quantity`, `package_bandwidth`, `btrc_package_price`

### `SupportTeam` (`support_teams` table)
Technical support teams:
- `name`, `team_lead_id` (FK → employees)
- `employees_id` — JSON array of employee IDs
- `created_by`

### `TechnicalBox` (`technical_boxes` table)
Network boxes assigned to support teams:
- `name`, `support_team_id` (FK → support_teams)
- `logical_issue_responsible_persons` — JSON array of employee IDs
- `created_by`

### `Employee` (`employees` table)
Staff members:
- `name`, `designation`, `admin_user_id`, `status`
- Related to tokens (assigned tickets), payroll, departments

### `User` (`users` table)
Admin/staff login accounts. Uses `HasRoles` from Spatie.

---

## Service & Helper Classes

### `app/Classes/`
| Class | Purpose |
|---|---|
| `MikrotikService/Mikrotik.php` | MikroTik router API connection |
| `MikrotikService/SyncWithMk.php` | Sync client data to MikroTik |
| `MikrotikService/MikrotikStaticIP.php` | Static IP assignment on router |
| `Accounting/Accounting.php` | RADIUS accounting logic |
| `Accounting/ActiveServers.php` | RADIUS server management |
| `Accounting/UserDisconnect.php` | Disconnect user from network |
| `BillgenerateUpdate.php` | Bill generation orchestration |
| `ExpireCustomerDeactive.php` | Deactivate expired customers |
| `TokenClass.php` | Support ticket processing |
| `SMS/` | SMS notification classes (due, disable, expire) |
| `AuthUser.php` | Auth helper |
| `Notification.php` | Push notification system |

### `app/Services/`
47 service classes. Key ones:

| Service | Purpose |
|---|---|
| `ClientServices.php` | Core client CRUD operations |
| `ClientActiveDeactiveService.php` | Toggle client network status |
| `ClientApproveService.php` | Approve new client applications |
| `GenerateMonthlyBill.php` | Monthly invoice generation |
| `PackageChange.php` | Handle package upgrades/downgrades |
| `BillingCycleChange.php` | Change billing date |
| `CommissionCalculationService.php` | Reseller commission math |
| `RadiusClientSync.php` | Sync with FreeRADIUS database |
| `Sms.php` / `ResellerSms.php` | Send SMS notifications |
| `PaymentGetwayCredentialService.php` | Payment gateway config |
| `ExpirationService.php` | Expiry date calculations |
| `TopmenuService.php` | Dynamic top menu builder |
| `queryCheckForReseller.php` | Role-based query modifier |

---

## Global Settings

Global settings are feature flags and configuration stored in the `global_settings` table, seeded via `database/seeders/GlobalSettingSeeder.php`. Always check via `globalSettings('key')` or the helper before implementing features.

Key setting groups:

**Feature Flags** (enable/disable whole modules):
- `store`, `Bank`, `payroll`, `new-line-request`, `setting`
- `Bandwidth-section`, `manage-map`, `technical_box`, `Box`
- `Portal`, `API Integration`, `CRM`, `Voice broadcast`

**Billing Behaviour**:
- `monthlyBillGenerate`, `dailyBillGenerate`, `auto-deactive`
- `february_add_2_days`, `round_expire_date`, `d2d_after_expire_some_day`
- `bill-payment-sms`, `auto-payment-sms-send`, `only_full_bill_for_local_client`

**Network / MikroTik**:
- `static-ip-address`, `client-mac-binding`, `ignore-mikrotik-check`
- `online-offline-using-api`, `disconnect-with-api`, `multi_router`
- `radius-upload-download`, `disable_if_bandwidth_limit_over`

**Payment Gateways**:
- `bkash-show`, `ssl-commerz-show`, `uddokta-pay-show`, `nagad-checkout-client-payment`
- `upay-payment`, `surjo-pay-show`, `pay-with-eps`, `paystation-pay-show`

**Customer Form Fields** (show/require fields on registration):
- `father-name-required`, `national_id`, `dob`, `area`, `customer_code`
- `onu_serial_required`, `olt_no_require`, `cable_meter_required`
- `dore_picture_required`, `national_id_picture`, `customer_agrement_required`

**Location**:
- `Division`, `District`, `Upazila`, `Thana`, `area-dropdown`

---

## Permissions

607 total permissions managed via `database/seeders/PermissionsSeeder.php`. Key groups:

| Group | Example Permissions |
|---|---|
| Dashboard | `show-dashboard`, `show-dashboard-customer-summary` |
| Client | `active-customer`, `expire-customer`, `disable-customer`, `newjoin`, `onlineUser` |
| Billing | `generate-bill`, `print-bill`, `payment-report`, `billing-report`, `bill-sheet` |
| Token/Support | `new-token`, `assign-token`, `close-token`, `token-search`, `token-report` |
| POP | `pop_index`, `pop_edit`, `pop_update`, `pop_destroy` |
| Package | `package_index`, `package_create`, `package_store` etc. |
| Reseller | `reseller_index`, `reseller_create`, `reseller_store` etc. |
| Employee/HR | `employee_index`, `employee_create`, `employee_store` etc. |
| Technical Box | `technical-box-index` |
| Support Team | `support-team-index` |
| Roles/Perms | `role_index`, `assign-permission`, `assign-role` |
| Inventory | `inventory_index`, `requisition_create`, `requisition_approve` etc. |
| Reports | `monthly-report`, `token-report`, `graph-report`, `nc-report` |
| SMS | `sms_balance`, `sms-log`, `send-batch-sms` |

---

## Payment Gateways

All gateways have feature-flag global settings and dedicated controllers:

| Gateway | Controller | Setting Key |
|---|---|---|
| bKash | `BkashController.php` | `bkash-show` |
| SSL Commerz | `SslCommerzPaymentController.php` | `ssl-commerz-show` |
| Nagad | `NagadIpnController.php` | `nagad-checkout-client-payment` |
| Uddokta Pay | `UddoktapayController.php` | `uddokta-pay-show` |
| Upay | `UpayPaymentController.php` | `upay-payment` |
| EPS | `EpsController.php` | `pay-with-eps` |

---

## SMS Notifications

SMS is sent via multiple configurable gateways. Trigger points:
- New customer creation (`new-user-create-sms`)
- Bill payment (`bill-payment-sms`)
- Token create/close (`token-create-sms`, `token-close-sms`)
- Customer disable (`disable-customer-sms`)
- Expiry reminders (`SendExpiredUserSms` job)

Use `app/Services/Sms.php` for all SMS sending. Resellers have their own SMS gateway via `ResellerSms.php`.

---

## Bill Types

The billing system handles multiple bill types (used in `BillGenerate` and `Billpayment`):
- `new` — new connection fee (OTC)
- `otc` — one-time charge
- `monthly` — regular monthly invoice
- `billing_cycle_change` — charge for changing billing date
- `package_change` — charge for upgrading/downgrading
- `reactive` — reactivation charge

---

## MikroTik / RADIUS Integration

When a client is activated/deactivated/has package changed:
1. `ClientServices.php` or relevant Service class handles business logic
2. `Classes/MikrotikService/Mikrotik.php` connects via RouterOS API
3. `Classes/Accounting/UserDisconnect.php` handles disconnection
4. `Services/RadiusClientSync.php` syncs credentials to FreeRADIUS `radcheck` table
5. `Models/Radcheck.php` stores the RADIUS user credentials

The `ignore-mikrotik-check` global setting can bypass MikroTik operations for testing.

---

## Database Conventions

- **Soft deletes** are used on `clients`, `users`, and most core models — never hard delete these
- `clients` table is the main subscription record; `clientsinfo` holds extended info
- JSON columns: `employees_id` (SupportTeam), `logical_issue_responsible_persons` (TechnicalBox, Pop) — always cast as `array` in models
- Foreign keys use `foreignId()` but are often not constrained at DB level
- The `global_settings` table drives feature toggles — check it before assuming a feature is always available

---

## View Conventions

- All views extend `resources/views/layout/layout.blade.php`
- Sidebar: `resources/views/layout/leftsidebar/admin.blade.php`
- Use `@can('permission-name')` for permission gates in views
- Use `globalPermission('feature-key')` for feature flag checks
- Flash messages use `success_message` and `error_message` session keys
- Datatables (Yajra) are used for all large list views

---

## Development Notes

- When adding a new module, always add: route resource, controller, model, migration, sidebar link, and permissions to `PermissionsSeeder.php`
- New feature flags go in `GlobalSettingSeeder.php`
- The `EditLogHistory` class (`app/Classes/EditLogHistory.php`) should be called on any update to track changes
- `SupportTeam` and `TechnicalBox` are the newest modules (April 2026) — still being integrated into client and POP forms
- The `update` method in `SupportTeamController` is missing a redirect on success — needs fixing

## React Customer Portal

- Behind the `new-customer-portal` global setting. On: `/customer_login`, `customerDashboard` and `customer-payment` redirect to `/customer-portal`; off: the portal and its API answer 404 and the Blade portal stays.
- Source in `resources/js/customer-portal` (built by `webpack.mix.js` like the map app, Tailwind 3 via its own `tailwind.config.js`). Build output in `public/js/customer-portal` and `public/css/customer-portal` is committed, because `deploy.sh` does not run a JS build.
- Auth: `customer` guard (JWT, `App\Models\PortalClient` = `Client` + auth contract). Needs `JWT_SECRET` in `.env`.
- The same API is meant for a mobile app; every endpoint is documented in `docs/customer-portal-api.md` — keep it in step when changing the API.
- Payments reuse the session-based gateway flows: `billing/checkout` returns a one-time `customer-portal/pay/{ticket}` link that sets the legacy session and presses the gateway's button.
- Coming back, the gateways still redirect to Blade pages (`customerDashboard`, the bKash result pages, `client-open-payment/{slug}`). Those pages hand the customer to the portal via `PortalPaymentReturn` with the flashed outcome (`?payment=success|failed&message=`). The quick-pay page only does so for a client the bridge marked, so Quick Payment keeps working.
- Package change and ticket creation live in `CustomerPackageChangeService` / `CustomerTicketService`, shared with the Blade portal.
- "Latest Update Services" news: `CustomerPortalNews`, admin CRUD under Settings → Customer Portal News (`customer-portal-news` permission).


