feat: supplier item name editing, N/A checkbox, and adjusted indicator on compare view

This commit is contained in:
Ghassan Yusuf 2026-06-01 12:19:52 +03:00
parent 7b399d5167
commit 6061e8ca4f
6 changed files with 626 additions and 41 deletions

View File

@ -61,14 +61,16 @@ class RfqPortalController extends Controller
}
$validated = $request->validate([
'terms' => ['accepted'],
'confirm_code' => ['required', 'string'],
'lead_time_days' => ['nullable', 'integer', 'min:0'],
'payment_terms' => ['nullable', 'string', 'max:200'],
'notes' => ['nullable', 'string', 'max:1000'],
'items' => ['required', 'array'],
'items.*.unit_price' => ['required', 'numeric', 'min:0'],
'items.*.is_vatable' => ['nullable', 'boolean'],
'terms' => ['accepted'],
'confirm_code' => ['required', 'string'],
'lead_time_days' => ['nullable', 'integer', 'min:0'],
'payment_terms' => ['nullable', 'string', 'max:200'],
'notes' => ['nullable', 'string', 'max:1000'],
'items' => ['required', 'array'],
'items.*.unit_price' => ['nullable', 'numeric', 'min:0'],
'items.*.is_vatable' => ['nullable', 'boolean'],
'items.*.not_available' => ['nullable', 'boolean'],
'items.*.supplier_description' => ['nullable', 'string', 'max:500'],
]);
$expectedCode = session('rfq_confirm_' . $token);
@ -98,24 +100,31 @@ class RfqPortalController extends Controller
$vatRate = (float) Setting::get('vat_rate', 0);
foreach ($purchaseItems as $i => $item) {
$unitPrice = (float)($validated['items'][$i]['unit_price'] ?? 0);
$qty = (float)$item->quantity_required;
$totalPrice = round($unitPrice * $qty, 3);
$isVatable = !empty($validated['items'][$i]['is_vatable']);
$subtotal += $totalPrice;
$notAvailable = !empty($validated['items'][$i]['not_available']);
$unitPrice = $notAvailable ? 0 : (float)($validated['items'][$i]['unit_price'] ?? 0);
$qty = (float)$item->quantity_required;
$totalPrice = $notAvailable ? 0 : round($unitPrice * $qty, 3);
$isVatable = !$notAvailable && !empty($validated['items'][$i]['is_vatable']);
$supplierDescription = !empty($validated['items'][$i]['supplier_description'])
? trim($validated['items'][$i]['supplier_description'])
: null;
$subtotal += $totalPrice;
if ($isVatable && $vatRate > 0) {
$vatAmount += round($totalPrice * $vatRate / 100, 3);
}
SupplierQuoteItem::create([
'supplier_quote_id' => $quote->id,
'description' => $item->description,
'unit' => $item->unit ?? '',
'quantity' => $qty,
'unit_price' => $unitPrice,
'total_price' => $totalPrice,
'is_vatable' => $isVatable,
'supplier_quote_id' => $quote->id,
'description' => $item->description,
'supplier_description'=> $supplierDescription,
'unit' => $item->unit ?? '',
'quantity' => $qty,
'unit_price' => $unitPrice,
'total_price' => $totalPrice,
'is_vatable' => $isVatable,
'not_available' => $notAvailable,
]);
}

View File

@ -7,11 +7,13 @@ use Illuminate\Database\Eloquent\Model;
class SupplierQuoteItem extends Model
{
protected $fillable = [
'supplier_quote_id', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'is_vatable',
'supplier_quote_id', 'description', 'supplier_description', 'unit', 'quantity',
'unit_price', 'total_price', 'is_vatable', 'not_available',
];
protected $casts = [
'is_vatable' => 'boolean',
'is_vatable' => 'boolean',
'not_available' => 'boolean',
];
public function quote()

View File

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('supplier_quote_items', function (Blueprint $table) {
$table->string('supplier_description')->nullable()->after('description');
$table->boolean('not_available')->default(false)->after('is_vatable');
});
}
public function down(): void
{
Schema::table('supplier_quote_items', function (Blueprint $table) {
$table->dropColumn(['supplier_description', 'not_available']);
});
}
};

View File

@ -0,0 +1,440 @@
# VAT Settings + RFQ Per-Item Vatable Checkbox — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a global VAT rate setting and per-item vatable checkboxes on the supplier RFQ portal, with live VAT breakdown in totals.
**Architecture:** Global VAT rate stored in existing `settings` key/value table. Per-item `is_vatable` flag added to `supplier_quote_items`. RFQ portal view updated with checkbox column and three-row footer (subtotal / VAT / grand total). New `VatSettingController` + `settings/vat` page added under the Admin-only System sidebar section.
**Tech Stack:** Laravel 12, PHP 8.2, SQLite, Blade, Alpine-free vanilla JS, Tailwind (inline styles per project convention)
---
### Task 1: Migration — add `is_vatable` to `supplier_quote_items`
**Files:**
- Modify: `database/migrations/2026_06_01_090734_add_is_vatable_to_supplier_quote_items.php`
- Modify: `app/Models/SupplierQuoteItem.php`
- [ ] **Step 1: Write the migration**
Replace the generated migration body with:
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('supplier_quote_items', function (Blueprint $table) {
$table->boolean('is_vatable')->default(false)->after('total_price');
});
}
public function down(): void
{
Schema::table('supplier_quote_items', function (Blueprint $table) {
$table->dropColumn('is_vatable');
});
}
};
```
- [ ] **Step 2: Run migration**
```bash
php artisan migrate
```
Expected: `Migrating: 2026_06_01_090734_add_is_vatable_to_supplier_quote_items` then `Migrated`.
- [ ] **Step 3: Update SupplierQuoteItem model**
In `app/Models/SupplierQuoteItem.php`, update `$fillable`:
```php
protected $fillable = [
'supplier_quote_id', 'description', 'unit', 'quantity', 'unit_price', 'total_price', 'is_vatable',
];
protected $casts = [
'is_vatable' => 'boolean',
];
```
- [ ] **Step 4: Commit**
```bash
git add database/migrations/2026_06_01_090734_add_is_vatable_to_supplier_quote_items.php app/Models/SupplierQuoteItem.php
git commit -m "feat: add is_vatable column to supplier_quote_items"
```
---
### Task 2: VatSettingController + route + view
**Files:**
- Create: `app/Http/Controllers/Settings/VatSettingController.php`
- Create: `resources/views/settings/vat.blade.php`
- Modify: `routes/web.php`
- [ ] **Step 1: Create VatSettingController**
Create `app/Http/Controllers/Settings/VatSettingController.php`:
```php
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
class VatSettingController extends Controller
{
public function index()
{
$vatRate = Setting::get('vat_rate', '0');
return view('settings.vat', compact('vatRate'));
}
public function update(Request $request)
{
$validated = $request->validate([
'vat_rate' => ['required', 'numeric', 'min:0', 'max:100'],
]);
Setting::set('vat_rate', (string) $validated['vat_rate']);
return response()->json(['message' => 'VAT rate saved.', 'vat_rate' => $validated['vat_rate']]);
}
}
```
- [ ] **Step 2: Add routes**
In `routes/web.php`, after the integrations routes (around line 148), add:
```php
// VAT settings
Route::get('settings/vat', [VatSettingController::class, 'index'])->name('settings.vat');
Route::post('settings/vat', [VatSettingController::class, 'update'])->name('settings.vat.update');
```
Also add the import at the top of the file with the other Settings controllers:
```php
use App\Http\Controllers\Settings\VatSettingController;
```
- [ ] **Step 3: Create the VAT settings view**
Create `resources/views/settings/vat.blade.php`:
```blade
@extends('layouts.app')
@section('title', 'Settings — VAT')
@section('content')
<div class="mb-5">
<h1 class="page-title">VAT Settings</h1>
<p class="page-subtitle">Set the global VAT rate applied to vatable items on supplier quotes.</p>
</div>
<div style="max-width:480px;">
<div style="background:white;border:1px solid #e2e8f0;border-radius:0.875rem;overflow:hidden;">
<div style="padding:1.25rem 1.5rem;border-bottom:1px solid #e2e8f0;background:#f8fafc;">
<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.05em;">VAT Configuration</div>
</div>
<div style="padding:1.5rem;">
<label style="display:block;font-size:12px;font-weight:600;color:#374151;margin-bottom:6px;">
VAT Rate (%)
</label>
<div style="display:flex;align-items:center;gap:10px;">
<input type="number" id="vat-rate-input" value="{{ $vatRate }}"
min="0" max="100" step="0.01" placeholder="e.g. 10"
style="width:160px;padding:9px 12px;border:1.5px solid #e2e8f0;border-radius:8px;font-size:14px;font-weight:600;outline:none;"
onfocus="this.style.borderColor='#2563eb'" onblur="this.style.borderColor='#e2e8f0'">
<span style="font-size:14px;color:#64748b;font-weight:500;">%</span>
</div>
<p style="font-size:12px;color:#94a3b8;margin-top:8px;">
Enter 0 to disable VAT. Suppliers will see the VAT checkbox on their quote form when this is greater than 0.
</p>
<button onclick="saveVat()"
style="margin-top:20px;padding:10px 24px;background:#2563eb;color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;">
Save VAT Rate
</button>
</div>
</div>
</div>
<script>
var CSRF = document.querySelector('meta[name="csrf-token"]').content;
function saveVat() {
var rate = document.getElementById('vat-rate-input').value;
fetch('{{ route('settings.vat.update') }}', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ vat_rate: rate })
}).then(function(r) {
return r.json().then(function(body) {
if (!r.ok) return Promise.reject(body);
return body;
});
}).then(function() {
showToast('VAT rate saved.', 'success');
}).catch(function(err) {
showToast((err.errors && err.errors.vat_rate ? err.errors.vat_rate[0] : null) || 'Failed to save.', 'error');
});
}
</script>
@endsection
```
- [ ] **Step 4: Commit**
```bash
git add app/Http/Controllers/Settings/VatSettingController.php resources/views/settings/vat.blade.php routes/web.php
git commit -m "feat: add VAT settings page and controller"
```
---
### Task 3: Add VAT link to sidebar
**Files:**
- Modify: `resources/views/layouts/app.blade.php`
- [ ] **Step 1: Add sidebar link**
In `resources/views/layouts/app.blade.php`, after the Integrations `<a>` tag (around line 198, just before `@endrole`), add:
```blade
<a href="{{ route('settings.vat') }}" style="
display:flex; align-items:center; gap:8px;
padding:7px 12px 7px 24px; border-radius:7px; margin-bottom:1px;
font-size:13px; text-decoration:none;
{{ request()->routeIs('settings.vat*') ? 'background:#1e293b;color:#fff;font-weight:500;' : 'color:#94a3b8;' }}
" onmouseover="if(!this.style.color.includes('fff'))this.style.color='#e2e8f0'" onmouseout="if(!this.style.background.includes('1e293b'))this.style.color='#94a3b8'">
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="flex-shrink:0;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 14l6-6m-5.5.5h.01m4.99 5h.01M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16l3.5-2 3.5 2 3.5-2 3.5 2z"/>
</svg>
VAT Settings
</a>
```
- [ ] **Step 2: Commit**
```bash
git add resources/views/layouts/app.blade.php
git commit -m "feat: add VAT Settings link to sidebar"
```
---
### Task 4: Update RfqPortalController — load VAT rate and store is_vatable
**Files:**
- Modify: `app/Http/Controllers/Purchase/RfqPortalController.php`
- [ ] **Step 1: Update show() to pass VAT rate**
Add `use App\Models\Setting;` at the top of the file.
Update the `show()` method — add before the `return view(...)` line:
```php
$vatRate = (float) Setting::get('vat_rate', 0);
```
Update the return to:
```php
return view('rfq.show', compact('invitation', 'purchaseRequest', 'items', 'confirmCode', 'vatRate'));
```
- [ ] **Step 2: Update submit() to store is_vatable and calculate VAT-inclusive total**
Add `'items.*.is_vatable' => ['nullable', 'boolean']` to the validation rules array.
Replace the items loop and total calculation:
```php
$subtotal = 0;
$vatAmount = 0;
$vatRate = (float) Setting::get('vat_rate', 0);
foreach ($purchaseItems as $i => $item) {
$unitPrice = (float)($validated['items'][$i]['unit_price'] ?? 0);
$qty = (float)$item->quantity_required;
$totalPrice = round($unitPrice * $qty, 3);
$isVatable = !empty($validated['items'][$i]['is_vatable']);
$subtotal += $totalPrice;
if ($isVatable && $vatRate > 0) {
$vatAmount += round($totalPrice * $vatRate / 100, 3);
}
SupplierQuoteItem::create([
'supplier_quote_id' => $quote->id,
'description' => $item->description,
'unit' => $item->unit ?? '',
'quantity' => $qty,
'unit_price' => $unitPrice,
'total_price' => $totalPrice,
'is_vatable' => $isVatable,
]);
}
$grand = round($subtotal + $vatAmount, 3);
$quote->update(['total_amount' => $grand]);
```
- [ ] **Step 3: Commit**
```bash
git add app/Http/Controllers/Purchase/RfqPortalController.php
git commit -m "feat: load VAT rate and store is_vatable in RFQ portal"
```
---
### Task 5: Update rfq/show.blade.php — checkbox column + VAT footer + JS
**Files:**
- Modify: `resources/views/rfq/show.blade.php`
- [ ] **Step 1: Add VAT rate JS variable**
At the start of the `<script>` block, add:
```javascript
var _vatRate = {{ $vatRate }};
```
- [ ] **Step 2: Replace calcRow() and add recalcTotals()**
Replace the entire `calcRow` function and grand total logic with:
```javascript
var _totals = {};
var _vatable = {};
function calcRow(i, qty) {
var inp = document.querySelector('input[name="items[' + i + '][unit_price]"]');
var cb = document.querySelector('input[name="items[' + i + '][is_vatable]"]');
var up = parseFloat(inp ? inp.value : 0) || 0;
var tot = Math.round(up * qty * 1000) / 1000;
_totals[i] = tot;
_vatable[i] = cb ? cb.checked : false;
var el = document.getElementById('tot-' + i);
if (el) el.textContent = tot > 0 ? 'BD ' + tot.toFixed(3) : '—';
recalcTotals();
}
function recalcTotals() {
var subtotal = 0;
var vatAmount = 0;
Object.keys(_totals).forEach(function(i) {
subtotal += _totals[i];
if (_vatable[i] && _vatRate > 0) {
vatAmount += Math.round(_totals[i] * _vatRate / 100 * 1000) / 1000;
}
});
var grand = Math.round((subtotal + vatAmount) * 1000) / 1000;
var elSub = document.getElementById('subtotal-row');
var elVat = document.getElementById('vat-row');
var elGrand = document.getElementById('grand-total');
if (elSub) elSub.textContent = 'BD ' + subtotal.toFixed(3);
if (elVat) {
elVat.textContent = 'BD ' + vatAmount.toFixed(3);
var vatTr = document.getElementById('vat-tr');
if (vatTr) vatTr.style.display = _vatRate > 0 ? '' : 'none';
}
if (elGrand) elGrand.textContent = 'BD ' + grand.toFixed(3);
}
```
- [ ] **Step 3: Update table header**
Replace the `<thead>` block:
```html
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Qty</th>
<th>Unit</th>
<th style="text-align:center;">VAT?</th>
<th style="text-align:right;">Unit Price (BD)</th>
<th style="text-align:right;">Total (BD)</th>
</tr>
</thead>
```
- [ ] **Step 4: Update each item row**
Replace the `@foreach` tbody rows:
```blade
@foreach($items as $i => $item)
<tr>
<td style="color:#94a3b8;font-size:12px;">{{ $i + 1 }}</td>
<td style="font-weight:500;">{{ $item->description }}</td>
<td>{{ rtrim(rtrim(number_format((float)$item->quantity_required, 3), '0'), '.') }}</td>
<td style="color:#64748b;">{{ $item->unit ?: '—' }}</td>
<td style="text-align:center;">
@if($vatRate > 0)
<input type="checkbox" name="items[{{ $i }}][is_vatable]" value="1"
onchange="calcRow({{ $i }}, {{ (float)$item->quantity_required }})"
style="width:16px;height:16px;accent-color:#2563eb;cursor:pointer;">
@else
<span style="color:#cbd5e1;font-size:11px;"></span>
@endif
</td>
<td style="text-align:right;">
<input type="number" class="price" name="items[{{ $i }}][unit_price]"
min="0" step="0.001" required placeholder="0.000"
oninput="calcRow({{ $i }}, {{ (float)$item->quantity_required }})">
</td>
<td style="text-align:right;font-weight:600;" id="tot-{{ $i }}"></td>
</tr>
@endforeach
```
- [ ] **Step 5: Update tfoot**
Replace the `<tfoot>` block:
```html
<tfoot>
<tr style="background:#f8fafc;">
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;">Subtotal:</td>
<td style="text-align:right;font-size:14px;color:#475569;" id="subtotal-row">BD 0.000</td>
</tr>
<tr id="vat-tr" style="background:#fffbeb;{{ $vatRate > 0 ? '' : 'display:none;' }}">
<td colspan="6" style="text-align:right;font-size:13px;color:#92400e;">VAT ({{ $vatRate }}%):</td>
<td style="text-align:right;font-size:14px;color:#92400e;" id="vat-row">BD 0.000</td>
</tr>
<tr style="background:#f8fafc;border-top:2px solid #e2e8f0;">
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;font-weight:700;">Grand Total:</td>
<td style="text-align:right;font-size:15px;color:#2563eb;font-weight:700;" id="grand-total">BD 0.000</td>
</tr>
</tfoot>
```
- [ ] **Step 6: Commit**
```bash
git add resources/views/rfq/show.blade.php
git commit -m "feat: add VAT checkbox column and live breakdown to RFQ portal"
```

View File

@ -48,7 +48,7 @@
{{-- Per-item rows --}}
@foreach($items as $i => $reqItem)
@php
$rowPrices = $quotes->map(fn($q) => optional($q->items->get($i))->unit_price)->filter()->values();
$rowPrices = $quotes->map(fn($q) => ($q->items->get($i) && !$q->items->get($i)->not_available) ? $q->items->get($i)->unit_price : null)->filter()->values();
$minPrice = $rowPrices->count() ? $rowPrices->min() : null;
@endphp
<tr>
@ -57,14 +57,24 @@
<div style="font-size:11px;color:#94a3b8;margin-top:2px;">Qty: {{ $reqItem->quantity }} {{ $reqItem->unit }}</div>
</td>
@foreach($quotes as $q)
@php $qItem = $q->items->get($i); $isMin = $qItem && $minPrice !== null && (float)$qItem->unit_price === (float)$minPrice && $rowPrices->count() > 1; @endphp
@php $qItem = $q->items->get($i); $isMin = $qItem && !$qItem->not_available && $minPrice !== null && (float)$qItem->unit_price === (float)$minPrice && $rowPrices->count() > 1; @endphp
<td style="padding:10px 14px;border-bottom:1px solid #f1f5f9;text-align:center;background:{{ $isMin ? '#f0fdf4' : '' }};">
@if($qItem)
<div style="font-weight:600;color:{{ $isMin ? '#15803d' : '#0f172a' }};">
@if($isMin)<span style="font-size:10px;font-weight:700;color:#15803d;display:block;">LOWEST</span>@endif
BD {{ number_format($qItem->unit_price, 3) }}
</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;">BD {{ number_format($qItem->total_price, 3) }}</div>
@if($qItem->not_available)
<span style="font-size:11px;font-weight:700;color:#dc2626;background:#fef2f2;padding:2px 8px;border-radius:4px;border:1px solid #fecaca;">Not available</span>
@else
@if($qItem->supplier_description)
<div style="font-size:10px;color:#64748b;margin-bottom:2px;font-style:italic;">
"{{ $qItem->supplier_description }}"
<span style="background:#fef3c7;color:#92400e;font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;border:1px solid #fde68a;font-style:normal;margin-left:2px;">adjusted</span>
</div>
@endif
<div style="font-weight:600;color:{{ $isMin ? '#15803d' : '#0f172a' }};">
@if($isMin)<span style="font-size:10px;font-weight:700;color:#15803d;display:block;">LOWEST</span>@endif
BD {{ number_format($qItem->unit_price, 3) }}
</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;">BD {{ number_format($qItem->total_price, 3) }}</div>
@endif
@else
<span style="color:#e2e8f0;"></span>
@endif

View File

@ -78,6 +78,7 @@
<th>Description</th>
<th>Qty</th>
<th>Unit</th>
<th style="text-align:center;">N/A?</th>
<th style="text-align:center;">VAT?</th>
<th style="text-align:right;">Unit Price (BD)</th>
<th style="text-align:right;">Total (BD)</th>
@ -85,14 +86,46 @@
</thead>
<tbody>
@foreach($items as $i => $item)
<tr>
<tr id="row-{{ $i }}">
<td style="color:#94a3b8;font-size:12px;">{{ $i + 1 }}</td>
<td style="font-weight:500;">{{ $item->description }}</td>
<td>
{{-- Display mode --}}
<div id="desc-display-{{ $i }}" style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
<span id="desc-text-{{ $i }}" style="font-weight:500;">{{ $item->description }}</span>
<button type="button" onclick="editDesc({{ $i }})"
title="Edit item name"
style="flex-shrink:0;background:none;border:1px solid #e2e8f0;border-radius:5px;padding:2px 6px;cursor:pointer;color:#64748b;font-size:11px;line-height:1.4;"
onmouseover="this.style.borderColor='#2563eb';this.style.color='#2563eb'"
onmouseout="this.style.borderColor='#e2e8f0';this.style.color='#64748b'">
Edit
</button>
<span id="desc-adj-{{ $i }}" style="display:none;font-size:10px;font-weight:700;background:#fef3c7;color:#92400e;padding:1px 6px;border-radius:4px;border:1px solid #fde68a;">adjusted</span>
</div>
{{-- Edit mode --}}
<div id="desc-edit-{{ $i }}" style="display:none;align-items:center;gap:6px;">
<input type="text" id="desc-input-{{ $i }}"
value="{{ $item->description }}"
style="flex:1;min-width:0;padding:5px 8px;border:1.5px solid #2563eb;border-radius:6px;font-size:13px;font-weight:500;outline:none;width:auto;">
<button type="button" onclick="saveDesc({{ $i }}, {{ json_encode($item->description) }})"
style="flex-shrink:0;background:#2563eb;color:#fff;border:none;border-radius:5px;padding:4px 8px;cursor:pointer;font-size:12px;font-weight:700;"></button>
<button type="button" onclick="cancelDesc({{ $i }})"
style="flex-shrink:0;background:#f1f5f9;color:#64748b;border:1px solid #e2e8f0;border-radius:5px;padding:4px 8px;cursor:pointer;font-size:12px;"></button>
</div>
{{-- Hidden field submitted with form --}}
<input type="hidden" name="items[{{ $i }}][supplier_description]" id="desc-hidden-{{ $i }}" value="">
</td>
<td>{{ rtrim(rtrim(number_format((float)$item->quantity_required, 3), '0'), '.') }}</td>
<td style="color:#64748b;">{{ $item->unit ?: '—' }}</td>
{{-- N/A checkbox --}}
<td style="text-align:center;">
<input type="checkbox" name="items[{{ $i }}][not_available]" id="na-{{ $i }}" value="1"
onchange="toggleNA({{ $i }}, {{ (float)$item->quantity_required }})"
style="width:16px;height:16px;accent-color:#dc2626;cursor:pointer;">
</td>
{{-- VAT checkbox --}}
<td style="text-align:center;">
@if($vatRate > 0)
<input type="checkbox" name="items[{{ $i }}][is_vatable]" value="1"
<input type="checkbox" name="items[{{ $i }}][is_vatable]" id="vat-cb-{{ $i }}" value="1"
onchange="calcRow({{ $i }}, {{ (float)$item->quantity_required }})"
style="width:16px;height:16px;accent-color:#2563eb;cursor:pointer;">
@else
@ -100,7 +133,7 @@
@endif
</td>
<td style="text-align:right;">
<input type="number" class="price" name="items[{{ $i }}][unit_price]"
<input type="number" class="price" id="price-{{ $i }}" name="items[{{ $i }}][unit_price]"
min="0" step="0.001" required placeholder="0.000"
oninput="calcRow({{ $i }}, {{ (float)$item->quantity_required }})">
</td>
@ -110,15 +143,15 @@
</tbody>
<tfoot>
<tr style="background:#f8fafc;">
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;">Subtotal:</td>
<td colspan="7" style="text-align:right;font-size:13px;color:#475569;">Subtotal:</td>
<td style="text-align:right;font-size:14px;color:#475569;" id="subtotal-row">BD 0.000</td>
</tr>
<tr id="vat-tr" style="background:#fffbeb;{{ $vatRate > 0 ? '' : 'display:none;' }}">
<td colspan="6" style="text-align:right;font-size:13px;color:#92400e;">VAT ({{ $vatRate }}%):</td>
<td colspan="7" style="text-align:right;font-size:13px;color:#92400e;">VAT ({{ $vatRate }}%):</td>
<td style="text-align:right;font-size:14px;color:#92400e;" id="vat-row">BD 0.000</td>
</tr>
<tr style="background:#f8fafc;border-top:2px solid #e2e8f0;">
<td colspan="6" style="text-align:right;font-size:13px;color:#475569;font-weight:700;">Grand Total:</td>
<td colspan="7" style="text-align:right;font-size:13px;color:#475569;font-weight:700;">Grand Total:</td>
<td style="text-align:right;font-size:15px;color:#2563eb;font-weight:700;" id="grand-total">BD 0.000</td>
</tr>
</tfoot>
@ -222,13 +255,77 @@
</div>
<script>
var _vatRate = {{ $vatRate }};
var _totals = {};
var _vatable = {};
var _vatRate = {{ $vatRate }};
var _totals = {};
var _vatable = {};
var _navail = {};
// ── Description inline edit ──────────────────────────────
function editDesc(i) {
document.getElementById('desc-display-' + i).style.display = 'none';
var ed = document.getElementById('desc-edit-' + i);
ed.style.display = 'flex';
document.getElementById('desc-input-' + i).focus();
}
function saveDesc(i, original) {
var val = document.getElementById('desc-input-' + i).value.trim();
if (!val) val = original;
document.getElementById('desc-text-' + i).textContent = val;
document.getElementById('desc-hidden-' + i).value = (val !== original) ? val : '';
var adj = document.getElementById('desc-adj-' + i);
adj.style.display = (val !== original) ? 'inline-block' : 'none';
document.getElementById('desc-edit-' + i).style.display = 'none';
document.getElementById('desc-display-' + i).style.display = 'flex';
}
function cancelDesc(i) {
var original = document.getElementById('desc-text-' + i).textContent;
document.getElementById('desc-input-' + i).value = original;
document.getElementById('desc-edit-' + i).style.display = 'none';
document.getElementById('desc-display-' + i).style.display = 'flex';
}
// ── N/A toggle ───────────────────────────────────────────
function toggleNA(i, qty) {
var naCb = document.getElementById('na-' + i);
var priceIn = document.getElementById('price-' + i);
var vatCb = document.getElementById('vat-cb-' + i);
var totEl = document.getElementById('tot-' + i);
var row = document.getElementById('row-' + i);
var na = naCb.checked;
_navail[i] = na;
if (na) {
priceIn.value = '';
priceIn.disabled = true;
priceIn.removeAttribute('required');
priceIn.style.opacity = '.35';
if (vatCb) { vatCb.checked = false; vatCb.disabled = true; vatCb.style.opacity = '.35'; }
totEl.innerHTML = '<span style="font-size:11px;font-weight:700;color:#dc2626;background:#fef2f2;padding:2px 7px;border-radius:4px;">Not available</span>';
row.style.opacity = '.6';
_totals[i] = 0;
_vatable[i] = false;
} else {
priceIn.disabled = false;
priceIn.setAttribute('required', '');
priceIn.style.opacity = '1';
if (vatCb) { vatCb.disabled = false; vatCb.style.opacity = '1'; }
totEl.textContent = '—';
row.style.opacity = '1';
_totals[i] = 0;
_vatable[i] = false;
calcRow(i, qty);
return;
}
recalcTotals();
}
// ── Price / VAT calculation ──────────────────────────────
function calcRow(i, qty) {
var inp = document.querySelector('input[name="items[' + i + '][unit_price]"]');
var cb = document.querySelector('input[name="items[' + i + '][is_vatable]"]');
if (_navail[i]) return;
var inp = document.getElementById('price-' + i);
var cb = document.getElementById('vat-cb-' + i);
var up = parseFloat(inp ? inp.value : 0) || 0;
var tot = Math.round(up * qty * 1000) / 1000;
_totals[i] = tot;
@ -242,6 +339,7 @@ function recalcTotals() {
var subtotal = 0;
var vatAmount = 0;
Object.keys(_totals).forEach(function(i) {
if (_navail[i]) return;
subtotal += _totals[i];
if (_vatable[i] && _vatRate > 0) {
vatAmount += Math.round(_totals[i] * _vatRate / 100 * 1000) / 1000;