140 lines
8.7 KiB
PHP
140 lines
8.7 KiB
PHP
<div class="mx-auto max-w-[1120px] space-y-5">
|
|
<div class="animate-rise">
|
|
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('new_invoice.title') }}</h1>
|
|
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('new_invoice.subtitle') }}</p>
|
|
</div>
|
|
|
|
{{-- Said here rather than only thrown at the click: the company profile is
|
|
another page, and finding out at the last step costs the whole form. --}}
|
|
@if ($missing !== [])
|
|
<x-ui.alert variant="warning">
|
|
{{ __('new_invoice.missing_profile', ['fields' => collect($missing)->map(fn ($f) => __('finance.field.'.$f))->join(', ')]) }}
|
|
<a href="{{ route('admin.finance') }}" class="font-semibold underline">{{ __('new_invoice.to_finance') }}</a>
|
|
</x-ui.alert>
|
|
@endif
|
|
|
|
@error('lines')<x-ui.alert variant="error">{{ $message }}</x-ui.alert>@enderror
|
|
|
|
<div class="grid gap-5 lg:grid-cols-[1fr_320px] lg:items-start">
|
|
<div class="space-y-5">
|
|
{{-- Who and out of which series --}}
|
|
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
|
|
<h2 class="font-semibold text-ink">{{ __('new_invoice.who_title') }}</h2>
|
|
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="customer">{{ __('new_invoice.customer') }}</label>
|
|
<select id="customer" wire:model.live="customerUuid"
|
|
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body">
|
|
<option value="">{{ __('new_invoice.pick_customer') }}</option>
|
|
@foreach ($customers as $c)
|
|
<option value="{{ $c->uuid }}">{{ $c->name }} · {{ $c->email }}</option>
|
|
@endforeach
|
|
</select>
|
|
@error('customerUuid')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="text-sm font-medium text-body" for="series">{{ __('new_invoice.series') }}</label>
|
|
<select id="series" wire:model="seriesId"
|
|
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 text-sm text-body">
|
|
<option value="">{{ __('new_invoice.series_default') }}</option>
|
|
@foreach ($series as $s)
|
|
<option value="{{ $s->id }}">{{ $s->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
{{-- The whole point of routing this through the same
|
|
door: one series, one consecutive numbering, no
|
|
document written outside it. --}}
|
|
<p class="mt-1 text-xs text-muted">{{ __('new_invoice.series_hint') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- The lines. A form that IS the page (R20): this creates a record,
|
|
it does not edit one in a table row. --}}
|
|
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:60ms]">
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<h2 class="font-semibold text-ink">{{ __('new_invoice.lines_title') }}</h2>
|
|
<x-ui.button wire:click="addLine" variant="secondary" size="sm" class="ml-auto">
|
|
<x-ui.icon name="plus" class="size-4" />{{ __('new_invoice.add_line') }}
|
|
</x-ui.button>
|
|
</div>
|
|
|
|
@foreach ($lines as $i => $line)
|
|
<div wire:key="line-{{ $i }}" class="rounded-md border border-line bg-surface-2 p-4">
|
|
<div class="grid gap-3 sm:grid-cols-[1fr_88px_88px_120px_auto] sm:items-end">
|
|
<x-ui.input :name="'lines.'.$i.'.description'" wire:model.blur="lines.{{ $i }}.description"
|
|
:label="__('new_invoice.col_description')" :placeholder="__('new_invoice.description_hint')" />
|
|
<x-ui.input :name="'lines.'.$i.'.quantity'" wire:model.live.debounce.400ms="lines.{{ $i }}.quantity"
|
|
:label="__('new_invoice.col_quantity')" inputmode="decimal" />
|
|
<x-ui.input :name="'lines.'.$i.'.unit'" wire:model.blur="lines.{{ $i }}.unit"
|
|
:label="__('new_invoice.col_unit')" :placeholder="__('new_invoice.unit_hint')" />
|
|
<x-ui.input :name="'lines.'.$i.'.price'" wire:model.live.debounce.400ms="lines.{{ $i }}.price"
|
|
:label="__('new_invoice.col_price')" inputmode="decimal" />
|
|
<button type="button" wire:click="removeLine({{ $i }})"
|
|
@disabled(count($lines) <= 1)
|
|
class="mb-1.5 rounded-md p-2 text-muted transition-colors hover:text-danger disabled:opacity-40"
|
|
aria-label="{{ __('new_invoice.remove_line') }}">
|
|
<x-ui.icon name="trash-2" class="size-4" />
|
|
</button>
|
|
</div>
|
|
<div class="mt-3">
|
|
<x-ui.input :name="'lines.'.$i.'.detail'" wire:model.blur="lines.{{ $i }}.detail"
|
|
:label="__('new_invoice.col_detail')" :placeholder="__('new_invoice.detail_hint')" />
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- What the document will say, before it exists. Computed by the same
|
|
InvoiceMath the invoice itself uses — a preview doing its own
|
|
arithmetic would be a second answer, and the first one anybody
|
|
notices differs is on a document that cannot be changed. --}}
|
|
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:120ms] lg:sticky lg:top-6">
|
|
<h2 class="font-semibold text-ink">{{ __('new_invoice.preview_title') }}</h2>
|
|
|
|
<dl class="space-y-2 text-sm">
|
|
<div class="flex justify-between gap-3">
|
|
<dt class="text-muted">{{ __('new_invoice.net') }}</dt>
|
|
<dd class="font-mono tabular-nums text-ink">{{ Number::currency($totals['net'] / 100, in: $currency, locale: app()->getLocale()) }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-3">
|
|
<dt class="text-muted">
|
|
{{ __('new_invoice.tax') }}
|
|
<span class="font-mono text-xs">{{ $treatment->percentLabel() }} %</span>
|
|
</dt>
|
|
<dd class="font-mono tabular-nums text-ink">{{ Number::currency($totals['tax'] / 100, in: $currency, locale: app()->getLocale()) }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-3 border-t border-line pt-2">
|
|
<dt class="font-medium text-ink">{{ __('new_invoice.gross') }}</dt>
|
|
<dd class="font-mono font-semibold tabular-nums text-ink">{{ Number::currency($totals['gross'] / 100, in: $currency, locale: app()->getLocale()) }}</dd>
|
|
</div>
|
|
</dl>
|
|
|
|
@if ($treatment->reverseCharge)
|
|
{{-- Shown before issuing, not discovered on the PDF. --}}
|
|
<x-ui.alert variant="info">{{ __('invoice.reverse_charge') }}</x-ui.alert>
|
|
@endif
|
|
|
|
<p class="text-xs leading-relaxed text-muted">{{ __('new_invoice.final_note') }}</p>
|
|
|
|
{{-- :disabled, not @disabled: the directive compiles to a raw
|
|
`if … echo … endif` and a Blade COMPONENT tag puts that inside
|
|
its attribute bag, where it is a PHP syntax error rather than an
|
|
attribute. On a plain element (the line's own remove button) it
|
|
is fine. --}}
|
|
<x-ui.button wire:click="issue" variant="primary" class="w-full"
|
|
wire:loading.attr="disabled" wire:target="issue"
|
|
:disabled="$missing !== [] || $customer === null">
|
|
{{ __('new_invoice.issue') }}
|
|
</x-ui.button>
|
|
|
|
<a href="{{ route('admin.invoices') }}" class="block text-center text-xs font-semibold text-muted hover:text-ink">
|
|
{{ __('new_invoice.cancel') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|