# One Step ISP Solution — Phase 0 & 1 Setup Guide
### Core Architecture + Central Auth System

---

## যা যা এই ধাপে আছে

- `database/schema.sql` — সম্পূর্ণ ডাটাবেজ স্ট্রাকচার (১৯টি টেবিল: Users/Reseller Hierarchy, RBAC Permissions, Mikrotik Routers, Packages, Customers, Invoices, Payments, Reseller Ledger, Vouchers, Support Tickets, Audit Log ইত্যাদি)
- `database/seed.sql` — Default Package (Lite/Regular/Premium) ও Permission ডেটা
- `config/config.php` — কেন্দ্রীয় কনফিগ (DB, Encryption Key, App Settings)
- `config/database.php` — PDO Singleton কানেকশন
- `includes/crypto.php` — Login Password Hash (bcrypt) + Mikrotik/PPPoE Password Reversible Encryption (AES-256-GCM)
- `includes/auth.php` — কেন্দ্রীয় Login/Session/RBAC/Audit Log সিস্টেম — **এখান থেকেই পুরো সিস্টেমের সব মডিউল Auth চেক করবে**
- `database/install_superadmin.php` — Super Admin অ্যাকাউন্ট তৈরির CLI স্ক্রিপ্ট
- `admin/login.php`, `admin/dashboard.php`, `admin/logout.php` — Ultra-modern Premium থিমের Login পেজ ও Dashboard Shell
- `assets/css/style.css` — সম্পূর্ণ থিম (Dark NOC Control-room aesthetic, Signal-pulse animation)

সব ফাইল লোকাল PHP 8.3 + MariaDB দিয়ে লাইভ টেস্ট করে যাচাই করা হয়েছে (login/wrong-password/session/audit-log/encryption সব কাজ করছে)।

### Phase 2 — Package & Pricing Module (নতুন)
- `admin/packages.php` — সব প্যাকেজের তালিকা (Tier অনুযায়ী সাজানো)
- `admin/package_edit.php` — প্যাকেজ Add/Edit ফর্ম (ভ্যালিডেশনসহ)
- `admin/package_delete.php` — প্যাকেজ ডিলিট (কোনো গ্রাহক ব্যবহার করলে ব্লক করে দেয়, নিষ্ক্রিয় করার পরামর্শ দেয়)
- `admin/reseller_pricing.php` + `admin/reseller_pricing_delete.php` — Reseller-wise কাস্টম প্যাকেজ প্রাইসিং Add/Remove
- `includes/partials/sidebar.php`, `includes/partials/topbar.php` — শেয়ার্ড লেআউট (পরের সব মডিউল এটাই ব্যবহার করবে)
- `includes/functions.php` — Flash message ও ছোট হেল্পার ফাংশন

**Live-tested:** Add → Edit → Delete প্যাকেজ, Reseller Custom Pricing Add → Delete, ভুল ইনপুটে ভ্যালিডেশন এরর, এবং "গ্রাহক ব্যবহার করছে এমন প্যাকেজ ডিলিট ব্লক করা" — সবকিছু আসল Server/DB চালিয়ে যাচাই করা হয়েছে, প্রতিটা অ্যাকশন Audit Log-এ সঠিকভাবে রেকর্ড হচ্ছে।

### Phase 3 — Customer/Subscriber Management (নতুন)
- `admin/customers.php` — গ্রাহক তালিকা: সার্চ (নাম/ফোন/কোড/PPPoE ID), স্ট্যাটাস ফিল্টার, পেজিনেশন (৫০০০ গ্রাহক স্কেলের জন্য প্রস্তুত)
- `admin/customer_edit.php` — Add/Edit ফর্ম: বেসিক তথ্য, সংযোগ তথ্য (PPPoE/Hotspot/Static), MAC/Caller-ID Bind, বিলিং সাইকেল, Package/Router/Reseller অ্যাসাইনমেন্ট, পূর্ণ ভ্যালিডেশন
- `admin/customer_delete.php` — ডিলিট, তবে বিলিং হিস্টোরি (Invoice) থাকলে ব্লক করে "চলে গেছেন" স্ট্যাটাসে রাখার পরামর্শ দেয়
- Customer Code Auto-generation: `CUS-000001` ফরম্যাটে
- PPPoE পাসওয়ার্ড AES-256-GCM দিয়ে এনক্রিপ্ট হয়ে সেভ হয় (Mikrotik API কল করতে দরকার হবে বলে one-way hash নয়)
- Package পরিবর্তন করলেই তা `customer_package_history` টেবিলে স্বয়ংক্রিয়ভাবে লগ হয়

**Live-tested:** নতুন গ্রাহক তৈরি, ফোন নম্বর/MAC ভ্যালিডেশন, ডুপ্লিকেট ফোন প্রতিরোধ, Package পরিবর্তনে History লগ, PPPoE পাসওয়ার্ড এনক্রিপ্ট/ডিক্রিপ্ট রাউন্ড-ট্রিপ, সার্চ ও পেজিনেশন (২৫+ ডামি গ্রাহক দিয়ে), এবং Invoice-সংযুক্ত গ্রাহক ডিলিট ব্লক — সবকিছু বাস্তব Server+DB চালিয়ে টেস্ট করা হয়েছে। টেস্টে দুটো বাগ (undefined array key warning ও Reseller/Router খালি রাখলে ভুল FK ভ্যালু বসে যাওয়া, এবং সার্চ কোয়েরিতে ডুপ্লিকেট named placeholder) ধরা পড়ে সাথে সাথেই ঠিক করা হয়েছে।

### Phase 4 — Mikrotik Integration, Multi-Router (নতুন)
- `includes/mikrotik_api.php` — RouterOS বাইনারি API প্রোটোকলের সম্পূর্ণ বাস্তবায়ন (length-prefixed word encoding, plain + MD5 challenge login, sentence read/write, `/system/identity/print`-এর মতো যেকোনো command চালানোর `talk()` মেথড)
- `admin/routers.php`, `admin/router_edit.php`, `admin/router_delete.php` — Router Add/Edit/Remove (একাধিক Router সাপোর্ট, প্রতিটির API পাসওয়ার্ড AES-256-GCM দিয়ে এনক্রিপ্টেড)
- `admin/router_test.php` — Router-এ সরাসরি লাইভ সংযোগ টেস্ট করে (Connect → Login → Identity Fetch), ফলাফল অনুযায়ী DB-তে Online/Offline স্ট্যাটাস আপডেট করে

**যেভাবে টেস্ট করা হয়েছে (এই sandbox-এ আসল Mikrotik হার্ডওয়্যার নেই বলে):**
1. প্রোটোকল লেয়ার (word length encoding/decoding) — ১৩টি boundary value (0 থেকে ২৬৮ মিলিয়ন পর্যন্ত) দিয়ে ইউনিট টেস্ট, সব পাস
2. একটা ছোট Mock RouterOS TCP Server বানিয়ে (`/login`, `/system/identity/print` রেসপন্স দেয়) আসল client-কে তার সাথে real socket-এ কানেক্ট করানো হয়েছে — Connect → Login → Command → Parse, পুরো চেইন কাজ করেছে
3. আসল ওয়েব অ্যাপ (`router_test.php`) দিয়ে সেই Mock Server-এ কানেক্ট করে সফল রেজাল্ট দেখানো এবং DB status আপডেট নিশ্চিত করা হয়েছে
4. অস্তিত্বহীন Router-এ কানেক্ট করার চেষ্টা করে graceful error handling যাচাই করা হয়েছে (Timeout/Connection Refused সঠিকভাবে ধরা পড়ছে, অ্যাপ ক্র্যাশ করছে না)
5. Router ডিলিট করার সময় গ্রাহক সংযুক্ত থাকলে ব্লক হওয়া নিশ্চিত করা হয়েছে

**⚠️ গুরুত্বপূর্ণ নোট:** আসল MikroTik রাউটারের সাথে সংযোগ, PPPoE Secret Sync (Push/Pull), এবং Live Bandwidth Monitor — এগুলো আসল হার্ডওয়্যারে (অথবা GNS3/CHR ভার্চুয়াল রাউটারে) আপনাকে একবার টেস্ট করে নিতে হবে, কারণ এই ডেভেলপমেন্ট এনভায়রনমেন্টে কোনো real Mikrotik ডিভাইস উপলব্ধ ছিল না। প্রোটোকল বাস্তবায়ন সঠিক MikroTik ডকুমেন্টেশন অনুসরণ করে, তবে বাস্তব ডিভাইসের quirks (RouterOS ভার্সনভেদে পার্থক্য) থাকতে পারে।

### Phase 5 — Billing & Invoice Engine (নতুন)
- `database/generate_monthly_bills.php` — মাসিক বিল জেনারেশন ইঞ্জিন (Cron-ready + Reusable ফাংশন)। Reseller-wise কাস্টম প্রাইসিং স্বয়ংক্রিয়ভাবে বিবেচনা করে, ডুপ্লিকেট বিল তৈরি হয় না
- `admin/invoices.php` — মাস/স্ট্যাটাস ফিল্টার, পেজিনেশন, কালেকশন/বকেয়া সামারি কার্ড, এক ক্লিকে "এই মাসের বিল জেনারেট করুন" বাটন, Overdue Invoice স্বয়ংক্রিয়ভাবে চিহ্নিত হয়
- `admin/invoice_view.php` — প্রিন্টযোগ্য ইনভয়েস (Browser Print দিয়ে PDF হিসেবেও সেভ করা যাবে), পেমেন্ট হিস্টোরিসহ
- `admin/payments.php` — পেমেন্ট রেকর্ড করা (Cash/bKash/Nagad/Rocket/Bank/Gateway), আংশিক পেমেন্ট সাপোর্ট করে, বকেয়ার চেয়ে বেশি পেমেন্ট নেওয়া আটকায়
- `admin/customer_throttle.php` — Dynamic Throttling: বকেয়া গ্রাহকের PPPoE প্রোফাইল Router-এ সরাসরি বদলে দেয় (সংযোগ পুরোপুরি বন্ধ না করে সীমিত-গতিতে নামিয়ে দেয়)

**Cron সেটআপ (প্রতিদিন রাত ১২টায় স্বয়ংক্রিয়ভাবে বিল তৈরি):**
```bash
0 0 * * * /usr/bin/php /path/to/isp-billing/database/generate_monthly_bills.php >> /path/to/isp-billing/logs/billing_cron.log 2>&1
```

**Dynamic Throttle ব্যবহারের আগে করণীয়:** আপনার প্রতিটা Mikrotik Router-এ `Throttled-FUP` নামে একটা সীমিত-স্পিড PPP Profile আগে থেকে তৈরি রাখুন (যেমন 1 Mbps)। "থ্রটল" চাপলে গ্রাহকের PPPoE Secret-এর প্রোফাইল এই নামে বদলে যাবে; "পুনরুদ্ধার" চাপলে গ্রাহকের বরাদ্দকৃত প্যাকেজের নামে (তাই Router-এ প্রতিটা Package-এর নামে হুবহু একটা প্রোফাইল থাকা প্রয়োজন)।

**যেভাবে টেস্ট করা হয়েছে:**
- বিল জেনারেশন: সঠিক গ্রাহকদের জন্য সঠিক মূল্যে বিল তৈরি, Package-বিহীন/Inactive গ্রাহক স্বয়ংক্রিয়ভাবে বাদ, দ্বিতীয়বার রান করলে ডুপ্লিকেট না হওয়া, Reseller Custom Pricing সঠিকভাবে প্রয়োগ হওয়া — সব বাস্তব DB-তে যাচাই করা হয়েছে
- Overdue Auto-marking, Invoice View/Print পেজ লোড, আংশিক→সম্পূর্ণ পেমেন্ট ফ্লো, বকেয়ার চেয়ে বেশি/সম্পূর্ণ-পরিশোধিত ইনভয়েসে পুনরায় পেমেন্ট আটকানো — সব লাইভ টেস্ট করা হয়েছে
- Dynamic Throttle: Mock RouterOS PPP Server বানিয়ে আসল socket-এ Throttle ও Restore দুটো অ্যাকশনই টেস্ট করা হয়েছে

**টেস্টের সময় ২টা বাগ ধরা পড়ে ঠিক করা হয়েছে:**
1. `invoice_view.php`-তে Flash Message দেখানোর কোড ছিল না — তাই Payment পেজ থেকে redirect হওয়া এরর মেসেজ (যেমন "ইতিমধ্যে সম্পূর্ণ পরিশোধিত") নীরবে হারিয়ে যাচ্ছিল
2. **RouterOS API-তে একটা গুরুত্বপূর্ণ প্রোটোকল বাগ:** Query/Filter শব্দ (যেমন `?name=value`) ভুলভাবে `=?name=value` হিসেবে এনকোড হচ্ছিল, যা আসল Mikrotik রাউটার রিজেক্ট করে দিত (Mock সার্ভার ফরম্যাট চেক করত না বলে প্রথমে ধরা পড়েনি)। মক সার্ভারের raw log পরীক্ষা করেই এটা ধরা পড়ে এবং ঠিক করা হয়েছে — এখন আসল প্রোটোকল স্পেসিফিকেশন অনুযায়ী সঠিকভাবে এনকোড হচ্ছে।

---

## সার্ভারে বসানোর ধাপ

### ১. Database তৈরি
```bash
mysql -u root -p < database/schema.sql
mysql -u root -p < database/seed.sql
```

### ২. Limited-privilege App User তৈরি (root দিয়ে সরাসরি অ্যাপ চালাবেন না)
```sql
CREATE USER 'isp_app'@'localhost' IDENTIFIED BY 'একটা-শক্তিশালী-পাসওয়ার্ড';
GRANT SELECT, INSERT, UPDATE, DELETE ON isp_billing.* TO 'isp_app'@'localhost';
FLUSH PRIVILEGES;
```

### ৩. `config/config.php` আপডেট
- `DB_PASS` → উপরের পাসওয়ার্ডটা বসান
- `APP_ENCRYPTION_KEY` → একটা র‍্যান্ডম ৩২-বাইট কী জেনারেট করে বসান:
  ```bash
  php -r "echo bin2hex(random_bytes(32));"
  ```
- `APP_URL`, `APP_ENV` (প্রোডাকশনে `production` রাখুন)

### ৪. Super Admin অ্যাকাউন্ট তৈরি
```bash
php database/install_superadmin.php admin "Xi0nPC@#"
```
এতে `admin` / `Xi0nPC@#` — আপনার দেওয়া credential-ই সেট হয়ে যাবে, তবে DB-তে শুধু bcrypt hash আকারে থাকবে, প্লেইনটেক্সট কোথাও লেখা থাকবে না।

### ৫. Web Server Document Root
Document root **এই ফোল্ডারের ভেতরে** সেট করুন, তবে `config/`, `database/`, `includes/` — এই তিনটা ফোল্ডার সরাসরি ব্রাউজার থেকে অ্যাক্সেসযোগ্য না হওয়া উচিত (Apache/Nginx-এ `.htaccess`/`location` ব্লক দিয়ে আটকাতে হবে — এটা Phase 13-এ Security Hardening ধাপে পূর্ণাঙ্গভাবে সেট করে দেব)।

### ৬. Login টেস্ট
`https://your-domain.com/admin/login.php` এ গিয়ে `admin` / `Xi0nPC@#` দিয়ে লগইন করুন।

---

## ✅ Phase 6 — Reseller Module (Multi-level) — সম্পন্ন

নতুন যা যোগ হয়েছে:

- **`admin/resellers.php`** — Reseller/Sub-reseller তালিকা, সার্চ, লেভেল ফিল্টার, সামারি কার্ড (মোট ওয়ালেট ব্যালেন্স ইত্যাদি)
- **`admin/reseller_edit.php`** — নতুন Reseller/Sub-reseller তৈরি বা এডিট (username, password, commission_percent, parent_id দিয়ে হায়ারার্কি)
- **`admin/reseller_view.php`** — একজন Reseller-এর প্রোফাইল, তার Sub-reseller-দের তালিকা, তার গ্রাহকদের তালিকা, এবং লেজার হিস্টোরি
- **`admin/reseller_ledger_add.php`** — ম্যানুয়াল Debit/Credit এন্ট্রি (যেমন ব্যাংক টপ-আপ, নগদ উত্তোলন)
- **`admin/reseller_delete.php`** — নিরাপদ ডিলিট (গ্রাহক/Sub-reseller/লেজার হিস্টোরি থাকলে মুছতে দেয় না, সাসপেন্ড করার পরামর্শ দেয়)
- **`includes/reseller_helpers.php`** — `creditResellerLedger()`, `debitResellerLedger()`, এবং মূল ইঞ্জিন `applyResellerCommission()`

### Multi-level কমিশন যেভাবে কাজ করে
কোনো গ্রাহকের বিল পরিশোধ হলে (`admin/payments.php`), সংশ্লিষ্ট গ্রাহকের সরাসরি Reseller (সাধারণত Sub-reseller) তার নিজের `commission_percent` অনুযায়ী কমিশন পায়। এরপর হায়ারার্কি বেয়ে উপরে ওঠা হয় (`parent_id` ধরে) — প্রতিটি Parent শুধু তার নিজের হার এবং ঠিক নিচের লেভেলের হারের **পার্থক্যটুকু** (override মার্জিন) কমিশন হিসেবে পায়, পুরো কমিশন আবার নতুন করে পায় না। প্রতিটি ক্রেডিট `reseller_ledger`-এ লগ হয় এবং `users.balance` সাথে সাথে আপডেট হয়।

উদাহরণ: Sub-reseller-এর কমিশন ৫%, তার Parent Reseller-এর কমিশন ৮%। ১০০০ টাকা বিল পরিশোধ হলে — Sub-reseller পায় ৳৫০ (৫%), Parent পায় শুধু ৳৩০ (৮%−৫%=৩% override), মোট বিতরণ ৳৮০ — ডাবল কমিশন হয় না।

### পরবর্তী ধাপ

**Phase 7 — Payment Gateway** — এখন Reseller ওয়ালেট ও কমিশন সিস্টেম কাজ করছে, তাই এবার Local/International Payment Gateway ইন্টিগ্রেশন এবং Online Payment History/Auto-reconciliation যুক্ত করা যুক্তিসঙ্গত ধাপ।

---

## ✅ Phase 7 — Payment Gateway Integration — সম্পন্ন

নতুন যা যোগ হয়েছে:

- **`database/migration_phase7.sql`** (schema.sql/seed.sql-এও মার্জ করা আছে) — `payment_gateways`, `gateway_transactions` টেবিল + `invoices.pay_token` কলাম
- **`includes/gateways/GatewayInterface.php`** — যেকোনো নতুন গেটওয়ে (Upay, Rocket, Stripe...) এই ইন্টারফেস implement করলেই সিস্টেমে যুক্ত হয়ে যাবে
- **`includes/gateways/BkashGateway.php`** — bKash Tokenized Checkout (grant token → create → execute/query)
- **`includes/gateways/SslcommerzGateway.php`** — কার্ড, নগদ, রকেট, ব্যাংক ট্রান্সফার একসাথে কভার করে (aggregator)
- **`includes/gateway_helpers.php`** — `loadGatewayDriver()`, `ensureInvoicePayToken()`, এবং কেন্দ্রীয় `reconcileTransaction()` (callback/IPN/cron — তিন জায়গা থেকেই কল হয়, ডাবল-ক্রেডিট অসম্ভব — row-level `FOR UPDATE` লক দিয়ে সুরক্ষিত)
- **`public/pay.php`, `public/pay_init.php`, `public/gateway_callback.php`, `public/gateway_ipn.php`** — গ্রাহক লগইন ছাড়াই `pay_token` দিয়ে বিল দেখে অনলাইনে পরিশোধ করতে পারবে; status সবসময় গেটওয়ের API থেকে সরাসরি (server-to-server) যাচাই করে নিশ্চিত হওয়া হয়, callback-এর query param কখনো বিশ্বাস করা হয় না
- **`admin/gateway_settings.php`** — bKash/SSLCommerz credential (AES-256-GCM এনক্রিপ্টেড), sandbox/live মোড, enable/disable টগল — ডিফল্টে শুধু **super_admin**-এর অ্যাক্সেস আছে (permission `gateway.settings`)
- **`admin/gateway_transactions.php`** — সব অনলাইন লেনদেনের লগ, ফিল্টার, এবং ম্যানুয়াল "পুনরায় যাচাই" বাটন
- **`admin/gateway_reconcile.php`** — একটা নির্দিষ্ট লেনদেন সাথে সাথে যাচাই করার অ্যাকশন হ্যান্ডলার
- **`database/reconcile_gateway_payments.php`** — Cron auto-reconciliation স্ক্রিপ্ট (নিচে দেখুন)
- **`admin/invoice_view.php`** আপডেট — unpaid ইনভয়েসে "🔗 পেমেন্ট লিংক কপি করুন" বাটন যোগ হয়েছে

### কীভাবে কাজ করে (End-to-end flow)

1. Admin `invoice_view.php`-এ গিয়ে অনলাইন পেমেন্ট লিংক কপি করে গ্রাহককে SMS/WhatsApp করে দেয় (অথবা Phase 9-এ Customer Portal থেকেও এই লিংক দেখা যাবে)
2. গ্রাহক লিংকে ক্লিক করে `public/pay.php`-তে বিলের বিস্তারিত দেখে, একটা গেটওয়ে বেছে নেয়
3. `pay_init.php` একটা `gateway_transactions` সারি বানায় (status=`initiated`) এবং গ্রাহককে গেটওয়ের পেমেন্ট পেজে পাঠিয়ে দেয়
4. পেমেন্ট শেষে গেটওয়ে গ্রাহককে `gateway_callback.php`-এ ফেরত পাঠায় — এখানে **merchant_txn_id** ধরে গেটওয়ের API-কে সরাসরি প্রশ্ন করা হয় (query/execute), browser param বিশ্বাস করা হয় না
5. status `completed` পাওয়া গেলে `reconcileTransaction()` — `payments` টেবিলে এন্ট্রি বসায়, ইনভয়েস status আপডেট করে, reseller কমিশন ক্রেডিট করে, audit log লেখে — সব একটা DB transaction-এর ভেতরে (atomic)
6. SSLCommerz-এর জন্য আলাদাভাবে `gateway_ipn.php`ও কাজ করে (গ্রাহক ব্রাউজার বন্ধ করে দিলেও সার্ভার-টু-সার্ভার নোটিফিকেশন আসে)
7. `reconcile_gateway_payments.php` cron প্রতি ৫ মিনিটে যেসব transaction ২ মিনিটের বেশি পুরনো কিন্তু এখনো `initiated`/`pending`, সেগুলো আবার যাচাই করে — callback/IPN দুটোই মিস হয়ে গেলে এটাই শেষ সেফটি-নেট

### Setup — গেটওয়ে চালু করা

1. **সেটিংসে ঢুকুন:** `admin/gateway_settings.php` (শুধু super_admin দেখতে পাবে)
2. **bKash Sandbox টেস্ট করতে চাইলে:** bKash Merchant Developer Portal থেকে sandbox `username`, `password`, `app_key`, `app_secret` নিয়ে বসান, মোড রাখুন `sandbox`
3. **SSLCommerz Sandbox:** [sslcommerz.com](https://sslcommerz.com)-এ sandbox অ্যাকাউন্ট খুলে `store_id`/`store_passwd` নিয়ে বসান
4. Callback URL স্বয়ংক্রিয়ভাবেই `config.php`-এর `APP_URL` থেকে বসে যায় — শুধু নিশ্চিত করুন `APP_URL` সঠিক ডোমেইনে সেট করা আছে
5. Cron যোগ করুন (crontab -e):
   ```
   */5 * * * * php /path/to/isp-billing/database/reconcile_gateway_payments.php >> /path/to/isp-billing/logs/reconcile.log 2>&1
   ```
6. টেস্ট: একটা unpaid ইনভয়েসে গিয়ে "পেমেন্ট লিংক কপি করুন" চাপুন, লিংকটা নতুন ট্যাবে খুলুন, sandbox দিয়ে পেমেন্ট সম্পন্ন করুন, তারপর `admin/gateway_transactions.php`-এ গিয়ে status `completed` ও ইনভয়েস `paid` হয়েছে কিনা দেখুন

### নিরাপত্তা নোট

- গেটওয়ে credential কখনো প্লেইনটেক্সটে DB-তে থাকে না — `Crypto::encrypt()`/`decrypt()` (AES-256-GCM) দিয়ে এনক্রিপ্টেড থাকে, ঠিক PPPoE পাসওয়ার্ডের মতোই
- Payment status **কখনোই** ব্রাউজার redirect-এর query param থেকে নেওয়া হয় না — সবসময় গেটওয়ের সার্ভারে সরাসরি API কল করে verify করা হয় (man-in-the-middle বা URL কারসাজি করে ভুয়া "success" দেখানো সম্ভব না)
- SSLCommerz verify-তে amount mismatch পাওয়া গেলে সেই লেনদেন স্বয়ংক্রিয়ভাবে reject হয়ে যায় (জালিয়াতি প্রতিরোধ)
- একটা transaction দুবার reconcile হওয়া থেকে আটকাতে DB-level row lock (`FOR UPDATE`) + `reconciled` ফ্ল্যাগ — একসাথে callback ও cron চললেও ডাবল পেমেন্ট বসবে না

### পরবর্তী ধাপ

**Phase 8 — Notification System** — এখন অনলাইন পেমেন্ট কাজ করছে, তাই SMS/WhatsApp/Email Auto-send (Bill Reminder, Payment Confirmation, OTP) এবং Telegram Admin Alert যুক্ত করা পরবর্তী যুক্তিসঙ্গত ধাপ।

---

## ✅ Phase 8 — Notification System — সম্পন্ন

নতুন যা যোগ হয়েছে:

- **`database/migration_phase8.sql`** (schema.sql-এও মার্জ করা আছে) — `notification_channels`, `notification_templates` (৭টা ইভেন্টের ডিফল্ট বাংলা টেমপ্লেট প্রি-সিড করা), `notification_queue`, `otp_codes`
- **`includes/notifications/NotificationDriverInterface.php`** — নতুন কোনো SMS/WhatsApp/Email/Telegram প্রোভাইডার ভবিষ্যতে যোগ করতে শুধু এই ইন্টারফেস implement করলেই হবে
- **`includes/notifications/SmsGatewayDriver.php`** — Generic HTTP SMS ড্রাইভার, URL টেমপ্লেট (`{api_key} {sender_id} {phone} {message}`) দিয়ে কনফিগারযোগ্য — তাই BulkSMSBD, MiMSMS, AdnSMS, Alpha SMS ইত্যাদি বাংলাদেশি প্রায় সব প্রোভাইডারের সাথেই কাজ করবে, কোনো ভেন্ডরে হার্ডকোড করা হয়নি
- **`includes/notifications/WhatsAppCloudDriver.php`** — Meta WhatsApp Business Cloud API (phone_number_id + access_token)
- **`includes/notifications/SmtpEmailDriver.php`** — কোনো external লাইব্রেরি (PHPMailer) ছাড়াই raw socket দিয়ে SMTP প্রোটোকল (STARTTLS/SSL, AUTH LOGIN) বাস্তবায়ন করা হয়েছে — প্রজেক্টে যেহেতু কোনো composer dependency নেই, বাকি কোডবেসের ধাঁচেই raw বাস্তবায়ন
- **`includes/notifications/TelegramBotDriver.php`** — Telegram Bot API, একাধিক admin chat_id-এ broadcast করতে পারে
- **`includes/notification_helpers.php`** — `loadNotificationDriver()`, `queueNotification()`, `processNotificationQueue()`, `queueTelegramAdminAlert()`, এবং পুনঃব্যবহারযোগ্য OTP ইঞ্জিন — `generateAndSendOtp()` / `verifyOtp()` (Phase 9-এর Customer Portal লগইনে সরাসরি ব্যবহার করা যাবে)
- **`admin/notification_settings.php`** — SMS/WhatsApp/Email/Telegram credential (AES-256-GCM এনক্রিপ্টেড), চ্যানেল enable/disable — ডিফল্টে শুধু **super_admin**-এর অ্যাক্সেস (permission `notifications.settings`, gateway.settings-এর মতোই প্যাটার্ন)
- **`admin/notification_templates.php`** ও **`admin/notification_template_edit.php`** — প্রতিটা ইভেন্ট+চ্যানেলের বার্তা এডিট করা যায়, `{{placeholder}}` সিনট্যাক্স সাপোর্ট করে
- **`admin/notification_logs.php`** ও **`admin/notification_resend.php`** — সব পাঠানো/ব্যর্থ/অপেক্ষমাণ বার্তার লগ, ফিল্টার, এবং ম্যানুয়াল "পুনরায় পাঠান" বাটন
- **`admin/notification_test.php`** — যেকোনো চ্যানেলে সরাসরি (queue ছাড়াই) একটা টেস্ট বার্তা পাঠিয়ে সেটআপ যাচাই করা যায়
- **`database/send_bill_reminders.php`** — Auto Bill Reminder cron: মেয়াদ পূর্তির ৩ দিন আগে, মেয়াদ পূর্তির দিনে, এবং মেয়াদোত্তীর্ণ হলে প্রতি ৩ দিন পরপর (সর্বোচ্চ ৫ বার) — একই দিনে ডাবল-পাঠানো ঠেকাতে বিল্ট-ইন dedup চেক আছে
- **`database/process_notification_queue.php`** — Queue Dispatcher cron: `notification_queue`-এ জমা হওয়া pending বার্তা প্রকৃতপক্ষে পাঠায় (ওয়েব রিকোয়েস্ট কখনো ধীরগতির SMS/Email API-এর জন্য ব্লক হয় না)
- **হুক যুক্ত হয়েছে:** নতুন গ্রাহক তৈরি (`customer_edit.php`) → টেলিগ্রাম অ্যাডমিন অ্যালার্ট; ম্যানুয়াল পেমেন্ট (`payments.php`) → গ্রাহক কনফার্মেশন + টেলিগ্রাম অ্যালার্ট; অনলাইন গেটওয়ে পেমেন্ট (`gateway_helpers.php::reconcileTransaction()`) → গ্রাহক কনফার্মেশন + টেলিগ্রাম অ্যালার্ট

### কীভাবে কাজ করে (End-to-end flow)

1. Admin `notification_settings.php`-এ গিয়ে যেসব চ্যানেল ব্যবহার করবেন সেগুলোর credential বসিয়ে সক্রিয় করেন (SMS/WhatsApp/Email/Telegram — সবগুলো লাগবে এমন নয়, যেকোনো একটা দিয়েও শুরু করা যায়)
2. `notification_templates.php`-এ গিয়ে বার্তার ভাষা/ফরম্যাট প্রয়োজনমতো কাস্টমাইজ করা যায়
3. কোনো ইভেন্ট ঘটলে (বিল রিমাইন্ডার cron, পেমেন্ট রেকর্ড, নতুন গ্রাহক) — কোড সরাসরি SMS/Email পাঠায় না, বরং `queueNotification()` দিয়ে `notification_queue`-তে একটা সারি বসায় (status=`pending`)
4. `process_notification_queue.php` cron (প্রতি ১-২ মিনিটে) সেই pending সারিগুলো তুলে নিয়ে সংশ্লিষ্ট ড্রাইভার দিয়ে প্রকৃতপক্ষে পাঠায়, status আপডেট করে (`sent`/`failed`), এবং ব্যর্থ হলে `max_attempts` পর্যন্ত পরের রানে আবার চেষ্টা করে
5. `send_bill_reminders.php` cron (দিনে একবার) unpaid/overdue ইনভয়েস স্ক্যান করে উপযুক্ত রিমাইন্ডার queue করে — প্রতিটা রিমাইন্ডারে `public/pay.php`-এর pay-link সহ থাকে (Phase 7-এর token সিস্টেম পুনঃব্যবহার করে)
6. `notification_logs.php`-এ সব বার্তার হিস্ট্রি দেখা যায়, ব্যর্থ হলে এক ক্লিকে "পুনরায় পাঠান"

### Setup — নোটিফিকেশন চালু করা

1. **সেটিংসে ঢুকুন:** `admin/notification_settings.php` (শুধু super_admin দেখতে পাবে)
2. **SMS:** আপনার SMS রিসেলারের ডকুমেন্টেশন থেকে HTTP API URL টেমপ্লেট, API key, sender ID নিন এবং বসান (উদাহরণ ফরম্যাট সেটিংস পেজেই দেখানো আছে)
3. **WhatsApp:** Meta Business Manager থেকে WhatsApp Cloud API-এর `phone_number_id` ও `access_token` নিন
4. **Email:** আপনার SMTP প্রোভাইডারের host/port/username/password বসান (Gmail হলে App Password ব্যবহার করুন, সাধারণ পাসওয়ার্ড কাজ করবে না)
5. **Telegram:** [@BotFather](https://t.me/BotFather)-এ গিয়ে `/newbot` দিয়ে একটা বট বানান, bot token নিন; নিজের chat_id জানতে বটকে একটা মেসেজ দিয়ে [@userinfobot](https://t.me/userinfobot) ব্যবহার করুন — একাধিক অ্যাডমিনের chat_id কমা দিয়ে বসানো যাবে
6. প্রতিটা চ্যানেল বসানোর পর "টেস্ট বার্তা পাঠান" (`notification_test.php`) দিয়ে যাচাই করুন
7. Cron যোগ করুন (crontab -e):
   ```
   */2 * * * * php /path/to/isp-billing/database/process_notification_queue.php >> /path/to/isp-billing/logs/notifications.log 2>&1
   0 9 * * * php /path/to/isp-billing/database/send_bill_reminders.php >> /path/to/isp-billing/logs/reminders.log 2>&1
   ```

### নিরাপত্তা নোট

- সব API key/token/SMTP পাসওয়ার্ড `Crypto::encrypt()`/`decrypt()` (AES-256-GCM) দিয়ে এনক্রিপ্টেড থাকে, ঠিক Payment Gateway credential-এর মতোই — প্লেইনটেক্সটে কোথাও থাকে না
- OTP কোড DB-তে কখনো প্লেইনটেক্সটে থাকে না — শুধু bcrypt hash (`password_hash`) থাকে, লগইন পাসওয়ার্ডের মতোই যাচাই হয় `password_verify()` দিয়ে
- প্রতিটা OTP-তে `max_attempts` লিমিট আছে — বারবার ভুল কোড দিলে সেই OTP আর কখনো verify হবে না, নতুন করে চাইতে হবে
- Bill Reminder-এ dedup চেক আছে (একই ইনভয়েসে একই দিনে দুইবার পাঠানো হবে না) এবং Overdue Reminder-এর একটা hard cap আছে (`OVERDUE_MAX_REMINDERS = 5`) — গ্রাহককে স্প্যাম করার সুযোগ নেই

### পরবর্তী ধাপ

**Phase 9 — Customer Self-service Portal** — এখন Notification + OTP ইঞ্জিন প্রস্তুত, তাই Customer Portal (লগইন OTP দিয়ে, বিল দেখা/পরিশোধ, প্যাকেজ আপগ্রেড রিকোয়েস্ট, স্পিড টেস্ট, সাপোর্ট টিকেট) যুক্ত করা পরবর্তী যুক্তিসঙ্গত ধাপ — `generateAndSendOtp()`/`verifyOtp()` ফাংশন দুটো ইতিমধ্যেই এই কাজের জন্য প্রস্তুত করা আছে।

---

## ✅ Phase 9 — Customer Self-service Portal — সম্পন্ন

নতুন যা যোগ হয়েছে (`/portal/` — সম্পূর্ণ আলাদা, লগইনবিহীন নয়, কিন্তু admin/-এর Auth থেকে স্বতন্ত্র):

- **`database/migration_phase9.sql`** (schema.sql-এও মার্জ করা আছে) — `package_change_requests`, `support_tickets`, `ticket_messages`, `speed_test_results` + Phase 9-এর নতুন নোটিফিকেশন টেমপ্লেট
- **`includes/portal_auth.php`** — `PortalAuth` ক্লাস: ফোন নম্বর + OTP দিয়ে লগইন (পাসওয়ার্ড লাগে না), Phase 8-এর `generateAndSendOtp()`/`verifyOtp()` পুনঃব্যবহার করে। আলাদা session name (`customer_portal_session`) ব্যবহার করে, তাই একই ব্রাউজারে admin লগইন থাকা অবস্থায়ও পোর্টাল আলাদাভাবে কাজ করে। একই ফোন নম্বরে একাধিক অ্যাকাউন্ট (পরিবার/একাধিক লাইন) থাকলে account picker দেখায়।
- **`portal/login.php`**, **`portal/verify.php`**, **`portal/logout.php`** — OTP লগইন ফ্লো
- **`portal/dashboard.php`** — বর্তমান প্যাকেজ, বকেয়া বিলের সারাংশ, সর্বশেষ ইনভয়েস, খোলা টিকেট সংখ্যা
- **`portal/invoices.php`** — সম্পূর্ণ বিলিং হিস্ট্রি, প্রতিটা অপরিশোধিত বিলে সরাসরি `public/pay.php`-এর পে-লিংক (Phase 7 টোকেন সিস্টেম পুনঃব্যবহার)
- **`portal/package_upgrade.php`** — সক্রিয় প্যাকেজ তালিকা থেকে আপগ্রেড/ডাউনগ্রেড রিকোয়েস্ট জমা দেওয়া যায়; জমা দিলে টেলিগ্রামে অ্যাডমিনদের অ্যালার্ট যায়
- **`portal/speed_test.php`** + **`speed_test_download.php`** / **`speed_test_upload.php`** / **`speed_test_save.php`** — সম্পূর্ণ self-hosted রিয়েল-টাইম স্পিড টেস্ট (পিং/ডাউনলোড/আপলোড) — কোনো external speed-test সার্ভিসের উপর নির্ভর করে না, তাই ফলাফল ISP-এর নিজস্ব সার্ভারের সাথে সংযোগের প্রকৃত গতি দেখায়; প্রতিটা ফলাফল `speed_test_results`-এ সংরক্ষিত হয়
- **`portal/tickets.php`** ও **`portal/ticket_view.php`** — সাপোর্ট টিকেট তৈরি ও থ্রেডেড রিপ্লাই (Live Chat-এর সরল সংস্করণ)
- **Admin পাশে নতুন পেজ:**
  - **`admin/package_change_requests.php`** — pending রিকোয়েস্ট অনুমোদন/প্রত্যাখ্যান করলে গ্রাহকের `package_id` সরাসরি আপডেট হয় এবং SMS-এ জানানো হয় (Mikrotik PPPoE profile সিঙ্ক করতে চাইলে `customer_edit.php`-এর Router Sync অংশ আলাদাভাবে ব্যবহার করুন)
  - **`admin/tickets.php`** ও **`admin/ticket_view.php`** — সব টিকেট দেখা, রিপ্লাই দেওয়া, স্ট্যাটাস পরিবর্তন (রিপ্লাই দিলে "খোলা" থেকে "প্রক্রিয়াধীন"-এ auto-move হয়) — রিপ্লাই করলে গ্রাহককে SMS/Email নোটিফিকেশন যায়
  - `permissions` টেবিলে `admin` রোলের জন্য `tickets.manage` যোগ করা হয়েছে (support role-এর আগে থেকেই আছে); `package_change_requests.php` বিদ্যমান `customers.manage` পারমিশন ব্যবহার করে

### নিরাপত্তা নোট

- পোর্টাল লগইনে পাসওয়ার্ড নেই — শুধু রেজিস্টার্ড ফোন নম্বরে পাঠানো OTP, যেটা bcrypt hash করে রাখা হয় (Phase 8-এর মতোই)
- একই নম্বরে বারবার OTP চাইলে ৬০ সেকেন্ডের rate-limit আছে (SMS স্প্যাম/খরচ ঠেকাতে)
- পোর্টাল সেশন অ্যাডমিন সেশন থেকে সম্পূর্ণ আলাদা কুকিতে থাকে (`customer_portal_session` বনাম ডিফল্ট `PHPSESSID`)
- স্পিড টেস্ট এন্ডপয়েন্ট (`speed_test_download.php`) সাইজ ১-৫০ MB-এর মধ্যে সীমাবদ্ধ, যাতে সার্ভার রিসোর্স অপব্যবহার না হয়
- গ্রাহক নিজের ছাড়া অন্য কারো ইনভয়েস/টিকেট/প্যাকেজ রিকোয়েস্ট দেখতে বা এডিট করতে পারবে না — প্রতিটা কোয়েরি `customer_id = :id` দিয়ে স্কোপড

### পরবর্তী ধাপ

**Phase 10 — Reports & Compliance** — BTRC DIS রিপোর্ট জেনারেটর (নির্ধারিত ফরম্যাটে এক-ক্লিক এক্সপোর্ট) এবং দৈনিক/মাসিক আয়, কালেকশন, ক্লায়েন্ট গ্রোথ রিপোর্ট — এখন কোর কাস্টমার/বিলিং/পেমেন্ট/টিকেট ডেটা সবই প্রস্তুত, তাই রিপোর্টিং লেয়ার তৈরির উপযুক্ত সময়।

---

## ✅ Phase 10 — Reports & Compliance — সম্পন্ন

নতুন যা যোগ হয়েছে:

- **`includes/report_helpers.php`** — `resolveReportScope()` একটাই কেন্দ্রীয় ফাংশন যা ঠিক করে কে কতটুকু ডেটা দেখবে: super_admin/admin (`reports.view` পারমিশন) পুরো সিস্টেম দেখে, reseller/sub_reseller (`reports.view_own`) শুধু নিজের `customers.reseller_id`-এর আওতাধীন ডেটা দেখে — এবং কোনো পারমিশনই না থাকলে (যেমন `support` রোল) সরাসরি 403 দেয়। `streamCsv()` — যেকোনো রিপোর্ট এক লাইনে UTF-8 BOM সহ CSV এক্সপোর্ট করতে পারে (Excel-এ বাংলা ঠিকভাবে দেখায়)
- **`admin/reports.php`** — রিপোর্ট হাব: এই মাসের কালেকশন/নতুন গ্রাহক/মেয়াদোত্তীর্ণ ইনভয়েসের কুইক KPI + ৪টা রিপোর্টের কার্ড। Reseller লগইনে Income ও BTRC DIS কার্ড দুটো স্বয়ংক্রিয়ভাবে লুকানো থাকে (এগুলো কোম্পানির নিয়ন্ত্রক/আর্থিক তথ্য)
- **`admin/report_collection.php`** — দৈনিক Payment Collection, পেমেন্ট-মেথড ব্রেকডাউন (cash/bKash/নগদ/রকেট/ব্যাংক/গেটওয়ে), ডেট-রেঞ্জ ফিল্টার, CSV এক্সপোর্ট, প্রিন্ট ভিউ
- **`admin/report_income.php`** *(admin/super_admin only)* — গত ৩/৬/১২/২৪ মাসের Income vs Expense ট্রেন্ড, খরচের ক্যাটাগরি ব্রেকডাউন, নিট মুনাফা
- **`admin/report_growth.php`** — মাসওয়ারি নতুন গ্রাহক vs Left/Churn, বর্তমান স্ট্যাটাস ব্রেকডাউন, শীর্ষ ১০ এরিয়া — reseller-স্কোপড
- **`admin/report_btrc_dis.php`** *(admin/super_admin only)* — পূর্ণ সাবস্ক্রাইবার রোস্টার (Subscriber ID, নাম, ফোন, NID, ঠিকানা, এরিয়া, সংযোগ ধরন, প্যাকেজ, সংযোগ তারিখ, স্ট্যাটাস) + অ্যাগ্রিগেট সামারি (সক্রিয়/স্থগিত/চলে যাওয়া সংখ্যা, প্যাকেজ-টিয়ার ব্রেকডাউন, এই কোয়ার্টারে নতুন সংযোগ) — উভয়ই CSV এক্সপোর্ট করা যায়। super_admin ইনলাইন ফর্ম থেকে অপারেটরের নাম/BTRC লাইসেন্স নম্বর/ক্লাস/NOC এরিয়া বসাতে পারে (`system_settings`-এ সংরক্ষিত হয়, রিপোর্টের প্রিন্ট হেডারে দেখায়)
- **`database/migration_phase10.sql`** (seed.sql-এও মার্জ করা আছে) — কোনো নতুন টেবিল লাগেনি (Phase 0-এর `system_settings` key-value টেবিলই যথেষ্ট), শুধু `isp_license_no`/`isp_license_class`/`isp_noc_area` key তিনটা সিড করা হয়েছে

### PDF এক্সপোর্ট কীভাবে কাজ করে

আলাদা কোনো PDF লাইব্রেরি (TCPDF/mPDF) যোগ না করে, ইনভয়েস মডিউলে (Phase 5) যে প্যাটার্ন আগে থেকেই ছিল সেটাই পুনঃব্যবহার করা হয়েছে — প্রতিটা রিপোর্ট পেজে `@media print` CSS (sidebar/topbar/ফিল্টার লুকিয়ে দেয়) + "🖨️ প্রিন্ট" বাটন আছে, যেটা ব্রাউজারের নিজস্ব "Print → Save as PDF" ব্যবহার করে ক্লিন, প্রফেশনাল-লুকিং PDF বানিয়ে দেয়। এতে কোনো Composer/PDF ডিপেন্ডেন্সি ছাড়াই কাজ চলে যায়।

### ⚠️ BTRC DIS নিয়ে গুরুত্বপূর্ণ নোট

BTRC-এর নতুন Data Information System (dis.btrc.gov.bd) লাইসেন্সধারীর লগইন দিয়েই দেখা যায়, এবং সাবস্ক্রাইবার তথ্য সাধারণত **মাসিক/কোয়ার্টারলি অ্যাগ্রিগেট সংখ্যা** হিসেবে জমা নেওয়া হয় (প্রতি সাবস্ক্রাইবারের রেকর্ড আলাদা করে না) — এই পাবলিক তথ্যের বাইরে সঠিক কলাম-লেভেল টেমপ্লেট BTRC আলাদাভাবে জানায় না, তাই এই মডিউল **অনুমান করে কোনো নির্দিষ্ট টেমপ্লেট হার্ডকোড করেনি**। বরং দুটো এক্সপোর্ট দিয়েছে —
1. সম্পূর্ণ সাবস্ক্রাইবার রোস্টার (ইতিমধ্যে সিস্টেমে থাকা সব প্রাসঙ্গিক তথ্য নিয়ে)
2. অ্যাগ্রিগেট সামারি (মোট/সক্রিয়/নতুন সংখ্যা, টিয়ার-ভিত্তিক)

DIS পোর্টালে জমা দেওয়ার আগে আপনার লাইসেন্স লগইনে গিয়ে সর্বশেষ অনুমোদিত কলাম-টেমপ্লেটের সাথে এই এক্সপোর্ট দুটো মিলিয়ে নিন — প্রয়োজনে CSV-এর কলাম অর্ডার/নাম মেলানো সহজ (একটাই `streamCsv()` কল পরিবর্তন করলেই হবে)।

### টেস্ট করে দেখা হয়েছে

এই ফেজ সরবরাহের আগে সরাসরি একটা লোকাল MySQL/MariaDB ইনস্ট্যান্সে পুরো schema.sql + seed.sql ইমপোর্ট করে, ৬০ জন টেস্ট গ্রাহক + ১২ মাসের ইনভয়েস/পেমেন্ট/খরচ ডেটা বসিয়ে, PHP built-in সার্ভারে সব কটা রিপোর্ট পেজ ও CSV এক্সপোর্ট সুপার-অ্যাডমিন এবং reseller — দুই রোল দিয়েই যাচাই করা হয়েছে:
- সব পেজ HTTP 200, কোনো PHP warning/notice/fatal error ছাড়াই
- Reseller লগইনে শুধু নিজের ২০ জন গ্রাহকের ডেটা দেখায় (৬০ জনের মধ্যে), Income/BTRC পেজে সরাসরি URL দিয়ে ঢোকার চেষ্টা করলে 403
- `support` রোল (যার কোনো reports পারমিশন নেই) reports হাবেই ঢুকতে পারে না — 403
- BTRC লাইসেন্স ইনফো ফর্ম সেভ করলে ঠিকভাবে `system_settings`-এ বসে এবং রিপোর্টে ফিরে আসে

### পরবর্তী ধাপ

Phase 11 (NOC Dashboard) নিচে যোগ করা হলো, এরপর Phase 12।

---

## ✅ Phase 11 — NOC Dashboard & Monitoring — সম্পন্ন

সব Router (Phase 4), Customer, ও Billing ডেটা আগে থেকেই প্রস্তুত ছিল — এই ফেজে সেগুলো দিয়ে একটা লাইভ অপারেশনাল ড্যাশবোর্ড বানানো হয়েছে।

নতুন যা যোগ হয়েছে:

- **`database/migration_phase11.sql`** — দুইটা নতুন টেবিল: `router_status_log` (প্রতিটা পোলের ফলাফল — আপ/ডাউন + লেটেন্সি — টাইম-সিরিজ হিসেবে জমা রাখে) এবং `router_outages` (অনলাইন→অফলাইন ট্রানজিশন থেকে outage window ট্র্যাক করে — কখন শুরু, কখন শেষ, কতক্ষণ স্থায়ী ছিল, অ্যালার্ট পাঠানো হয়েছে কিনা)
- **`database/noc_poll_routers.php`** — Cron entry point; প্রতি কয়েক মিনিটে সব Mikrotik Router-এ কানেক্ট করে আপ/ডাউন চেক করে, `router_status_log`-এ এন্ট্রি বসায়, এবং স্ট্যাটাস বদলালে (আপ→ডাউন বা ডাউন→আপ) `router_outages`-এ নতুন outage খোলে/বন্ধ করে
- **`includes/noc_helpers.php`** — সব হেল্পার ফাংশন: `getActiveOutages()`, `getRecentOutages()`, রাউটার আপটাইম % ক্যালকুলেশন, এবং ব্যবসায়িক অ্যানালিটিক্স — `getRevenueTrend()` (মাসওয়ারি কালেকশন), `getChurnTrend()` (নতুন বনাম চলে যাওয়া গ্রাহক), `getAreaGrowth()` (এরিয়া-ভিত্তিক শীর্ষ গ্রাহক সংখ্যা)। Reseller লগইন করলে শুধু নিজের স্কোপের ডেটা দেখে, super_admin/admin পুরো সিস্টেম দেখে
- **`admin/noc_dashboard.php`** — সব Router-এর লাইভ স্ট্যাটাস গ্রিড (অনলাইন/অফলাইন/অজানা, লেটেন্সি, ৭-দিনের আপটাইম %), চলমান ও সাম্প্রতিক outage টেবিল, এবং Revenue/Churn/Area-wise Growth চার্ট। ঐচ্ছিক অটো-রিফ্রেশ টগল (৬০ সেকেন্ড)
- **সাইডবারে নতুন লিংক:** "🛰️ NOC ড্যাশবোর্ড" — Overview গ্রুপে, Dashboard-এর ঠিক নিচে

### Setup

`database/noc_poll_routers.php` cron দিয়ে প্রতি ৫ মিনিটে চালাতে হবে:
```
*/5 * * * * php /path/to/isp-billing/database/noc_poll_routers.php >> /path/to/isp-billing/logs/noc_poll.log 2>&1
```
পোল ইন্টারভাল `system_settings`-এর `noc_poll_interval_minutes` key দিয়ে ড্যাশবোর্ডে দেখানো হয় (cron শিডিউলের সাথে মিলিয়ে সেট করুন)।

### পরবর্তী ধাপ

---

## ✅ Phase 12 — Multi-device Sync & Auto Cloud Backup — সম্পন্ন

*(রোডম্যাপের ক্রম অনুযায়ী Phase 11-এর আগেই আপনার অনুরোধে এই ফেজ করা হয়েছিল — Phase 11 (NOC Dashboard) উপরে যোগ করা হয়েছে।)*

নতুন যা যোগ হয়েছে:

- **`includes/backup_helpers.php`** — ব্যাকআপ ইঞ্জিনের কেন্দ্রীয় ফাইল। `dumpDatabaseToFile()` কোনো `mysqldump` শেল কমান্ড ছাড়াই বিশুদ্ধ PDO দিয়ে পুরো DB (schema + data, chunked ৫০০ রো করে — মেমরি-সচেতন) ডাম্প করে, তাই শেয়ার্ড হোস্টিং-এ যেখানে `shell_exec()`/`exec()` বন্ধ থাকে সেখানেও কাজ করবে। `zipConfigToFile()` config/ ফোল্ডার জিপ করে। `performBackup()` — দুটোই চালিয়ে `backup_log`-এ এন্ট্রি বসায়, retention অনুযায়ী পুরনো ফাইল ছাঁটাই করে (`cleanupOldBackups()`), এবং সেটিংসে রিমোট FTP চালু থাকলে `pushBackupsToRemote()` দিয়ে অফসাইট কপি পাঠায়
- **`database/backup_run.php`** — Cron entry point, `auto_backup_enabled` বন্ধ থাকলে স্কিপ করে
- **`admin/backups.php`** — ব্যাকআপ সেটিংস (সময়, রিটেনশন দিন, রিমোট FTP ক্রেডেনশিয়াল — AES-256-GCM এনক্রিপ্টেড), "⚡ এখনই ব্যাকআপ নিন" ম্যানুয়াল বাটন, এবং সম্পূর্ণ হিস্ট্রি টেবিল (ফাইল, উৎস cron/manual, গন্তব্য local/remote, সাইজ, স্ট্যাটাস, ডাউনলোড লিংক)
- **`admin/backup_run_now.php`** ও **`admin/backup_download.php`** — ম্যানুয়াল ট্রিগার হ্যান্ডলার এবং path-traversal-গার্ডেড সিকিউর ডাউনলোড (শুধু `backups.manage` পারমিশনধারীরা)
- **`includes/session_helpers.php`** — `user_sessions` টেবিল (Phase 0-তেই ডিজাইন করা ছিল, এই ফেজে প্রকৃতপক্ষে ব্যবহৃত হলো)। `listSessionsForUser()`, `listAllActiveSessions()`, `revokeSessionById()`, `revokeAllOtherSessions()`, `cleanupExpiredSessions()`, এবং `deviceLabelFromUserAgent()` (User-Agent থেকে সহজবোধ্য "Windows · Chrome" জাতীয় লেবেল বানায়)
- **`includes/auth.php` আপডেট** — `Auth::check()` এখন শুধু `$_SESSION['user_id']` আছে কিনা দেখে না, বরং প্রতি রিকোয়েস্টে `user_sessions`-এ টোকেনটা এখনো বৈধ (মুছে ফেলা/মেয়াদোত্তীর্ণ হয়নি) কিনা যাচাই করে — sliding expiry-ও আপডেট করে। ফলে অন্য ডিভাইস থেকে "লগআউট করুন" চাপলে সেই ডিভাইসের **পরের রিকোয়েস্টেই** সেশন বাতিল হয়ে যায়, কোনো পোলিং/WebSocket ছাড়াই
- **`admin/sessions.php`** — নিজের সক্রিয় ডিভাইস তালিকা (বর্তমান ডিভাইস হাইলাইট করা), এক ক্লিকে যেকোনো ডিভাইস থেকে রিমোট লগআউট, "এই ডিভাইস বাদে বাকি সব থেকে লগআউট" বাটন — এবং super_admin/admin-এর জন্য সিস্টেম-ওয়াইড ভিউ (সব ইউজারের সব সক্রিয় সেশন, নাম/রোল/IP/ডিভাইস সহ)
- **`admin/session_revoke.php`** — রিভোক হ্যান্ডলার; নিজের সেশন যে কেউ রিভোক করতে পারে, অন্য ইউজারের সেশন রিভোক করতে `sessions.manage_all` পারমিশন লাগে
- **`database/migration_phase12.sql`** (schema.sql/seed.sql-এও merge করা আছে) — `backup_log`-এ `triggered_by`/`triggered_by_user`/`destination`/`note` কলাম যোগ, নতুন `system_settings` key (`backup_retention_days`, `backup_remote_*` ইত্যাদি — Phase 0-এর `auto_backup_enabled`/`auto_backup_time`/`multi_device_sync_enabled` placeholder-গুলো এখন আসল কাজে লাগলো), এবং নতুন permission key (`backups.manage`, `sessions.manage_all` — ডিফল্টে admin+ পাবে)
- **সাইডবারে নতুন লিংক:** "🔗 ডিভাইস ও সেশন" এবং "☁️ ব্যাকআপ" — System গ্রুপে

### কীভাবে কাজ করে (End-to-end flow)

**ব্যাকআপ:**
1. `admin/backups.php`-এ গিয়ে অটো ব্যাকআপ সময়/রিটেনশন সেট করুন, চাইলে রিমোট FTP-ও কনফিগার করুন
2. `backup_run.php` cron (দিনে একবার) DB dump + config zip করে `BACKUP_PATH`-এ (`/backups`) রাখে, `backup_log`-এ এন্ট্রি বসায়, রিটেনশনের চেয়ে পুরনো ফাইল মুছে দেয়
3. প্রয়োজনে "⚡ এখনই ব্যাকআপ নিন" চেপে ম্যানুয়ালি যেকোনো সময় ব্যাকআপ নেওয়া যায়
4. হিস্ট্রি টেবিল থেকে যেকোনো পুরনো ব্যাকআপ ডাউনলোড করা যায়; সমস্যা হলে `note` কলামে কারণ দেখা যায়

**মাল্টি-ডিভাইস সিঙ্ক:**
1. Admin যেকোনো ডিভাইস থেকে লগইন করলেই `user_sessions`-এ একটা এন্ট্রি বসে (device/IP/token সহ)
2. `admin/sessions.php`-এ গিয়ে সব সক্রিয় ডিভাইস দেখা যায়
3. কোনো ডিভাইস হারিয়ে গেলে/সন্দেহজনক মনে হলে সেটার পাশের "🔒 লগআউট করুন" চাপলেই — সেই ডিভাইসের পরের যেকোনো পেজ-রিকোয়েস্টে সাথে সাথে সেশন invalid হয়ে লগইন পেজে পাঠিয়ে দেয়
4. super_admin/admin চাইলে সিস্টেমের সব ইউজারের সব ডিভাইস দেখতে ও প্রয়োজনে জোরপূর্বক লগআউট করাতে পারবেন (যেমন কোনো স্টাফ চাকরি ছাড়ার পর)

### Setup

1. Cron যোগ করুন (crontab -e):
   ```
   0 3 * * * php /path/to/isp-billing/database/backup_run.php >> /path/to/isp-billing/logs/backup_cron.log 2>&1
   ```
2. নিশ্চিত করুন `backups/` ফোল্ডারে PHP-এর write পারমিশন আছে (প্রথম রানেই অটো তৈরি হয়ে যায়, তবে parent ফোল্ডারের পারমিশন ঠিক থাকতে হবে)
3. রিমোট অফসাইট কপি চাইলে `admin/backups.php`-এ FTP হোস্ট/ইউজার/পাসওয়ার্ড/পাথ বসিয়ে "রিমোট FTP কপি চালু রাখুন" টিক দিন (PHP-তে `ftp` এক্সটেনশন সক্রিয় থাকা লাগবে)
4. Config zip ব্যাকআপের জন্য `php-zip` এক্সটেনশন সক্রিয় থাকা আবশ্যক

### নিরাপত্তা নোট

- ব্যাকআপ ফাইলে DB ক্রেডেনশিয়াল ও সব গ্রাহক/বিলিং ডেটা প্লেইনটেক্সটে থাকে (এনক্রিপ্টেড কলামগুলো ছাড়া) — তাই `backups/` ফোল্ডার ওয়েব-রুটের বাইরে বা `.htaccess` দিয়ে সুরক্ষিত রাখা জরুরি, এবং শুধু `backups.manage` পারমিশনধারীরাই ডাউনলোড করতে পারবেন (path-traversal গার্ড সহ)
- FTP পাসওয়ার্ড বাকি সব ক্রেডেনশিয়ালের মতোই AES-256-GCM (`Crypto::encrypt()`) দিয়ে এনক্রিপ্টেড থাকে
- `Auth::check()`-এর DB-ভ্যালিডেশন মানে কোনো সেশন রিভোক হলে সেটা তাৎক্ষণিক কার্যকর হয় — attacker-এর কোনো সেশন হাইজ্যাক হলেও admin এক ক্লিকে সেটা বন্ধ করে দিতে পারবেন
- সেশন টেবিলে sliding expiry আছে — সক্রিয় থাকলে মেয়াদ বাড়ে, নিষ্ক্রিয় থাকলে `SESSION_LIFETIME_MINUTES` পর এমনিতেই মেয়াদোত্তীর্ণ হয়ে যায়

### টেস্ট করে দেখা হয়েছে

- একটা লোকাল MariaDB ইনস্ট্যান্সে fresh install (schema.sql + seed.sql) এবং upgrade path (আগের স্কিমায় `migration_phase12.sql` রান করে) — দুটোই যাচাই করা হয়েছে, কোনো এরর ছাড়া
- `performBackup()` সরাসরি রান করে DB dump + config zip তৈরি হয়েছে, `backup_log`-এ সঠিক এন্ট্রি বসেছে
- তৈরি হওয়া `.sql` ডাম্প একটা খালি ডাটাবেজে restore করে যাচাই করা হয়েছে — সবগুলো টেবিল (৩০টা) ও প্রতিটা টেবিলের row count হুবহু মূল ডাটাবেজের সাথে মিলেছে (round-trip সঠিক)

### পরবর্তী ধাপ

Phase 11 (NOC Dashboard) উপরে যোগ করা হয়েছে। এরপর **Phase 13 — Final Polish & Security Hardening**, নিচে দেখুন।

---

## ✅ Phase 13 — Final Polish & Security Hardening — সম্পন্ন

### ⚠️ এই ফেজ শুরুর আগে যা পাওয়া গেছে

Phase 12 zip ডেলিভারির সময় **Phase 11-এর পুরো NOC Dashboard ফিচার হারিয়ে গিয়েছিল** — `admin/noc_dashboard.php`, `includes/noc_helpers.php`, `database/noc_poll_routers.php`, `database/migration_phase11.sql`, এমনকি সাইডবারের লিংকও মুছে গিয়েছিল। এই ফেজে সেগুলো আগে পুনরুদ্ধার করা হয়েছে (উপরে "Phase 11" সেকশন দেখুন), তারপর নিচের হার্ডেনিং কাজ করা হয়েছে।

### CSRF (Cross-Site Request Forgery) সুরক্ষা — নতুন

আগের কোনো ফেজেই CSRF টোকেন ছিল না — প্রতিটা POST ফর্ম এবং delete/revoke/throttle-এর মতো GET action link এতদিন CSRF আক্রমণের জন্য উন্মুক্ত ছিল (আক্রমণকারী লগইন করা অ্যাডমিনকে একটা সাজানো পেজে আনতে পারলে তার অজান্তেই গ্রাহক ডিলিট/রাউটার ডিলিট/সেশন রিভোকের মতো কাজ করিয়ে নিতে পারত)।

- **`includes/csrf.php`** (নতুন) — কেন্দ্রীয় CSRF হেল্পার:
  - `csrf_token()` — session-ভিত্তিক টোকেন (না থাকলে `random_bytes(32)` দিয়ে তৈরি করে)
  - `csrf_field()` — ফর্মে বসানোর hidden input
  - `csrf_verify()` — POST হ্যান্ডলারের শুরুতে কল করে, mismatch হলে 403
  - `csrf_token_qs()` / `csrf_verify_get()` — delete/revoke/resend-এর মতো GET action link-এর জন্য (link-এ `&t=...` প্যারামিটার হিসেবে বসে)
- `includes/auth.php` ও `includes/portal_auth.php` — উভয়েই এখন `csrf.php` অটো-লোড করে, তাই admin ও portal-এর প্রায় প্রতিটা পেজেই ফাংশনগুলো এমনিতেই পাওয়া যায়
- **২২টা POST ফর্মে** টোকেন ফিল্ড + verify যোগ হয়েছে: login (admin + portal), customer/router/reseller/package edit, payments, invoice generate, reseller ledger, reseller pricing, notification settings/template/test, gateway settings, backups settings, ticket reply, portal ticket/package-upgrade/OTP verify, public bill-pay
- **১২টা ধ্বংসাত্মক GET action link** (delete/revoke/resend/reconcile/throttle/manual-backup) এখন `t=` টোকেন যাচাই করে — টোকেন ছাড়া বা ভুল টোকেন দিয়ে সরাসরি URL হিট করলে 403

### Audit Log Viewer — নতুন (`admin/audit_log.php`)

`audit_logs` টেবিলে Phase 1 থেকেই প্রতিটা create/update/delete/login সিস্টেম-জুড়ে জমা হয়ে আসছিল (`Auth::logAudit()`-এর মাধ্যমে), কিন্তু সেটা দেখার কোনো UI ছিল না — write-only ছিল। এখন:
- শুধু `super_admin` দেখতে পারবে
- মডিউল, অ্যাকশন, ইউজার, তারিখ-রেঞ্জ দিয়ে ফিল্টার করা যায়
- প্রতিটা এন্ট্রিতে কে করেছে, কী করেছে, কোন রেকর্ডে, কোন IP থেকে, এবং আগে/পরের JSON snapshot (collapsible) দেখা যায়
- পেজিনেটেড (৪০/পেজ)

### সাইডবার পরিষ্কার করা হয়েছে

সাইডবারে তিনটা লিংক (`vouchers.php`, `settings.php`, `audit_log.php`) এমন পেজের দিকে নির্দেশ করছিল যেগুলো আসলে কখনো তৈরিই হয়নি — ক্লিক করলে 404। `audit_log.php` এখন বাস্তবে তৈরি করা হয়েছে (উপরে দেখুন)। `vouchers.php` ও `settings.php` — এই দুটো ফিচার রোডম্যাপের কোনো ফেজেই এখনো implement করা হয়নি (শুধু ডাটাবেজ টেবিল/স্কিমা আগে থেকে আছে), তাই আপাতত ডেড লিংক দুটো সাইডবার থেকে সরানো হয়েছে যাতে ব্যবহারকারী ভাঙা পেজে না পড়েন। প্রয়োজনে এই দুটো একটা পরবর্তী ফেজ হিসেবে আলাদাভাবে বানানো যেতে পারে।

### অন্যান্য হার্ডেনিং

- `Auth`-এর brute-force lockout ও audit IP লগিং এখন শুধু trusted proxy সেট থাকলেই `X-Forwarded-For` হেডার বিশ্বাস করে (আগে যেকোনো ক্লায়েন্টের পাঠানো IP সরাসরি বিশ্বাস করা হতো, ফলে audit log-এ IP স্পুফ করা সম্ভব ছিল)
- `php -l` দিয়ে পুরো কোডবেসের প্রতিটা `.php` ফাইল সিনট্যাক্স-লিন্ট করে যাচাই করা হয়েছে — কোনো ফাইলে parse error নেই

### ⚠️ আপনার সরাসরি action দরকার এমন একটা পুরনো ইস্যু

`config/config.php`-এ একটা বাস্তব-সদৃশ ডাটাবেজ পাসওয়ার্ড (`DB_PASS`) সোর্স কোডে প্লেইনটেক্সটে হার্ডকোড করা আছে (আগের কোনো ফেজ থেকেই)। এটা:
- Git-এ কমিট থাকলে যেকোনো রিপো অ্যাক্সেসকারীর কাছে উন্মুক্ত
- প্রতিদিনের auto-backup zip-এও (`zipConfigToFile()`) কপি হয়ে যায়

**সুপারিশ:** এই পাসওয়ার্ড এখনই রোটেট (পরিবর্তন) করুন, এবং production-এ `config/config.php`-কে ওয়েব-রুটের বাইরে সরিয়ে বা `.env` (গিট-ইগনোরড) থেকে লোড করার ব্যবস্থা করুন — এই রিপোর কাঠামো পরিবর্তন না করে এখনই এটা নিরাপদে অটোমেট করা যায়নি বলে ম্যানুয়ালি ফ্ল্যাগ করা হলো।

### পরবর্তী ধাপ

রোডম্যাপের মূল ১৪টা ফেজ (Phase 0–13) এখন সম্পূর্ণ। ভবিষ্যতে চাইলে যা যোগ করা যায়: Vouchers মডিউল, একটা কেন্দ্রীয় general Settings পেজ, এবং RBAC permission ম্যাট্রিক্স এডিট করার UI (বর্তমানে `permissions` টেবিল সরাসরি DB থেকে ম্যানেজ করতে হয়)।




