Smart order builder
← Back to purchases
@if ($savedPurchaseOrderId)
Purchase order {{ $savedPurchaseOrderNumber }} saved. The Excel file has been downloaded. Open the order · Edit it
@endif

Order details

Set these once — they apply whichever way you build the order below.

Paste the order

Paste the rate list order the way you'd write it down — one product and quantity per line, blank lines between groups, an optional group label like "Serums" or "Creams". Place Order matches every line to a product; you review and fix anything before it's saved.

@include('livewire.partials.validate-items-button')
@if (! empty($draftLines) && ! $showModal) @endif

Or build from a target amount

Pick the items you need and say roughly what you want to spend. Quantities are allocated in full boxes and stay editable before anything is saved.

Items @if (count($selected)) {{ count($selected) }} selected @endif

@if (count($selected)) @endif
@if (count($selected))
@foreach ($selected as $id) @php $product = $this->productMap()->get((int) $id); @endphp @continue(! $product) {{ $product->name }} @endforeach
@endif @php $minimum = $this->selectionMinimum(); $targetFloat = (float) $targetAmount; $minVariancePct = $targetFloat > 0 ? (($minimum['total'] - $targetFloat) / $targetFloat * 100) : null; @endphp @if ($minimum['count'] > 0 && $targetFloat > 0)
$minVariancePct > 0 && $minVariancePct <= 50, 'bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-300' => $minVariancePct > 50, 'bg-gray-50 dark:bg-gray-900/40 text-gray-500 dark:text-gray-400' => $minVariancePct <= 0, ])> @if ($minVariancePct > 0) Minimum possible spend for {{ $minimum['count'] }} item{{ $minimum['count'] === 1 ? '' : 's' }} — one box each — is ₹{{ number_format($minimum['total'], 2) }}, already {{ number_format($minVariancePct, 0) }}% over your ₹{{ number_format($targetFloat, 2) }} target. Deselect a few items or raise the target before building the order. @else Minimum for {{ $minimum['count'] }} item{{ $minimum['count'] === 1 ? '' : 's' }} — one box each — is ₹{{ number_format($minimum['total'], 2) }}, within your ₹{{ number_format($targetFloat, 2) }} target. @endif
@endif
@if ($showModal)

Review order — {{ count($draftLines) }} line{{ count($draftLines) === 1 ? '' : 's' }}

@if ($this->unresolvedCount() > 0)
{{ $this->unresolvedCount() }} line(s) below still need a product and/or a quantity greater than zero before you can generate.
@endif @php $totals = $this->totals(); $freeTotal = array_sum(array_column($this->schemePreview(), 'free')); @endphp @if ($freeTotal > 0)
{{ $freeTotal }} free unit{{ $freeTotal === 1 ? '' : 's' }} are due under the supplier's scheme once the goods arrive. Record them on the order after receiving, so stock and the scheme benefit stay right.
@endif @if ($totals['target'] > 0)

abs($totals['variance_pct']) <= 2, 'text-amber-600 dark:text-amber-400' => abs($totals['variance_pct']) > 2, ])> {{ $totals['variance'] >= 0 ? '+' : '−' }}₹{{ number_format(abs($totals['variance']), 2) }} ({{ number_format(abs($totals['variance_pct']), 2) }}%) against target

@endif
@if ($reviewSearch !== '') {{ count($this->visibleLineIndices()) }} of {{ count($draftLines) }} shown @endif
{{-- Not overflow-x-auto: the per-row product picker is an absolutely-positioned dropdown, and overflow-x set to anything but visible clips it (setting -x forces -y to auto too, per the CSS spec, even though only -x was set here) - see the identical note on smart-sales-entry.blade.php's review modal. This modal's own overflow-y-auto (on the wrapper above, needed so a long order still scrolls) can still clip a dropdown opened on a row right at the bottom edge of the current scroll position - scroll the row into better view first if that happens. --}}
@php $priced = $this->pricedDraftLines(); $visible = $this->visibleLineIndices(); $schemes = $this->schemePreview(); @endphp @foreach ($draftLines as $i => $line) @continue(! in_array($i, $visible, true)) @php $p = $priced[$i] ?? null; $needsAttention = empty($line['product_id']) || (int) ($line['quantity'] ?? 0) <= 0; @endphp $needsAttention])> @endforeach @if (empty($visible)) @endif
Typed as Group Matched product Box GST Rate Quantity Scheme Value
{{ $line['raw_text'] }} {{ $line['section_label'] ?? '—' }} @if ($line['status'] === 'exact') Matched @elseif ($line['status'] === 'fuzzy') Check match{{ isset($line['candidates'][0]['score']) ? ' ('.$line['candidates'][0]['score'].'%)' : '' }} @elseif ($line['status'] === 'auto') Auto-suggested @else Select a product @endif @if (count($line['candidates']) > 1)
@foreach (array_slice($line['candidates'], 1) as $c) @endforeach
@endif
{{ $p['box_quantity'] ?? '—' }} {{ $p ? rtrim(rtrim(number_format($p['gst_rate'], 2), '0'), '.').'%' : '—' }} {{ $p ? number_format($p['rate'], 2) : '—' }}
@php $scheme = $schemes[$i] ?? null; @endphp @if ($scheme && $scheme['free'] > 0) +{{ $scheme['free'] }} free @endif @if ($scheme && $scheme['next'])
{{ $scheme['next'] }}
@endif
{{ $p ? number_format($p['inclusive_total'], 2) : '—' }}
No lines match "{{ $reviewSearch }}".
Total quantity
{{ $totals['quantity'] }} PCS
Taxable value
{{ number_format($totals['taxable'], 2) }}
GST
{{ number_format($totals['tax_total'], 2) }}
@if ($totals['target'] > 0)
Target
{{ number_format($totals['target'], 2) }}
@endif
Order total
₹{{ number_format($totals['total'], 2) }}
@endif