<div align="center">

# 💍 Expense Tracker

**Rasel & Rupa-র বিয়ের খরচের হিসাব**

এক পেজের অ্যাপ · বাংলা লেবেল · টোস্ট বার্তা · প্রিন্ট-রেডি শিট · বাংলা PDF রিপোর্ট

<br>

![Laravel](https://img.shields.io/badge/Laravel-10-FF2D20?style=flat-square&logo=laravel&logoColor=white)
![Vue](https://img.shields.io/badge/Vue-3-4FC08D?style=flat-square&logo=vuedotjs&logoColor=white)
![Vite](https://img.shields.io/badge/Vite-5-646CFF?style=flat-square&logo=vite&logoColor=white)
![PHP](https://img.shields.io/badge/PHP-8.1+-777BB4?style=flat-square&logo=php&logoColor=white)
![MySQL](https://img.shields.io/badge/MySQL-Aiven-4479A1?style=flat-square&logo=mysql&logoColor=white)
![mPDF](https://img.shields.io/badge/mPDF-8.3-B5121B?style=flat-square)
![Tests](https://img.shields.io/badge/tests-60%20passed-2ea043?style=flat-square&logo=phpunit&logoColor=white)

</div>

---

## 📋 সূচি

<table>
<tr>
<td>

- [✨ এক নজরে](#-এক-নজরে)
- [🚀 চালু করা](#-চালু-করা)
- [🔐 লগইন আর অনুমতি](#-লগইন-আর-অনুমতি)
- [🧭 SPA কীভাবে সাজানো](#-spa-কীভাবে-সাজানো)

</td>
<td>

- [🔔 টোস্ট বার্তা](#-টোস্ট-বার্তা)
- [🎨 ডিজাইন](#-ডিজাইন)
- [🧱 গঠন](#-গঠন)
- [🏷️ ব্যানার](#-ব্যানার) · [📄 PDF](#-pdf-রিপোর্ট-mpdf)
- [🧪 টেস্ট](#-টেস্ট) · [🗄️ ডেটাবেজ](#-ডেটাবেজ)

</td>
</tr>
</table>

---

## ✨ এক নজরে

পুরো অ্যাপ একটাই পেজ — যোগ, এডিট, ডিলিট, ফিল্টার, কোনো কাজেই পেজ রিলোড হয় না; প্রতিটা
কাজের ফলাফল কোনায় **টোস্ট বার্তা** হয়ে জানায়।

| | কাজ | কীভাবে |
|:--:| --- | --- |
| ➕ | **খরচ যোগ** | উপরে **নতুন খরচ** (ওয়েব) বা **+** ভাসমান বাটন (মোবাইল) |
| ✏️ | **এডিট / ডিলিট** | প্রতি রো বা কার্ডে বাটন; ডিলিটে কনফার্ম ডায়ালগ |
| 🏷️ | **নতুন ক্যাটাগরি** | ফর্মের ক্যাটাগরি ঘরে সরাসরি টাইপ করলেই তৈরি হয়ে যায় |
| 🔍 | **খোঁজা** | বিবরণ, মন্তব্য বা ক্যাটাগরি দিয়ে — টাইপ থামলেই নিজে থেকে খোঁজে |
| 🎚️ | **ফিল্টার** | ক্যাটাগরি, পেমেন্ট মাধ্যম, তারিখের রেঞ্জ — বাছলেই সাথে সাথে চলে |
| 📊 | **হিসাব** | মোট, এন্ট্রি সংখ্যা, গড়, সবচেয়ে বড় খরচ + ক্যাটাগরি/পেমেন্ট ভাগ |
| 🖨️ | **প্রিন্ট** | `/print` — ব্রাউজার থেকে শুধু শিটটাই ছাপে |
| 📄 | **PDF রিপোর্ট** | `/report` — mPDF দিয়ে তৈরি; `?download=1` দিলে ডাউনলোড হয় |
| 📥 | **Excel / CSV** | `/export` — UTF-8 BOM সহ, তাই Excel-এ বাংলা ঠিক দেখায় |
| 🌗 | **থিম** | লাইট/ডার্ক টগল, `localStorage`-এ মনে রাখে |

**এক কোডবেসে দুই ভিউ:**

| | ব্রেকপয়েন্ট | কী দেখায় |
|:--:| --- | --- |
| 🖥️ | ৯০১px+ | মূল স্প্রেডশিটের মতো বর্ডারযুক্ত টেবিল, নিচে `মোট` রো |
| 📱 | ৯০০px বা কম | কার্ড লিস্ট, নিচে নীল `মোট` বার, কর্নারে ভাসমান `+` বাটন |

> [!TIP]
> ফিল্টার চালু থাকলে টাইলে **ফিল্টার করা মোট** দেখায়, নিচে সর্বমোট — আর প্রিন্ট, PDF
> আর এক্সপোর্টও ঠিক সেই ফিল্টারই মেনে চলে।

---

## 🚀 চালু করা

**প্রথমবার সেটআপ**

```bash
composer install
npm install
npm run build                                     # Vue বান্ডল → public/build

php artisan migrate                               # categories + expenses + permission টেবিল
php artisan db:seed --class=RolePermissionSeeder   # রোল, পারমিশন আর দুইটা অ্যাকাউন্ট
php artisan db:seed --class=TrackerSeeder          # মূল শিটের ১৫টি রো (মোট ২৮,৫৮৫)
```

সিডার বারবার চালালেও ডুপ্লিকেট হবে না (`description` + `amount` মিলিয়ে চেক করে)।

**এরপর**

| | কোথায় | কমান্ড / URL |
|:--:| --- | --- |
| 🟢 | WAMP | `http://localhost/tracker/public/` |
| 🔵 | ডেভ সার্ভার | `php artisan serve` |
| 🔥 | Vue ডেভেলপমেন্ট | `npm run dev` — হট রিলোড, বদল সাথে সাথে দেখা যায় |

> [!IMPORTANT]
> **Vue ফাইল বদলালে `npm run build` আবার চালাতে হবে** — নয়তো ব্রাউজারে পুরনো বান্ডলই চলবে।
> ডেভ করার সময় `npm run dev` চালু রাখলে এই ঝামেলা নেই।

---

## 🔐 লগইন আর অনুমতি

| | অ্যাকাউন্ট | পাসওয়ার্ড | রোল | কী করতে পারে |
|:--:| --- | --- | --- | --- |
| 👑 | `rasel@demo.com` | `1103104` | `admin` | দেখা, যোগ, এডিট, ডিলিট |
| 👀 | `rupa@demo.com` | `rupaforeverlove` | `viewer` | শুধু দেখা (প্রিন্ট, CSV, PDF-ও — সবই read-only) |

চারটে পারমিশন, [spatie/laravel-permission](https://spatie.be/docs/laravel-permission) দিয়ে:

```
expense.view      expense.create      expense.edit      expense.delete
```

**🛡️ দুই জায়গায় একই পারমিশন চেক হয়** — সার্ভারে রুট মিডলওয়্যারে (`can:expense.create`), আর
অ্যাপে বাটন দেখানোর সময় (`can('expense.create')`)। রুপা লগইন করলে **নতুন খরচ** বাটন, `+`
ভাসমান বাটন, প্রতি রো-র এডিট/ডিলিট বাটন, এমনকি মোডাল আর কনফার্ম ডায়ালগও DOM-এ থাকে না;
তবু কেউ হাতে API কল করলে সার্ভার `403` দেয় — **UI লুকানোটা সুবিধা, নিরাপত্তা নয়।**

**🍪 সেশন-ভিত্তিক লগইন** — কুকি + CSRF, কোনো টোকেন নয়। লগইন/লগআউটে সেশন regenerate হয়,
তাই রেসপন্সে নতুন CSRF টোকেন আসে আর অ্যাপ সেটাই ব্যবহার করতে শুরু করে। লগইন রুটে
`throttle:6,1` — পাসওয়ার্ড অনুমান করার চেষ্টা ধীর করতে।

**🔧 অ্যাকাউন্ট বদলাতে বা পাসওয়ার্ড রিসেট করতে**
[`database/seeders/RolePermissionSeeder.php`](database/seeders/RolePermissionSeeder.php)
এডিট করে আবার চালান — ফাইলটাই এই দুই অ্যাকাউন্টের source of truth।

> [!WARNING]
> স্টার্টার-কিটের `admin@example.com` (পাসওয়ার্ড `password`) অ্যাকাউন্টটা ডেটাবেজে ছিল।
> সিডার এখন ওটার রোল সরিয়ে দেয় — নইলে `admin` রোলে খরচের পারমিশন বসানোর সাথে সাথে ওই
> দুর্বল পাসওয়ার্ডের অ্যাকাউন্টটাও পূর্ণ অ্যাডমিন হয়ে যেত। অ্যাকাউন্টটা মুছতে চাইলে হাতে মুছতে হবে।

---

## 🧭 SPA কীভাবে সাজানো

| | রুট | কী দেয় |
|:--:| --- | --- |
| 🏠 | `GET /` | Blade শেল — শুধু `<div id="app">` + `window.__TRACKER__` (কে লগইন, কী পারমিশন, লেবেল, সময়কাল, রুট) |
| 🔑 | `POST /api/login` | ইউজার + পারমিশন + নতুন CSRF টোকেন |
| 🚪 | `POST /api/logout` | সেশন শেষ + নতুন CSRF টোকেন |
| 📖 | `GET /api/expenses` | তালিকা + সারসংক্ষেপ + ক্যাটাগরি + সর্বমোট |
| ✍️ | `POST` `PUT` `DELETE` `/api/expenses[/{id}]` | লেখার পর ওই ফিল্টারের নতুন তালিকাটাই ফেরত আসে |

শেল-ই বলে দেয় কে লগইন করা — তাই প্রথম পেইন্টেই হয় লগইন স্ক্রিন, নয় শিট; মাঝখানে ভুল
স্ক্রিনের ঝলক নেই। সেশন শেষ হয়ে গেলে যেকোনো কলে `401` আসে, অ্যাপ তখন নিজেই লগইন
স্ক্রিনে ফিরে যায় আর টোস্টে জানায়।

**তিনটে ব্যাপার জেনে রাখলে কোড পড়তে সুবিধা:**

- 🔄 **এক রাউন্ড ট্রিপে সেভ + রিফ্রেশ** — লেখার রিকোয়েস্টেও চালু ফিল্টার query string-এ যায়,
  সার্ভার নতুন করে হিসাব করা পুরো তালিকা ফেরত দেয়। তাই ফ্রন্টএন্ডকে মোট/গড়/ভাগ নিজে হিসাব
  করতে হয় না — সার্ভারের হিসাবই একমাত্র হিসাব।
- 🍪 **API `routes/web.php`-এ, `routes/api.php`-এ নয়** — SPA একই origin-এ, তাই সেশন কুকি
  আর CSRF টোকেনই যথেষ্ট, আলাদা টোকেন লাগে না।
- 🔗 **ফিল্টার URL-এ থাকে** (`?q=…&payment_method=…`) — লিংক শেয়ার করা যায়, রিফ্রেশেও থাকে,
  আর প্রিন্ট/PDF/CSV লিংকে একই ফিল্টার জুড়ে যায়।

---

## 🔔 টোস্ট বার্তা

পুরনো “সেভ করে রিডাইরেক্ট, তারপর সবুজ ফ্ল্যাশ” ব্যাপারটার জায়গায় এখন টোস্ট:

| | কখন | কী দেখায় |
|:--:| --- | --- |
| ✅ | যোগ / আপডেট / ডিলিট সফল | সবুজ টোস্ট, সার্ভারের পাঠানো বার্তা |
| 👋 | লগইন সফল | সবুজ টোস্ট — “স্বাগতম, Rasel!” |
| ⚠️ | ভ্যালিডেশন ফেল (`422`), ভুল পাসওয়ার্ড | লাল টোস্ট + কোন ঘরে কী ভুল, সাথে ফর্মেও ইনলাইন এরর |
| 🚫 | অনুমতি নেই (`403`) | লাল টোস্ট — “এই কাজের অনুমতি আপনার নেই।” |
| 📡 | নেটওয়ার্ক / সার্ভার / সেশন শেষ (`401`) | লাল টোস্ট, বাংলায় কী করতে হবে বলা |
| ℹ️ | ফিল্টার রিসেট, লগআউট | নীল তথ্য-টোস্ট |

টোস্ট নিজে নিজে মিলিয়ে যায় (নিচে সময়ের বার দেখায়), মাউস রাখলে থামে, আর একই বার্তা পরপর
এলে নতুন টোস্ট না বানিয়ে `×২` গোনে।

> 🧩 ইঞ্জিন [`useToasts.js`](resources/js/composables/useToasts.js) · দেখায়
> [`ToastHost.vue`](resources/js/components/ToastHost.vue) · ডিলিটের আগে
> `window.confirm()`-এর বদলে [`ConfirmDialog.vue`](resources/js/components/ConfirmDialog.vue)

---

## 🎨 ডিজাইন

স্ক্রিনে অ্যাপটা মডার্ন — ভায়োলেট/রোজ গ্রেডিয়েন্ট হিরো, প্রতি ক্যাটাগরির নিজস্ব আইকন ও রঙ,
নরম শ্যাডো, আর লাইট/ডার্ক থিম (উপরের ডানে টগল, `localStorage`-এ মনে রাখে; না বদলালে
সিস্টেমের থিম মানে)।

🖨️ **কিন্তু প্রিন্ট আউটপুট ইচ্ছে করেই আলাদা** — `/print` আর <kbd>Ctrl</kbd>+<kbd>P</kbd>-তে
গ্রেডিয়েন্ট, আইকন আর রঙ বাদ পড়ে মূল স্প্রেডশিটের চেহারা ফিরে আসে (নীল ব্যানার + নীল
বর্ডারের গ্রিড)। কোডে এটা [`public/css/app.css`](public/css/app.css)-এর শেষে `@media print`
ব্লকে।

🎯 **আইকন সব ইনলাইন SVG**, কোনো আইকন লাইব্রেরি লাগে না — দুই জায়গায় একই সেট:

| | কোথায় | ফাইল | ব্যবহার |
|:--:| --- | --- | --- |
| 🟢 | Vue অ্যাপ | [`resources/js/icons.js`](resources/js/icons.js) | `<AppIcon name="gift"/>` |
| 🔴 | প্রিন্ট / PDF | [`icon.blade.php`](resources/views/components/icon.blade.php) | `<x-icon name="gift"/>` |

নাম আসে `config/tracker.php` থেকে, তাই দুটোতেই একই নাম থাকতে হবে; নতুন ক্যাটাগরি ম্যাপে
না থাকলে ডিফল্ট আইকন পায়।

🖼️ হিরো ব্যানারের ছবিটা [`public/images/hero.jpg`](public/images/hero.jpg) — Unsplash থেকে
নেওয়া (লাইসেন্স-মুক্ত), প্রজেক্টেই রাখা আছে যাতে ইন্টারনেট ছাড়াও চলে। বদলাতে চাইলে ওই
ফাইলটা রিপ্লেস করলেই হবে; ছবি না থাকলেও গ্রেডিয়েন্ট থেকে যায়, ব্যানার ভাঙে না।

---

## 🧱 গঠন

```
tracker/
├── app/
│   ├── Http/Controllers/
│   │   ├── Api/AuthController.php        🔑  সেশন লগইন / লগআউট
│   │   ├── Api/ExpenseController.php     🔌  JSON API — তালিকা + CRUD
│   │   └── ExpenseController.php         🏠  SPA শেল, প্রিন্ট, CSV, PDF
│   ├── Http/Resources/
│   │   └── ExpenseResource.php           📦  একটা খরচ JSON-এ কী নিয়ে যায়
│   ├── Models/                           🗃️  Expense · Category · User
│   └── Support/
│       ├── ExpenseReport.php             🧮  ফিল্টার · কোয়েরি · সারসংক্ষেপ (একটাই সোর্স)
│       ├── TrackerBoot.php               🚀  window.__TRACKER__ পেলোড
│       ├── TrackerPeriod.php             📅  সময়কাল আর মাসের গোনা
│       └── BanglaPdf.php                 🅰️  বাংলা পারে এমন mPDF ইনস্ট্যান্স
├── resources/
│   ├── js/                               💚  Vue 3 অ্যাপ (নিচে বিস্তারিত)
│   └── views/
│       ├── app.blade.php                 🏠  SPA শেল
│       ├── expenses/print.blade.php      🖨️  প্রিন্ট শিট
│       ├── expenses/pdf.blade.php        📄  PDF রিপোর্টের লেআউট
│       └── components/icon.blade.php     🎯  প্রিন্ট/PDF-এর SVG আইকন
├── database/seeders/
│   ├── RolePermissionSeeder.php          🔐  রোল, পারমিশন, দুই অ্যাকাউন্ট
│   └── TrackerSeeder.php                 🌱  মূল শিটের ১৫টি রো
├── config/tracker.php                    ⚙️  শিরোনাম · তারিখ · মাধ্যম · বাংলা লেবেল
└── public/css/app.css                    🎨  পুরো ডিজাইন সিস্টেম + প্রিন্ট স্টাইল
```

<details>
<summary><b>💚 Vue অ্যাপ — <code>resources/js/</code></b></summary>

<br>

| | ফাইল | কাজ |
|:--:| --- | --- |
| 🚀 | `app.js` | অ্যাপ মাউন্ট + গ্লোবাল error handler (ক্র্যাশও টোস্ট হয়ে জানায়) |
| 🧩 | `App.vue` | পুরো পেজের সাজ; লগইন স্ক্রিন, মোডাল, কনফার্ম আর টোস্ট হোস্ট এখান থেকে |
| 🗄️ | `store.js` | একটাই reactive state — ইউজার/পারমিশন, তালিকা, ফিল্টার, ফ্ল্যাগ, সব অ্যাকশন |
| 🔌 | `api.js` | axios ক্লায়েন্ট; HTTP এররকে বাংলা বার্তায় বদলায় (`ApiError`) |
| 🔢 | `format.js` | `money()`, `taka()`, `dateLabel()`, ফিল্টার → query string |
| 🎯 | `icons.js` | Vue-র আইকন সেট |
| 🔔 | `composables/useToasts.js` | টোস্ট কিউ |
| 🌗 | `composables/useTheme.js` | লাইট/ডার্ক টগল |

**কম্পোনেন্ট** — `components/`

`LoginScreen` · `AppBar` · `HeroBanner` · `StatTiles` · `FilterPanel` · `ExpenseList` ·
`RowActions` · `BreakdownPanel` · `ExpenseModal` · `ConfirmDialog` · `ToastHost` · `AppIcon`

</details>

<details>
<summary><b>🗃️ মডেল আর কনফিগ — কী কোথায়</b></summary>

<br>

| | ফাইল | মনে রাখার মতো |
|:--:| --- | --- |
| 💸 | [`Expense.php`](app/Models/Expense.php) | `Expense::money()` পুরো টাকা দশমিক ছাড়া দেখায়, পয়সা থাকলে দুই ঘর |
| 🏷️ | [`Category.php`](app/Models/Category.php) | `Category::resolve()` না থাকলে বানিয়ে নেয়; `tint()` স্থায়ী রঙ দেয় (১–৮) |
| 👤 | [`User.php`](app/Models/User.php) | spatie-র `HasRoles` — রোল/পারমিশন এখান থেকেই আসে |
| 📦 | [`ExpenseResource.php`](app/Http/Resources/ExpenseResource.php) | JSON-এ যায় সংখ্যা আর ISO তারিখ; টাকা/তারিখের ফরম্যাট হয় ব্রাউজারে (`format.js`) |
| ⚙️ | [`config/tracker.php`](config/tracker.php) | ব্যানারের শিরোনাম, শুরু/শেষের তারিখ, পেমেন্ট মাধ্যম, ক্যাটাগরি↔আইকন, বাংলা লেবেল |

</details>

<details>
<summary><b>🧩 কেন CSS Vite-এর বাইরে</b></summary>

<br>

[`public/css/app.css`](public/css/app.css) Vite বান্ডলের অংশ নয়, কারণ প্রিন্ট শিট আর PDF
রিপোর্ট সার্ভার-রেন্ডার করা পেজ — ওদের স্টাইল Vite ছাড়াই লাগে। Vite শুধু Vue বান্ডলটা
বানায়, সেটআপ [`vite.config.js`](vite.config.js)-এ।

</details>

---

## 🏷️ ব্যানার

ব্যানারের লাইনটা নিজেই তৈরি হয় শুরুর তারিখ থেকে — মাসের সংখ্যা হাতে বদলাতে হয় না:

```
Rasel & Rupa - Expense Tracker (25 May 2026 - 31 July 2026) 2 Months 6 Days
```

```env
TRACKER_TITLE="Rasel & Rupa - Expense Tracker"
TRACKER_START_DATE="2026-05-25"
TRACKER_END_DATE=            # খালি থাকলে আজকের তারিখ, আর দিন-মাস গোনা চলতেই থাকবে
```

| | সেটিং | ফল |
|:--:| --- | --- |
| ⏳ | `TRACKER_END_DATE` খালি | `25 May - Present`, আজকের তারিখ ধরে মাস গোনা (২৫ আগস্টে নিজেই `3 Month`) |
| 🏁 | `TRACKER_END_DATE=2026-07-30` | `25 May - 30 July`, মাসও ওই তারিখেই থেমে যায় |
| 📆 | প্রথম মাস পূর্ণ হওয়ার আগে | দিনে দেখায় (যেমন `16 Day`) |

হিসাবটা [`TrackerPeriod.php`](app/Support/TrackerPeriod.php)-এ। ব্যানার মার্কআপ দুই জায়গায় —
অ্যাপে [`HeroBanner.vue`](resources/js/components/HeroBanner.vue), প্রিন্টে
[`banner.blade.php`](resources/views/partials/banner.blade.php); লেখাটা দুজনেই সার্ভারের
হিসাব করা একই লাইন দেখায়।

---

## 📄 PDF রিপোর্ট (mPDF)

`/report` — mPDF 8.3 দিয়ে সার্ভার-সাইডে তৈরি। ভেতরে থাকে নীল ব্যানার, পুরো খরচের শিট +
`মোট`, তারপর ক্যাটাগরি ভাগ, পেমেন্ট মাধ্যম ভাগ আর সারসংক্ষেপ; ফুটারে পৃষ্ঠা নম্বর।
তালিকার ফিল্টার চালু থাকলে রিপোর্টেও সেটাই যায়, আর উপরে কোন ফিল্টার ছিল তা লেখা থাকে।

<details>
<summary><b>🅰️ বাংলা ফন্টের ব্যাপারটা — কেন Lohit, কেন Noto নয়</b></summary>

<br>

mPDF-এর সাথে কোনো বাংলা ফন্ট আসে না, আর বাংলা যুক্তাক্ষরের জন্য OpenType shaping লাগে।
তাই [`storage/fonts/Lohit-Bengali.ttf`](storage/fonts) রেজিস্টার করা আছে `useOTL => 0xFF`
দিয়ে — সেটআপ [`BanglaPdf.php`](app/Support/BanglaPdf.php)-এ।

দুটো জিনিস জানা থাকলে ভালো:

- ❌ **Noto Sans Bengali কাজ করে না।** mPDF 8.3-এর TTF পার্সার আধুনিক Noto পড়তে পারে না
  (`GPOS Lookup Type 5, Format 3 not supported`, আর GDEF টেবিলে unmapped glyph)। Lohit
  পরিষ্কারভাবে পার্স হয় এবং যুক্তাক্ষর ঠিকঠাক বসায় — রেন্ডার করে যাচাই করা হয়েছে।
- 🔤 **Lohit-এ ল্যাটিন অক্ষর নেই** (বাংলা, ডিজিট আর যতিচিহ্ন আছে)। তাই `useSubstitutions`
  চালু করে `dejavusanscondensed` ব্যাকআপ ফন্ট দেওয়া — নইলে "Rasel & Rupa", "Cash",
  "bKash" সব □ বক্স হয়ে যেত। Lohit-এ `·` (U+00B7)-ও নেই, তাই রিপোর্টে ASCII যতিচিহ্ন
  ব্যবহার করা হয়েছে।

ফন্ট ফাইল না থাকলে `/report` **503** দেয় (চুপচাপ ভাঙা PDF বানায় না)। mPDF-এর ফন্ট ক্যাশ
`storage/app/mpdf`-এ জমে, git-ignored।

</details>

---

## 🧪 টেস্ট

```bash
php artisan test
```

<div align="center">

![Tests](https://img.shields.io/badge/60%20tests-205%20assertions-2ea043?style=flat-square&logo=phpunit&logoColor=white)

</div>

টেস্ট in-memory SQLite-এ চলে, তাই আসল ডেটাবেজে হাত পড়ে না। SPA হওয়ার পর টেস্টগুলো HTML
না দেখে JSON API-ই যাচাই করে (`getJson('/api/expenses')`), আর শেল টেস্টে `withoutVite()`
দেওয়া — তাই টেস্ট চালানোর আগে `npm run build` লাগে না।

| | ফাইল | কী যাচাই করে |
|:--:| --- | --- |
| 📊 | [`ExpenseTrackerTest.php`](tests/Feature/ExpenseTrackerTest.php) | শিট, ফিল্টার, CRUD, প্রিন্ট/CSV/PDF (অ্যাডমিন হিসেবে চলে) |
| 🔐 | [`AuthorizationTest.php`](tests/Feature/AuthorizationTest.php) | গেস্ট কিছুই পায় না, রুপা শুধু পড়তে পারে (লিখতে `403`), রাসেল সব পারে, লগইন/লগআউট |

---

## 🗄️ ডেটাবেজ

রিমোট **Aiven MySQL**; SSL সার্টিফিকেট `storage/certs/aiven-ca.pem`-এ, পাথ `.env`-এর
`MYSQL_ATTR_SSL_CA`-তে।

<div align="center">
<br>

**❤️ Rasel & Rupa**

</div>
