Faza 8: PDF generation pentru fișa lucru (DomPDF)

- barryvdh/laravel-dompdf instalat
- WorkOrderPdfService: încarcă WO cu toate relațiile (works/parts/payments),
  embed-ează logo ca data URI, foloseste theme_color din settings
- Blade template /resources/views/pdf/work-order.blade.php:
  - Header cu logo + date companie + nr fișă + data
  - Box-uri client + auto (kilometraj/VIN/plate)
  - Plângere + diagnostic
  - Tabel manopere (h, preț/h, total) cu maistru pe fiecare rând
  - Tabel piese (cod, brand, qty, preț, total)
  - Box total cu discount + plăți efectuate + rest de achitat
  - Block recomandări cu fundal galben (warning)
  - Linii semnătură client + maistru
  - Footer cu timestamp generare
- Action 'PDF' (icon descărcare) pe rând în lista de WO
- Action 'Descarcă PDF' în header-ul paginii Edit WO
This commit is contained in:
2026-05-07 13:01:42 +00:00
parent f1d196f018
commit 19a7afac27
6 changed files with 904 additions and 2 deletions
@@ -134,6 +134,19 @@ class WorkOrderResource extends Resource
->options(fn () => User::pluck('name', 'id')), ->options(fn () => User::pluck('name', 'id')),
]) ])
->actions([ ->actions([
Actions\Action::make('pdf')
->label('PDF')
->icon('heroicon-m-document-arrow-down')
->color('gray')
->action(function (WorkOrder $r) {
$svc = app(\App\Services\WorkOrderPdfService::class);
$pdf = $svc->generate($r);
$filename = $svc->filename($r);
return response()->streamDownload(
fn () => print($pdf->output()),
$filename
);
}),
Actions\EditAction::make(), Actions\EditAction::make(),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]) ])
@@ -3,6 +3,8 @@
namespace App\Filament\Tenant\Resources\WorkOrderResource\Pages; namespace App\Filament\Tenant\Resources\WorkOrderResource\Pages;
use App\Filament\Tenant\Resources\WorkOrderResource; use App\Filament\Tenant\Resources\WorkOrderResource;
use App\Models\Tenant\WorkOrder;
use App\Services\WorkOrderPdfService;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
@@ -12,6 +14,22 @@ class EditWorkOrder extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [Actions\DeleteAction::make()]; return [
Actions\Action::make('pdf')
->label('Descarcă PDF')
->icon('heroicon-m-document-arrow-down')
->color('gray')
->action(function () {
/** @var WorkOrder $wo */
$wo = $this->record;
$svc = app(WorkOrderPdfService::class);
$pdf = $svc->generate($wo);
return response()->streamDownload(
fn () => print($pdf->output()),
$svc->filename($wo)
);
}),
Actions\DeleteAction::make(),
];
} }
} }
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace App\Services;
use App\Models\Tenant\WorkOrder;
use Barryvdh\DomPDF\Facade\Pdf;
class WorkOrderPdfService
{
public function generate(WorkOrder $wo): \Barryvdh\DomPDF\PDF
{
$wo->load(['client', 'vehicle', 'master', 'works.master', 'parts', 'payments']);
$company = $wo->company ?: \App\Models\Central\Company::find($wo->company_id);
$themeColor = $company?->settings['theme_color'] ?? '#3B82F6';
$currency = $company?->settings['currency'] ?? 'MDL';
$worksTotal = (float) $wo->works->sum('total');
$partsTotal = (float) $wo->parts->sum('total');
$paid = (float) $wo->payments->sum('amount');
$balance = max(0, (float) $wo->total - $paid);
// Embed logo as data URI (DomPDF can't fetch external URLs reliably).
$logoData = null;
if ($company && ($logoMedia = $company->getFirstMedia('logo'))) {
try {
$path = $logoMedia->getPath();
if (file_exists($path)) {
$mime = mime_content_type($path) ?: 'image/png';
$logoData = 'data:' . $mime . ';base64,' . base64_encode(file_get_contents($path));
}
} catch (\Throwable $e) {
$logoData = null;
}
}
return Pdf::loadView('pdf.work-order', [
'wo' => $wo,
'company' => $company,
'themeColor' => $themeColor,
'currency' => $currency,
'worksTotal' => $worksTotal,
'partsTotal' => $partsTotal,
'paid' => $paid,
'balance' => $balance,
'logoData' => $logoData,
])->setPaper('A4');
}
public function filename(WorkOrder $wo): string
{
return 'WO-' . $wo->number . '-' . optional($wo->opened_at)->format('Ymd') . '.pdf';
}
}
+1
View File
@@ -7,6 +7,7 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"filament/filament": "^5.6", "filament/filament": "^5.6",
"filament/spatie-laravel-media-library-plugin": "^5.6", "filament/spatie-laravel-media-library-plugin": "^5.6",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
Generated
+523 -1
View File
@@ -4,8 +4,85 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5125ba119dd13de03ede18bda2cbaae5", "content-hash": "9abf8007c46bb50da99790f38e3cc040",
"packages": [ "packages": [
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12|^13.0",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2026-02-21T08:51:10+00:00"
},
{ {
"name": "blade-ui-kit/blade-heroicons", "name": "blade-ui-kit/blade-heroicons",
"version": "2.7.0", "version": "2.7.0",
@@ -871,6 +948,161 @@
], ],
"time": "2024-02-05T11:56:58+00:00" "time": "2024-02-05T11:56:58+00:00"
}, },
{
"name": "dompdf/dompdf",
"version": "v3.1.5",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.5"
},
"time": "2026-03-03T13:54:37+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
},
"time": "2026-01-20T14:10:26+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4 || ^9.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
},
"time": "2026-01-02T16:01:13+00:00"
},
{ {
"name": "dragonmantank/cron-expression", "name": "dragonmantank/cron-expression",
"version": "v3.6.0", "version": "v3.6.0",
@@ -3722,6 +3954,73 @@
], ],
"time": "2026-04-11T18:38:28+00:00" "time": "2026-04-11T18:38:28+00:00"
}, },
{
"name": "masterminds/html5",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
},
"time": "2025-07-25T09:04:22+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "3.10.0", "version": "3.10.0",
@@ -5430,6 +5729,86 @@
], ],
"time": "2026-03-19T10:36:26+00:00" "time": "2026-03-19T10:36:26+00:00"
}, },
{
"name": "sabberworm/php-css-parser",
"version": "v9.3.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.32 || 2.1.32",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.2.8",
"rector/type-perfect": "1.0.0 || 2.1.0",
"squizlabs/php_codesniffer": "4.0.1",
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.4.x-dev"
}
},
"autoload": {
"files": [
"src/Rule/Rule.php",
"src/RuleSet/RuleContainer.php"
],
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0"
},
"time": "2026-03-03T17:31:43+00:00"
},
{ {
"name": "scrivo/highlight.php", "name": "scrivo/highlight.php",
"version": "v9.18.1.10", "version": "v9.18.1.10",
@@ -8996,6 +9375,149 @@
], ],
"time": "2026-03-30T13:44:50+00:00" "time": "2026-03-30T13:44:50+00:00"
}, },
{
"name": "thecodingmachine/safe",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^10",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"files": [
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rnp.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2026-02-04T18:08:13+00:00"
},
{ {
"name": "tijsverkoyen/css-to-inline-styles", "name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0", "version": "v2.4.0",
+295
View File
@@ -0,0 +1,295 @@
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<title>{{ $wo->number }} Fișă lucru</title>
<style>
* { box-sizing: border-box; }
body {
font-family: DejaVu Sans, sans-serif;
font-size: 11px;
color: #1f2937;
margin: 0; padding: 24px;
}
h1, h2, h3 { margin: 0; }
.row { display: table; width: 100%; }
.col { display: table-cell; vertical-align: top; }
.col-half { width: 50%; }
.col-third { width: 33.33%; }
.header { border-bottom: 2px solid {{ $themeColor }}; padding-bottom: 12px; margin-bottom: 16px; }
.brand { font-size: 18px; font-weight: 700; color: {{ $themeColor }}; }
.brand-sub { font-size: 11px; color: #6b7280; margin-top: 4px; }
.doc-title {
text-align: right;
font-size: 22px; font-weight: 700;
color: {{ $themeColor }};
}
.doc-meta {
text-align: right;
font-size: 11px; color: #6b7280; margin-top: 4px;
}
.box {
border: 1px solid #e5e7eb; border-radius: 6px;
padding: 12px; margin-bottom: 12px;
}
.box h3 {
font-size: 12px; text-transform: uppercase;
color: #6b7280; margin-bottom: 8px;
border-bottom: 1px solid #f3f4f6; padding-bottom: 4px;
}
.kv { font-size: 11px; line-height: 1.6; }
.kv b { display: inline-block; min-width: 90px; color: #6b7280; font-weight: 500; }
table.items {
width: 100%; border-collapse: collapse;
margin: 8px 0 12px;
}
table.items th {
background: #f9fafb;
text-align: left; padding: 6px 8px;
font-size: 10px; text-transform: uppercase;
color: #6b7280;
border-bottom: 2px solid #e5e7eb;
}
table.items th.r, table.items td.r { text-align: right; }
table.items td {
padding: 6px 8px;
border-bottom: 1px solid #f3f4f6;
font-size: 11px;
}
table.items tfoot td {
font-weight: 700;
border-top: 2px solid #e5e7eb;
border-bottom: none;
}
.total-box {
margin-top: 16px;
padding: 12px;
background: #f9fafb;
border-radius: 6px;
text-align: right;
}
.total-line { font-size: 11px; color: #6b7280; padding: 2px 0; }
.total-line.grand {
font-size: 16px; font-weight: 700;
color: {{ $themeColor }};
border-top: 1px solid #e5e7eb;
margin-top: 6px; padding-top: 8px;
}
.recommendations {
background: #fef3c7; border-left: 4px solid #f59e0b;
padding: 10px 12px; margin: 12px 0;
font-size: 11px;
}
.recommendations h3 {
color: #92400e; font-size: 11px;
margin-bottom: 4px; text-transform: uppercase;
}
.signatures {
margin-top: 24px;
}
.sig-cell {
width: 50%;
padding: 8px 16px;
font-size: 10px;
color: #6b7280;
}
.sig-line {
border-bottom: 1px solid #1f2937;
height: 28px;
}
.footer {
margin-top: 16px; padding-top: 8px;
border-top: 1px solid #f3f4f6;
font-size: 9px; color: #9ca3af;
text-align: center;
}
</style>
</head>
<body>
<div class="header">
<div class="row">
<div class="col col-half">
@if (!empty($logoData))
<img src="{{ $logoData }}" style="max-height: 50px; max-width: 200px;" alt="logo">
<div class="brand-sub">{{ $company->display_name ?? $company->name }}</div>
@else
<div class="brand">{{ $company->display_name ?? $company->name }}</div>
@endif
@if ($company->city)
<div class="brand-sub">{{ $company->city }}</div>
@endif
@if ($company->phone)
<div class="brand-sub">📞 {{ $company->phone }}</div>
@endif
@if ($company->email)
<div class="brand-sub"> {{ $company->email }}</div>
@endif
</div>
<div class="col col-half">
<div class="doc-title">FIȘĂ DE LUCRU</div>
<div class="doc-meta">Nr.: <b>{{ $wo->number }}</b></div>
<div class="doc-meta">Data deschiderii: <b>{{ $wo->opened_at?->format('d.m.Y') }}</b></div>
@if ($wo->closed_at)
<div class="doc-meta">Data închiderii: <b>{{ $wo->closed_at->format('d.m.Y') }}</b></div>
@endif
<div class="doc-meta">
Status: <b>{{ \App\Models\Tenant\WorkOrder::STATUSES[$wo->status] ?? $wo->status }}</b>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col col-half" style="padding-right: 8px;">
<div class="box">
<h3>Client</h3>
<div class="kv">
<div><b>Nume:</b> {{ $wo->client?->name ?? '—' }}</div>
<div><b>Telefon:</b> {{ $wo->client?->phone ?? '—' }}</div>
@if ($wo->client?->email)
<div><b>Email:</b> {{ $wo->client->email }}</div>
@endif
</div>
</div>
</div>
<div class="col col-half" style="padding-left: 8px;">
<div class="box">
<h3>Auto</h3>
<div class="kv">
<div><b>Marca/Model:</b> {{ $wo->vehicle?->make }} {{ $wo->vehicle?->model }} {{ $wo->vehicle?->year }}</div>
<div><b>Nr.:</b> {{ $wo->vehicle?->plate ?? '—' }}</div>
@if ($wo->vehicle?->vin)
<div><b>VIN:</b> {{ $wo->vehicle->vin }}</div>
@endif
<div><b>Km la intrare:</b> {{ $wo->mileage_in ? number_format($wo->mileage_in, 0, '.', ' ') : '—' }}</div>
@if ($wo->mileage_out)
<div><b>Km la ieșire:</b> {{ number_format($wo->mileage_out, 0, '.', ' ') }}</div>
@endif
</div>
</div>
</div>
</div>
@if ($wo->complaint || $wo->diagnosis)
<div class="box">
@if ($wo->complaint)
<h3>Plângere client</h3>
<div style="margin-bottom: 8px;">{{ $wo->complaint }}</div>
@endif
@if ($wo->diagnosis)
<h3>Diagnostic</h3>
<div>{{ $wo->diagnosis }}</div>
@endif
</div>
@endif
@if ($wo->works->isNotEmpty())
<h3 style="font-size: 12px; margin: 12px 0 4px; text-transform: uppercase; color: {{ $themeColor }};">Manopere</h3>
<table class="items">
<thead>
<tr>
<th style="width: 50%;">Denumire</th>
<th class="r" style="width: 12%;">Ore</th>
<th class="r" style="width: 18%;">Preț/h</th>
<th class="r" style="width: 20%;">Total</th>
</tr>
</thead>
<tbody>
@foreach ($wo->works as $w)
<tr>
<td>{{ $w->name }}@if ($w->master?->name) <span style="color:#9ca3af;"> {{ $w->master->name }}</span>@endif</td>
<td class="r">{{ number_format((float) $w->hours, 2) }}</td>
<td class="r">{{ number_format((float) $w->price_per_hour, 2, '.', ' ') }}</td>
<td class="r"><b>{{ number_format((float) $w->total, 2, '.', ' ') }}</b></td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="3" class="r">Subtotal manopere:</td>
<td class="r">{{ number_format((float) $worksTotal, 2, '.', ' ') }} {{ $currency }}</td>
</tr>
</tfoot>
</table>
@endif
@if ($wo->parts->isNotEmpty())
<h3 style="font-size: 12px; margin: 12px 0 4px; text-transform: uppercase; color: {{ $themeColor }};">Piese</h3>
<table class="items">
<thead>
<tr>
<th style="width: 40%;">Denumire</th>
<th style="width: 18%;">Cod</th>
<th class="r" style="width: 10%;">Cant.</th>
<th class="r" style="width: 14%;">Preț</th>
<th class="r" style="width: 18%;">Total</th>
</tr>
</thead>
<tbody>
@foreach ($wo->parts as $p)
<tr>
<td>{{ $p->name }}@if ($p->brand) <span style="color:#9ca3af;">({{ $p->brand }})</span>@endif</td>
<td>{{ $p->article ?? '—' }}</td>
<td class="r">{{ number_format((float) $p->qty, 2) }} {{ $p->unit }}</td>
<td class="r">{{ number_format((float) $p->sell_price, 2, '.', ' ') }}</td>
<td class="r"><b>{{ number_format((float) $p->total, 2, '.', ' ') }}</b></td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr>
<td colspan="4" class="r">Subtotal piese:</td>
<td class="r">{{ number_format((float) $partsTotal, 2, '.', ' ') }} {{ $currency }}</td>
</tr>
</tfoot>
</table>
@endif
<div class="total-box">
@if ($wo->discount_pct > 0)
<div class="total-line">Subtotal: {{ number_format((float) ($worksTotal + $partsTotal), 2, '.', ' ') }} {{ $currency }}</div>
<div class="total-line">Discount: -{{ $wo->discount_pct }}%</div>
@endif
<div class="total-line grand">
TOTAL DE PLATĂ: {{ number_format((float) $wo->total, 2, '.', ' ') }} {{ $currency }}
</div>
@if ($paid > 0)
<div class="total-line">Achitat: {{ number_format($paid, 2, '.', ' ') }} {{ $currency }}</div>
<div class="total-line"><b>Rest de achitat: {{ number_format($balance, 2, '.', ' ') }} {{ $currency }}</b></div>
@endif
</div>
@if ($wo->recommendations)
<div class="recommendations">
<h3> Recomandări</h3>
<div>{{ $wo->recommendations }}</div>
</div>
@endif
<div class="signatures">
<table style="width: 100%;">
<tr>
<td class="sig-cell">
<div>Client (nume, semnătură):</div>
<div class="sig-line"></div>
</td>
<td class="sig-cell">
<div>Maistru / Recepție:</div>
<div class="sig-line"></div>
</td>
</tr>
</table>
</div>
<div class="footer">
Document generat automat {{ now()->format('d.m.Y H:i') }} {{ $company->display_name ?? $company->name }}
</div>
</body>
</html>