@php $steps = [ 'source' => __('Upload'), 'configure' => __('Map columns'), 'preview' => __('Review'), 'done' => __('Done'), ]; $order = ['source', 'configure', 'preview', 'done']; $currentIndex = array_search($stage, $order, true); $statusBadge = [ 'DRAFT' => 'slate', 'SENT' => 'sky', 'PARTIAL' => 'amber', 'PAID' => 'emerald', 'OVERDUE' => 'rose', 'VOID' => 'slate', ]; @endphp
{{ __('File read, :n columns detected.', ['n' => count($headers)]) }}
@endif {{-- Or load straight from a shared Google Sheet --}}{{ __('…or load from Google Sheets') }}
{{ __('Paste a shared Google Sheet link to pull it in directly, then pick the tab and map columns as usual.') }}
{{ __('Google Sheet loaded, :n columns detected.', ['n' => count($headers)]) }}
@endif{{ __('The sheet must be shared as “Anyone with the link → Viewer”.') }}
{{ __('What gets created') }}
{{ __('Each row is one line item. Rows are grouped into documents (by document number, customer + date, or one per row) and each becomes a Customer → Invoice → Job, with Products created from any new item names.') }}
{{ __('On the next step you map your columns, only Item Name is required; Date, Customer, Qty, Unit Price, Status, Currency and discounts are optional.') }} {{ __('Download the CSV template (top-right) for the exact column layout.') }}
{{ __('Got a workbook where each tab is a single job, like the "April costing" sheets, with one Item / Qty / Rate / Discount / Total table per tab? Tick the box above; the tab name becomes the customer and costing-only tabs are skipped.') }}
{{ __('Tab holding the monthly P&L ledger.') }}
{{ __('Calendar year the month columns belong to.') }}
{{ __('Sync live from Google Sheets') }}
{{ __('Pulls the latest from the live sheet: new rows are added, changed rows are updated in place, and rows deleted from the sheet are removed, nothing is duplicated.') }}
{{ __('The sheet must be shared as “Anyone with the link → Viewer”. Paste the full URL or just the sheet ID.') }}
{{ __('A file import only adds new rows (existing ones are skipped). Use the live sync to also update changed rows.') }}
{{ __('What gets created') }}
{{ __('Each job → customer, invoice (revenue treated as collected), completed job, and production-cost lines routed to the right cost bucket by their COA code. Each month → operating-cost lines filed under the matching expense category, with director-funded overheads recorded as repayable director loans.') }}
{{ __('Load the costing workbook from Google Sheets') }}
{{ __('Paste the shared link to your "Semita Print Costing" workbook, then choose which tabs to import.') }}
{{ __('The sheet must be shared as “Anyone with the link → Viewer”.') }}
{{ __(':n importable tab(s) found', ['n' => count($productsSections)]) }}
{{ __('Tiers & retail details attach to products by name, they\'ll match products from this import or a previous one. Re-running updates existing records.') }}
{{ __('What gets created') }}
{{ __('Each product tab becomes real Products with an itemised bill of materials (material, ink, labour, overhead) reproducing the sheet\'s cost build-up, plus markup/buffer/risk pricing, quantity tiers, and the rate master (labour, overhead, ink). Optional tabs add the chart of accounts, asset register and startup capital.') }}
{{ __('That filename is too long to upload, Windows cannot store it. Rename the file to something short, like') }} statement.xlsx{{ __(', and choose it again.') }}
{{ __('A bank export (CSV or Excel, not PDF), the header block, running balance and fee lines are all read automatically. A cash book works too: any table with a date, a description and money-out / money-in columns, imported against the cash or wallet account it belongs to.') }}
{{ __('What each line becomes') }}
{{ __('Lines matching an existing movement on this account are marked as duplicates and excluded. The resulting balance is verified against the statement\'s closing figure.') }}
{{ __('The .json file another workspace produced on its Data Export screen. Nothing is written until you have seen the preview.') }}
{{ __('What comes across, and what does not') }}
{{ __('Importing the same file twice changes nothing: every document remembers where it came from, and one already here is recognised rather than repeated.') }}
{{ __(':n file(s) ready.', ['n' => count($pdfUploads)]) }}
@endif @endif{{ __('Pick the row that holds your column titles.') }}
| {{ $i + 1 }} | @foreach (array_slice($row, 0, 10) as $cell){{ \Illuminate\Support\Str::limit($cell, 24) }} | @endforeach
{{ __('Only Item Name is required. Leave a field blank to skip it.') }}
{{ $stat['value'] }}
{{ $stat['label'] }}
| {{ __('Revenue') }} | {{ __('Production cost') }} | {{ __('Operating cost') }} | |
|---|---|---|---|
| {{ __('Parsed') }} | {{ $fmt($recon['parsed']['revenue'] ?? 0) }} | {{ $fmt($recon['parsed']['cost'] ?? 0) }} | {{ $fmt($recon['parsed']['opex'] ?? 0) }} |
| {{ __('Sheet totals') }} | {{ $fmt($recon['sheet']['revenue'] ?? 0) }} | {{ $fmt($recon['sheet']['cost'] ?? 0) }} | {{ $fmt($recon['sheet']['opex'] ?? 0) }} |
⚠ {{ $w }}
@endforeach| {{ __('Month') }} | {{ __('Job') }} | {{ __('Customer') }} | {{ __('Revenue') }} | {{ __('Cost') }} |
|---|---|---|---|---|
| {{ $j['month'] }} |
{{ \Illuminate\Support\Str::limit($j['title'], 48) }}
@if ($j['reprints'] > 0) |
{{ \Illuminate\Support\Str::limit($j['customer'], 28) }} | {{ $fmt($j['revenue']) }} | {{ $fmt($j['cost']) }} |
{{ $statement->accountName }} @if ($statement->accountNumber) · {{ $statement->accountNumber }} @endif @if ($statement->currency) · {{ $statement->currency }} @endif
@if ($statement->periodFrom && $statement->periodTo){{ $statement->periodFrom->format('d M Y') }} → {{ $statement->periodTo->format('d M Y') }}
@endif{{ __('Opening :o + movements = :d', ['o' => number_format($statement->openingBalance ?? 0, 2), 'd' => number_format($recon['expected'], 2)]) }} @if ($recon['closing'] !== null) · {{ __('bank says :c', ['c' => number_format($recon['closing'], 2)]) }} @endif
{{ __('Transactions') }}
{{ count($bankRows) }}
{{ __('Fee lines folded in') }}
{{ count($statement->rows) - count($bankRows) }}
{{ __('Money in') }}
{{ number_format($statement->totalIn(), 2) }}
{{ __('Money out') }}
{{ number_format($statement->totalOut(), 2) }}
{{ __(':n row(s) look already recorded', ['n' => count($bankDuplicates)]) }}
{{ __('These match a movement already on this account by amount, direction and date. They start unticked so nothing is counted twice, tick one back on only if you are sure it is a separate transaction.') }}
| {{ __('Use') }} | {{ __('Date') }} | {{ __('Description') }} | {{ __('Amount') }} | {{ __('Fees') }} | {{ __('Treat as') }} | {{ __('Post to') }} |
|---|---|---|---|---|---|---|
| {{ $row->date?->format('d M Y') }} |
{{ \Illuminate\Support\Str::limit($row->description, 90) }} @if ($isDupe) |
$row->direction() === 'in', 'text-rose-700' => $row->direction() === 'out', ])> {{ $row->direction() === 'in' ? '+' : '−' }}{{ number_format($row->amount(), 2) }} | {{ $fees > 0 ? number_format($fees, 2) : '—' }} | @if (in_array($kind, $accountKinds, true)) @elseif (in_array($kind, $expenseKinds, true)) @elseif ($kind === 'receipt') @else — @endif |
{{ __(':created created · :duplicates already present · :skipped skipped', [ 'created' => $bankResult['created'], 'duplicates' => $bankResult['duplicates'], 'skipped' => $bankResult['skipped'], ]) }}
{{ __('Rows that could not be imported') }}
{{ __('Fix the cause, then re-import the same file, rows already in are recognised and left alone.') }}
{{ __(':name now reads :balance.', [ 'name' => $this->bankAccount()->name, 'balance' => number_format($this->bankAccount()->fresh()->balance, 2), ]) }} {{ __('Compare that against the closing balance printed on the statement, if they agree, the account is fully reconciled.') }}
{{ $bundlePlan['workspace'] }} {{ __('wrote this file, covering :from to :to.', [ 'from' => \Illuminate\Support\Carbon::parse($bundlePlan['window']['from'])->format('j M Y'), 'to' => \Illuminate\Support\Carbon::parse($bundlePlan['window']['to'])->format('j M Y'), ]) }} @if ($bundlePlan['manifest']['generated_by'] ?? null) {{ __('Exported by :who.', ['who' => $bundlePlan['manifest']['generated_by']]) }} @endif
{{ __('Nothing has been written yet.') }} {{ __('Untick anything that should stay out, then import. Documents already here are recognised and left alone whatever you tick.') }}
@foreach ($bundlePlan['warnings'] as $warning)
{{ __(':new new · :renumbered renumbered · :adopted already here · :lines line(s)', [ 'new' => $bundlePlan['totals'][$entity]['new'], 'renumbered' => $bundlePlan['totals'][$entity]['renumbered'], 'adopted' => $bundlePlan['totals'][$entity]['adopted'], 'lines' => $bundlePlan['totals'][$entity]['lines'], ]) }}
| {{ __('Number') }} | {{ __('Raised') }} | {{ __('Customer') }} | {{ __('Lines') }} | {{ __('Total') }} | {{ __('What happens') }} | |
|---|---|---|---|---|---|---|
| {{ $row['final_number'] }} @if ($row['final_number'] !== $row['number']) {{ __('was :number', ['number' => $row['number']]) }} @endif | {{ $row['issued_at'] ? \Illuminate\Support\Carbon::parse($row['issued_at'])->format('j M Y') : '—' }} | @if ($row['customer_id']) {{ $row['customer'] }} @else {{ $row['customer'] ?? __('nobody') }} @endif | {{ $row['lines'] }} | {{ $row['currency_code'] }} {{ number_format($row['total'], 2) }} |
|
{{ __(':imported imported · :adopted already here · :skipped left out · :lines line(s)', [ 'imported' => array_sum($bundleResult['imported']), 'adopted' => array_sum($bundleResult['adopted']), 'skipped' => array_sum($bundleResult['skipped']), 'lines' => $bundleResult['lines'], ]) }}
{{ __('Worth knowing') }}
{{ __('Fix the cause, then import the same file again, everything already in is recognised and left alone.') }}
{{ __('Some PDFs were skipped:') }}
{{ __('Sync handles existing records automatically.') }}
@else @endif{{ __('Only PAID invoices in the account currency are recorded.') }}
@endif{{ __(':n document(s) ready', ['n' => count($documents)]) }}
{{ $doc['document_number'] ?: __('(number will be generated)') }} @if ($doc['date']) · {{ $doc['date'] }} @endif @if ($doc['currency_code']) · {{ $doc['currency_code'] }} @endif
{{ __('Total') }}
{{ number_format($doc['totals']['total'] ?? 0, 2) }}
@if (($doc['cost_total'] ?? null) !== null) @php $docTotal = $doc['totals']['total'] ?? 0; $docMargin = $docTotal > 0 ? round((($docTotal - $doc['cost_total']) / $docTotal) * 100) : null; @endphp{{ __('Cost') }} {{ number_format($doc['cost_total'], 2) }}@if ($docMargin !== null) · {{ $docMargin }}%@endif
@endif| {{ __('Item') }} | {{ __('Qty') }} | {{ __('Rate') }} | {{ __('Discount') }} | {{ __('Total') }} |
|---|---|---|---|---|
|
{{ $line['name'] }}
@if (! ($m['products'][$line['name']] ?? false))
@if (! empty($line['description']))
{{ \Illuminate\Support\Str::limit($line['description'], 90) }} @endif |
{{ rtrim(rtrim(number_format($line['qty'], 2), '0'), '.') }} | {{ number_format($line['unit_price'], 2) }} | {{ number_format($line['discount'], 2) }} | {{ number_format($line['line_total'], 2) }} |
⚠ {{ $w }}
@endforeach{{ __('Your monthly jobs and operating costs are now in BillPro.') }}
{{ $stat['value'] }}
{{ $stat['label'] }}
{{ __('Notes') }}
@foreach ($pnlResult['notes'] as $n)⚠ {{ $n }}
@endforeach{{ __(':n row(s) could not be imported', ['n' => count($pnlResult['errors'])]) }}
@foreach (array_slice($pnlResult['errors'], 0, 20) as $e){{ $e }}
@endforeach{{ __('Your costing workbook is now in BillPro.') }}
{{ $stat['value'] }}
{{ $stat['label'] }}
{{ __('Notes') }}
@foreach ($productsResult['notes'] as $n)⚠ {{ $n }}
@endforeach{{ __('Your past jobs are now in BillPro.') }}
{{ $stat['value'] }}
{{ $stat['label'] }}
{{ __('Skipped') }}
@foreach ($result['skipped'] as $s){{ $s['ref'] }}, {{ $s['message'] }}
@endforeach{{ __('Errors') }}
@foreach ($result['errors'] as $e){{ $e['ref'] }}, {{ $e['message'] }}
@endforeach