134 lines
4.7 KiB
Markdown
134 lines
4.7 KiB
Markdown
# VAT Settings + RFQ Per-Item Vatable Checkbox — Design Spec
|
||
|
||
**Date:** 2026-06-01
|
||
**Status:** Approved
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
Two connected features:
|
||
|
||
1. **Global VAT setting** — Admin sets a VAT rate (%) once in Settings. Stored in the existing `settings` key/value table as `vat_rate`.
|
||
2. **Per-item vatable checkbox on RFQ portal** — When a supplier fills in their quote, each item row has a "VAT?" checkbox. Ticking it applies the global VAT rate to that item's total. The footer shows a live breakdown: Subtotal → VAT → Grand Total. Everything is stored against the quote on submission.
|
||
|
||
---
|
||
|
||
## 1. VAT Global Setting
|
||
|
||
### Route & Controller
|
||
- `GET settings/vat` → `VatSettingController@index` — renders the VAT settings page
|
||
- `POST settings/vat` → `VatSettingController@update` — saves the rate via AJAX, returns JSON
|
||
|
||
### Storage
|
||
- Uses the existing `Setting` model (`settings` table, key/value pairs)
|
||
- Key: `vat_rate`, value: decimal string e.g. `"10"` for 10%
|
||
- Default when not set: `0`
|
||
|
||
### View: `resources/views/settings/vat.blade.php`
|
||
- Extends `layouts.app`
|
||
- Single card with a number input: "VAT Rate (%)" — accepts decimals (e.g. 5, 10, 14.5)
|
||
- Save button submits via `fetch()` AJAX (no page reload, per project rules)
|
||
- Shows current saved value on load
|
||
- Success/error feedback via `showToast()`
|
||
|
||
### Sidebar
|
||
- Add "VAT Settings" link under the `@role('Admin')` System section in `layouts/app.blade.php`
|
||
- Active state: `request()->routeIs('settings.vat')`
|
||
|
||
---
|
||
|
||
## 2. RFQ Portal — Per-Item Vatable Checkbox
|
||
|
||
### Database
|
||
- New migration: add `is_vatable` boolean (default `false`) to `supplier_quote_items` table
|
||
- `SupplierQuoteItem::$fillable` gains `is_vatable`
|
||
|
||
### Controller: `RfqPortalController`
|
||
|
||
**`show()` method:**
|
||
- Load VAT rate: `$vatRate = (float) Setting::get('vat_rate', 0)`
|
||
- Pass `$vatRate` to `rfq.show` view
|
||
|
||
**`submit()` method:**
|
||
- Add validation rule: `'items.*.is_vatable' => ['nullable', 'boolean']`
|
||
- When creating each `SupplierQuoteItem`, set `is_vatable` from submitted checkbox value
|
||
- Calculate VAT: sum of `(unit_price × quantity)` for vatable items × `vatRate / 100`
|
||
- Store `total_amount` on `SupplierQuote` as the VAT-inclusive grand total
|
||
|
||
### View: `resources/views/rfq/show.blade.php`
|
||
|
||
**Table header** — add "VAT?" column:
|
||
```
|
||
# | Description | Qty | Unit | Unit Price (BD) | VAT? | Total (BD)
|
||
```
|
||
|
||
**Each item row** — add checkbox cell:
|
||
```html
|
||
<input type="checkbox" name="items[N][is_vatable]" value="1"
|
||
onchange="calcRow(N, qty)" style="accent-color:#2563eb;width:16px;height:16px;">
|
||
```
|
||
|
||
**Footer** — replace single "Grand Total" row with three rows:
|
||
```
|
||
Subtotal (before VAT): BD XXX.XXX
|
||
VAT (10%): BD XXX.XXX ← hidden when vatRate is 0
|
||
Grand Total: BD XXX.XXX
|
||
```
|
||
|
||
**JavaScript `calcRow()` update:**
|
||
- Each row calculates its own total (unit_price × qty), unchanged
|
||
- `recalcTotals()` called after every row change:
|
||
- Sums all row totals → subtotal
|
||
- Sums row totals for checked (vatable) rows × `vatRate/100` → vatAmount
|
||
- grand = subtotal + vatAmount
|
||
- Updates subtotal, VAT, and grand total display elements
|
||
- VAT rate injected as a JS variable: `var _vatRate = {{ $vatRate }};`
|
||
- VAT row is hidden when `_vatRate === 0`
|
||
|
||
### Mobile stacked layout
|
||
On mobile (≤640px), the table renders as cards. The VAT checkbox appears as a labelled row inside each card.
|
||
|
||
---
|
||
|
||
## 3. Data Flow Summary
|
||
|
||
```
|
||
Admin sets vat_rate = 10 in settings/vat
|
||
↓
|
||
RfqPortalController::show() reads vat_rate, passes to view
|
||
↓
|
||
Supplier ticks VAT on items 1 and 3
|
||
↓
|
||
JS: subtotal = sum(all items), vat = sum(vatable items) × 0.10, grand = subtotal + vat
|
||
↓
|
||
Supplier submits form
|
||
↓
|
||
RfqPortalController::submit():
|
||
- stores is_vatable per SupplierQuoteItem
|
||
- total_amount on SupplierQuote = subtotal + vat (VAT-inclusive)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Files Touched
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| `routes/web.php` | Add `GET/POST settings/vat` routes |
|
||
| `app/Http/Controllers/Settings/VatSettingController.php` | New controller |
|
||
| `resources/views/settings/vat.blade.php` | New view |
|
||
| `resources/views/layouts/app.blade.php` | Add sidebar link |
|
||
| `database/migrations/..._add_is_vatable_to_supplier_quote_items.php` | New migration |
|
||
| `app/Models/SupplierQuoteItem.php` | Add `is_vatable` to `$fillable` |
|
||
| `app/Http/Controllers/Purchase/RfqPortalController.php` | Load VAT rate, store `is_vatable`, calc VAT total |
|
||
| `resources/views/rfq/show.blade.php` | Add checkbox column, VAT footer breakdown, JS update |
|
||
|
||
---
|
||
|
||
## 5. Out of Scope
|
||
|
||
- Showing VAT breakdown on the internal quote comparison view (can be added later)
|
||
- Per-supplier or per-item VAT rates (single global rate only)
|
||
- VAT registration numbers
|