Updates for designs

This commit is contained in:
David Bomba 2025-02-05 15:52:39 +11:00
commit 3dd9e36997
40 changed files with 2620 additions and 3402 deletions

View File

@ -69,9 +69,9 @@ class DesignUpdate extends Command
private function handleOnDb() private function handleOnDb()
{ {
foreach (Design::whereIsCustom(false)->get() as $design) { foreach (Design::where('is_custom', false)->get() as $design) {
$invoice_design = new \App\Services\PdfMaker\Design(strtolower($design->name));
$invoice_design->document(); $invoice_design = new \App\Services\Pdf\DesignExtractor($design->name);
$design_object = new stdClass(); $design_object = new stdClass();
$design_object->includes = $invoice_design->getSectionHTML('style'); $design_object->includes = $invoice_design->getSectionHTML('style');

View File

@ -554,9 +554,6 @@ class DesignController extends BaseController
$company = $user->getCompany(); $company = $user->getCompany();
nlog("Design Change {$company->id}");
nlog($request->all());
$design = Design::where('company_id', $company->id) $design = Design::where('company_id', $company->id)
->orWhereNull('company_id') ->orWhereNull('company_id')
->where('id', $design_id) ->where('id', $design_id)

View File

@ -21,6 +21,7 @@ use App\Jobs\Util\PreviewPdf;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Services\Pdf\PdfMock; use App\Services\Pdf\PdfMock;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\VendorHtmlEngine;
use App\Services\Pdf\PdfService; use App\Services\Pdf\PdfService;
use App\Utils\PhantomJS\Phantom; use App\Utils\PhantomJS\Phantom;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
@ -32,6 +33,7 @@ use Illuminate\Support\Facades\App;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\Traits\MakesInvoiceHtml;
use Turbo124\Beacon\Facades\LightLogs; use Turbo124\Beacon\Facades\LightLogs;
use App\Models\PurchaseOrderInvitation;
use App\Utils\Traits\Pdf\PageNumbering; use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use App\DataMapper\Analytics\LivePreview; use App\DataMapper\Analytics\LivePreview;
@ -39,7 +41,6 @@ use App\Services\Template\TemplateService;
use App\Http\Requests\Preview\ShowPreviewRequest; use App\Http\Requests\Preview\ShowPreviewRequest;
use App\Http\Requests\Preview\DesignPreviewRequest; use App\Http\Requests\Preview\DesignPreviewRequest;
use App\Http\Requests\Preview\PreviewInvoiceRequest; use App\Http\Requests\Preview\PreviewInvoiceRequest;
use App\Utils\VendorHtmlEngine;
class PreviewController extends BaseController class PreviewController extends BaseController
{ {
@ -145,99 +146,65 @@ class PreviewController extends BaseController
public function show(ShowPreviewRequest $request) public function show(ShowPreviewRequest $request)
{ {
if ($request->input('design.is_template')) { if ($request->input('design.is_template')) {
return $this->template(); return $this->template();
} }
if (request()->has('entity') && if ($request->input('entity', false) &&
request()->has('entity_id') && $request->input('entity_id', false) != '-1') {
! empty(request()->input('entity')) &&
! empty(request()->input('entity_id'))) {
if ($request->input('entity') == 'purchase_order') { $design_object = json_decode(json_encode($request->input('design')));
return app(\App\Http\Controllers\PreviewPurchaseOrderController::class)->show($request);
}
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) { if (! is_object($design_object)) {
return response()->json(['message' => ctrans('texts.invalid_design_object')], 400); return response()->json(['message' => ctrans('texts.invalid_design_object')], 400);
} }
$entity = Str::camel(request()->input('entity')); $entity = Str::camel($request->input('entity'));
$class = "App\Models\\$entity"; $class = "App\Models\\$entity";
$entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); $entity_obj = $class::whereId($this->decodePrimaryKey($request->input('entity_id')))->company()->first();
if (! $entity_obj) { if (! $entity_obj) {
return $this->blankEntity(); return $this->blankEntity();
} }
$entity_obj->load('client'); if($entity_obj->client){
$entity_obj->load('client');
$locale = $entity_obj->client->preferredLocale();
$settings = $entity_obj->client->getMergedSettings();
}
else {
$entity_obj->load('vendor');
$locale = $entity_obj->vendor->preferredLocale();
$settings = $entity_obj->vendor->getMergedSettings();
}
App::forgetInstance('translator'); App::forgetInstance('translator');
$t = app('translator'); $t = app('translator');
App::setLocale($entity_obj->client->preferredLocale()); App::setLocale($locale);
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); $t->replace(Ninja::transformTranslations($settings));
$invitation = $entity_obj->invitations()->first();
if ($entity_obj->client) { $ps = new PdfService($invitation, 'product', [
$html = new HtmlEngine($entity_obj->invitations()->first()); 'client' => $entity_obj->client ?? false,
} else { 'vendor' => $entity_obj->vendor ?? false,
$html = new VendorHtmlEngine($entity_obj->invitations()->first()); $request->input('entity')."s" => [$entity_obj],
]);
$ps->boot()
->designer
->buildFromPartials($request->design['design']);
$ps->builder
->build();
if ($request->query('html') == 'true') {
return $ps->getHtml();
} }
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); $pdf = $ps->getPdf();
$state = [
'template' => $design->elements([
'client' => $entity_obj->client,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $entity_obj->client->company->markdown_enabled,
'options' => [
'client' => $entity_obj->client ?? [],
'vendor' => $entity_obj->vendor ?? [],
request()->input('entity_type', 'invoice')."s" => [$entity_obj],
]
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
/** @var \App\Models\User $user */
$user = auth()->user();
$company = $user->company();
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle();
return response()->streamDownload(function () use ($pdf) { return response()->streamDownload(function () use ($pdf) {
echo $pdf; echo $pdf;
@ -338,200 +305,85 @@ class PreviewController extends BaseController
$t = app('translator'); $t = app('translator');
$t->replace(Ninja::transformTranslations($company->settings)); $t->replace(Ninja::transformTranslations($company->settings));
/** @var \App\Models\InvoiceInvitation $invitation */ $entity_string = 'invoice';
$invitation = InvoiceInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first();
if(request()->input('entity') == 'purchase_order') {
$invitation = PurchaseOrderInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first();
$entity_string = 'purchase_order';
}
else{
/** @var \App\Models\InvoiceInvitation $invitation */
$invitation = InvoiceInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first();
}
/* If we don't have a valid invitation in the system - create a mock using transactions */ /* If we don't have a valid invitation in the system - create a mock using transactions */
if (! $invitation) { if (! $invitation) {
return $this->mockEntity(); return $this->mockEntity();
} }
$design_object = json_decode(json_encode(request()->input('design'))); $design_object = json_decode(json_encode(request()->input('design')), true);
if (! is_object($design_object)) { if (! is_array($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400); return response()->json(['message' => 'Invalid custom design object'], 400);
} }
$html = new HtmlEngine($invitation); $ps = new PdfService($invitation, 'product', [
'client' => $invitation->client ?? false,
'vendor' => $invitation->vendor ?? false,
"{$entity_string}s" => [$invitation->{$entity_string}],
]);
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); $ps->boot()
->designer
->buildFromPartials($design_object['design']);
$state = [ $ps->builder
'template' => $design->elements([ ->build();
'client' => $invitation->invoice->client,
'entity' => $invitation->invoice,
'pdf_variables' => (array) $invitation->invoice->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $invitation->invoice->client->company->markdown_enabled,
'options' => [
'client' => $invitation->invoice->client,
'invoices' => [$invitation->invoice],
]
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {
return $maker->getCompiledHTML(); return $ps->getHtml();
} }
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { $pdf = $ps->getPdf();
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
/** @var \App\Models\User $user */ return response()->streamDownload(function () use ($pdf) {
$user = auth()->user(); echo $pdf;
}, 'preview.pdf', [
'Content-Disposition' => 'inline',
'Content-Type' => 'application/pdf',
'Cache-Control:' => 'no-cache',
]);
/** @var \App\Models\Company $company */
$company = $user->company();
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle();
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');
return $response;
} }
private function mockEntity() private function mockEntity()
{ {
/** @var \App\Models\User $user */
$start = microtime(true);
$user = auth()->user(); $user = auth()->user();
/** @var \App\Models\Company $company */ /** @var \App\Models\Company $company */
$company = $user->company(); $company = $user->company();
try { $request = request()->input('design');
DB::connection($company->db)->beginTransaction(); $request['entity_type'] = request()->input('entity', 'invoice');
/** @var \App\Models\Client $client */
$client = Client::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
]);
/** @var \App\Models\ClientContact $contact */
$contact = ClientContact::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'client_id' => $client->id,
'is_primary' => 1,
'send_email' => true,
]);
$settings = $company->settings;
/** @var \App\Models\Invoice $invoice */
$invoice = Invoice::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'client_id' => $client->id,
'terms' => $company->settings->invoice_terms,
'footer' => $company->settings->invoice_footer,
'public_notes' => 'Sample Public Notes',
]);
if ($settings->invoice_number_pattern) {
$invoice->number = $this->getFormattedEntityNumber(
$invoice,
rand(1, 9999),
$settings->counter_padding ?: 4,
$settings->invoice_number_pattern,
);
$invoice->save();
}
$invitation = InvoiceInvitation::factory()->create([
'user_id' => $user->id,
'company_id' => $company->id,
'invoice_id' => $invoice->id,
'client_contact_id' => $contact->id,
]);
$invoice->setRelation('invitations', $invitation);
$invoice->setRelation('client', $client);
$invoice->setRelation('company', $company);
$invoice->load('client.company');
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400);
}
$html = new HtmlEngine($invoice->invitations()->first());
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
$state = [
'template' => $design->elements([
'client' => $invoice->client,
'entity' => $invoice,
'pdf_variables' => (array) $settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $invoice->client->company->markdown_enabled,
'options' => [
'client' => $invoice->client,
'invoices' => [$invoice],
]
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
DB::connection($company->db)->rollBack();
} catch (\Exception $e) {
DB::connection($company->db)->rollBack();
return response()->json(['message' => $e->getMessage()], 400);
}
$pdf = (new PdfMock($request, $company))->build();
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {
return $maker->getCompiledHTML(); return $pdf->getHtml();
} }
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { $pdf = $pdf->getPdf();
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $response = Response::make($pdf, 200);
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle();
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf'); $response->header('Content-Type', 'application/pdf');
$response->header('Server-Timing', (string) (microtime(true) - $start));
return $response; return $response;
} }
} }

View File

@ -37,8 +37,6 @@ use Illuminate\Support\Facades\Response;
use App\DataMapper\Analytics\LivePreview; use App\DataMapper\Analytics\LivePreview;
use App\Repositories\PurchaseOrderRepository; use App\Repositories\PurchaseOrderRepository;
use App\Http\Requests\Preview\ShowPreviewRequest; use App\Http\Requests\Preview\ShowPreviewRequest;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Http\Requests\Preview\PreviewPurchaseOrderRequest; use App\Http\Requests\Preview\PreviewPurchaseOrderRequest;
class PreviewPurchaseOrderController extends BaseController class PreviewPurchaseOrderController extends BaseController
@ -87,18 +85,17 @@ class PreviewPurchaseOrderController extends BaseController
*/ */
public function show(ShowPreviewRequest $request) public function show(ShowPreviewRequest $request)
{ {
if (request()->has('entity') && if ($request->input('entity', false) &&
request()->has('entity_id') && $request->input('entity_id', false) != '-1' &&
! empty(request()->input('entity')) && $request->has('body')) {
! empty(request()->input('entity_id')) &&
request()->has('body')) {
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) { $design_object = json_decode(json_encode($request->input('design')), true);
if (! is_array($design_object)) {
return response()->json(['message' => ctrans('texts.invalid_design_object')], 400); return response()->json(['message' => ctrans('texts.invalid_design_object')], 400);
} }
$entity_obj = PurchaseOrder::query()->whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); $entity_obj = PurchaseOrder::query()->whereId($this->decodePrimaryKey($request->input('entity_id')))->company()->first();
if (! $entity_obj) { if (! $entity_obj) {
return $this->blankEntity(); return $this->blankEntity();
@ -109,63 +106,36 @@ class PreviewPurchaseOrderController extends BaseController
App::setLocale($entity_obj->company->locale()); App::setLocale($entity_obj->company->locale());
$t->replace(Ninja::transformTranslations($entity_obj->company->settings)); $t->replace(Ninja::transformTranslations($entity_obj->company->settings));
$html = new VendorHtmlEngine($entity_obj->invitations()->first()); $invitation = $entity_obj->invitations()->first();
$design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; $ps = new PdfService($invitation, 'product', [
'client' => $entity_obj->client ?? false,
'vendor' => $entity_obj->vendor ?? false,
$request->input('entity')."s" => [$entity_obj],
]);
$design_class = new $design_namespace(); $ps->boot()
->designer
->buildFromPartials($request->design['design']);
$state = [ $ps->builder
'template' => $design_class->elements([ ->build();
'client' => null,
'vendor' => $entity_obj->vendor,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'variables' => $html->generateLabelsAndValues(),
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $entity_obj->company->markdown_enabled,
'options' => [
'vendor' => $entity_obj->vendor ?? [],
request()->input('entity')."s" => [$entity_obj],
]
];
$design = new Design(request()->design['name']); if ($request->query('html') == 'true') {
$maker = new PdfMaker($state); return $ps->getHtml();
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
} }
//if phantom js...... inject here.. $pdf = $ps->getPdf();
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
/** @var \App\Models\User $user */ return response()->streamDownload(function () use ($pdf) {
$user = auth()->user(); echo $pdf;
}, 'preview.pdf', [
'Content-Disposition' => 'inline',
'Content-Type' => 'application/pdf',
'Cache-Control:' => 'no-cache',
]);
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $user->company());
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
//else
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle();
return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
} }
return $this->blankEntity(); return $this->blankEntity();
@ -238,177 +208,70 @@ class PreviewPurchaseOrderController extends BaseController
return $this->mockEntity(); return $this->mockEntity();
} }
$design_object = json_decode(json_encode(request()->input('design')));
$design_object = json_decode(json_encode(request()->input('design')), true);
if (! is_object($design_object)) { if (! is_array($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400); return response()->json(['message' => 'Invalid custom design object'], 400);
} }
$html = new VendorHtmlEngine($invitation); $ps = new PdfService($invitation, 'product', [
'client' => $invitation->client ?? false,
'vendor' => $invitation->vendor ?? false,
"{$entity_string}s" => [$invitation->{$entity_string}],
]);
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); $ps->boot()
->designer
->buildFromPartials($design_object['design']);
$state = [ $ps->builder
'template' => $design->elements([ ->build();
'client' => null,
'vendor' => $invitation->purchase_order->vendor,
'entity' => $invitation->purchase_order,
'pdf_variables' => (array) $invitation->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $invitation->company->markdown_enabled,
'options' => [
'vendor' => $invitation->purchase_order->vendor,
'purchase_orders' => [$invitation->purchase_order],
],
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {
return $maker->getCompiledHTML(); return $ps->getHtml();
} }
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { $pdf = $ps->getPdf();
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
/** @var \App\Models\User $user */ return response()->streamDownload(function () use ($pdf) {
$user = auth()->user(); echo $pdf;
}, 'preview.pdf', [
'Content-Disposition' => 'inline',
'Content-Type' => 'application/pdf',
'Cache-Control:' => 'no-cache',
]);
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $user->company());
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle();
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');
return $response;
} }
private function mockEntity() private function mockEntity()
{ {
/** @var \App\Models\User $user */
nlog("mockEntity");
$start = microtime(true);
$user = auth()->user(); $user = auth()->user();
DB::connection($user->company()->db)->beginTransaction(); /** @var \App\Models\Company $company */
$company = $user->company();
/** @var \App\Models\Vendor $vendor */ $request = request()->input('design');
$vendor = Vendor::factory()->create([ $request['entity_type'] = request()->input('entity', 'invoice');
'user_id' => $user->id,
'company_id' => $user->company()->id,
]);
/** @var \App\Models\VendorContact $contact */ $pdf = (new PdfMock($request, $company))->build();
$contact = VendorContact::factory()->create([
'user_id' => $user->id,
'company_id' => $user->company()->id,
'vendor_id' => $vendor->id,
'is_primary' => 1,
'send_email' => true,
]);
/** @var \App\Models\PurchaseOrder $purchase_order */
$purchase_order = PurchaseOrder::factory()->create([
'user_id' => $user->id,
'company_id' => $user->company()->id,
'vendor_id' => $vendor->id,
'terms' => 'Sample Terms',
'footer' => 'Sample Footer',
'public_notes' => 'Sample Public Notes',
]);
/** @var \App\Models\PurchaseOrderInvitation $invitation */
$invitation = PurchaseOrderInvitation::factory()->create([
'user_id' => $user->id,
'company_id' => $user->company()->id,
'purchase_order_id' => $purchase_order->id,
'vendor_contact_id' => $contact->id,
]);
$purchase_order->setRelation('invitations', $invitation);
$purchase_order->setRelation('vendor', $vendor);
$purchase_order->setRelation('company', $user->company());
$purchase_order->load('vendor.company');
$design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400);
}
$html = new VendorHtmlEngine($purchase_order->invitations()->first());
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]);
$state = [
'template' => $design->elements([
'client' => null,
'vendor' => $purchase_order->vendor,
'entity' => $purchase_order,
'pdf_variables' => (array) $purchase_order->company->settings->pdf_variables,
'products' => request()->design['design']['product'],
]),
'variables' => $html->generateLabelsAndValues(),
'process_markdown' => $purchase_order->company->markdown_enabled,
'options' => [
'vendor' => $invitation->purchase_order->vendor,
'purchase_orders' => [$invitation->purchase_order],
],
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
/** @var \App\Models\User $user */
$user = auth()->user();
DB::connection($user->company()->db)->rollBack();
if (request()->query('html') == 'true') { if (request()->query('html') == 'true') {
return $maker->getCompiledHTML(); return $pdf->getHtml();
} }
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { $pdf = $pdf->getPdf();
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $response = Response::make($pdf, 200);
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $user->company());
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $user->company()))->handle();
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf'); $response->header('Content-Type', 'application/pdf');
$response->header('Server-Timing', (string) (microtime(true) - $start));
return $response; return $response;
} }
} }

View File

@ -78,7 +78,7 @@ class SetupController extends Controller
$db_database = $request->input('db_database'); $db_database = $request->input('db_database');
$db_username = $request->input('db_username'); $db_username = $request->input('db_username');
$db_password = $request->input('db_password'); $db_password = $request->input('db_password');
$mail_port = $request->input('mail_port'); $mail_port = $request->input('mail_port',0);
$encryption = $request->input('encryption'); $encryption = $request->input('encryption');
$mail_host = $request->input('mail_host'); $mail_host = $request->input('mail_host');
$mail_username = $request->input('mail_username'); $mail_username = $request->input('mail_username');

View File

@ -11,12 +11,13 @@
namespace App\Http\Requests\Preview; namespace App\Http\Requests\Preview;
use App\Http\Requests\Request;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Models\Vendor; use App\Models\Vendor;
use App\Utils\Traits\CleanLineItems; use App\Models\PurchaseOrder;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
use App\Utils\Traits\CleanLineItems;
use App\Models\PurchaseOrderInvitation;
class PreviewPurchaseOrderRequest extends Request class PreviewPurchaseOrderRequest extends Request
{ {
@ -40,9 +41,14 @@ class PreviewPurchaseOrderRequest extends Request
public function rules() public function rules()
{ {
/** @var \App\Models\User $user */
$user = auth()->user();
$rules = []; $rules = [];
$rules['number'] = ['nullable']; $rules['number'] = ['nullable'];
$rules['vendor_id'] = ['required', Rule::exists(Vendor::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)];
return $rules; return $rules;
} }

View File

@ -55,8 +55,8 @@ class RecurringInvoicesCron
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('next_send_date', '<=', now()->toDateTimeString()) ->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
$query->where('is_deleted', 0) $query->where('is_deleted', false)
->where('deleted_at', null); ->whereNull('deleted_at');
}) })
->whereHas('company', function ($query) { ->whereHas('company', function ($query) {
$query->where('is_disabled', 0) $query->where('is_disabled', 0)
@ -97,8 +97,9 @@ class RecurringInvoicesCron
->whereNotNull('next_send_date') ->whereNotNull('next_send_date')
->where('next_send_date', '<=', now()->toDateTimeString()) ->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) { ->whereHas('client', function ($query) {
$query->where('is_deleted', 0) $query->where('is_deleted', false)
->where('deleted_at', null); ->whereNull('deleted_at');
}) })
->whereHas('company', function ($query) { ->whereHas('company', function ($query) {
$query->where('is_disabled', 0) $query->where('is_disabled', 0)

View File

@ -1,218 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Vendor;
use App\Exceptions\FilePermissionsFailure;
use App\Libraries\MultiDB;
use App\Models\Design;
use App\Services\Pdf\PdfService;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\VendorHtmlEngine;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
/** @deprecated 26-10-2023 5.7.30x */
class CreatePurchaseOrderPdf implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
use NumberFormatter;
use MakesInvoiceHtml;
use PdfMaker;
use MakesHash;
use PageNumbering;
public $entity;
public $company;
public $contact;
private $disk;
public $invitation;
public $entity_string = '';
public $vendor;
private string $path = '';
private string $file_path = '';
/**
* Create a new job instance.
*
* @param $invitation
*/
public function __construct($invitation, $disk = null)
{
$this->invitation = $invitation;
$this->company = $invitation->company;
$this->entity = $invitation->purchase_order;
$this->entity_string = 'purchase_order';
$this->contact = $invitation->contact;
$this->vendor = $invitation->contact->vendor;
$this->vendor->load('company');
$this->disk = $disk ?? config('filesystems.default');
}
public function handle()
{
/** Testing this override to improve PDF generation performance */
$ps = new PdfService($this->invitation, 'product', [
'client' => $this->entity->client ?? false,
'vendor' => $this->entity->vendor ?? false,
"{$this->entity_string}s" => [$this->entity],
]);
nlog("returning purchase order");
return $ps->boot()->getPdf();
}
public function rawPdf()
{
MultiDB::setDb($this->company->db);
/* Forget the singleton*/
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->vendor->locale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->generate($this->invitation, true);
}
$entity_design_id = '';
$this->path = $this->vendor->purchase_order_filepath($this->invitation);
$entity_design_id = 'purchase_order_design_id';
$this->file_path = $this->path.$this->entity->numberFormatter().'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
$design = Design::withTrashed()->find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if (!$design) {
/** @var \App\Models\Design $design */
$design = Design::find(2);
}
$html = new VendorHtmlEngine($this->invitation);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true)
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => null,
'vendor' => $this->vendor,
'entity' => $this->entity,
'pdf_variables' => (array) $this->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $this->entity->company->getSetting('all_pages_header'),
'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'),
'client' => null,
'vendor' => $this->vendor,
'entity' => $this->entity,
'variables' => $variables,
],
'process_markdown' => $this->entity->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
} else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
}
} catch (\Exception $e) {
nlog($e->getMessage());
}
if (config('ninja.log_pdf_html')) {
nlog($maker->getCompiledHTML());
}
$maker = null;
$state = null;
return $pdf;
}
public function failed($e)
{
}
}

View File

@ -319,6 +319,11 @@ class Vendor extends BaseModel
return $this->language ? $this->language->locale : $this->company->locale(); return $this->language ? $this->language->locale : $this->company->locale();
} }
public function preferredLocale(): string
{
return $this->locale();
}
public function language(): \Illuminate\Database\Eloquent\Relations\BelongsTo public function language(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{ {
return $this->belongsTo(Language::class); return $this->belongsTo(Language::class);

View File

@ -11,23 +11,21 @@
namespace App\Repositories; namespace App\Repositories;
use App\Models\Activity; use App\Models\User;
use App\Models\Quote;
use App\Models\Backup; use App\Models\Backup;
use App\Models\CompanyToken;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\PurchaseOrder; use App\Models\Activity;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\User;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Models\CompanyToken;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\VendorHtmlEngine; use App\Utils\VendorHtmlEngine;
use App\Models\RecurringInvoice;
use App\Services\Pdf\PdfService;
use App\Utils\Traits\MakesInvoiceHtml;
/** /**
* Class for activity repository. * Class for activity repository.
@ -111,7 +109,7 @@ class ActivityRepository extends BaseRepository
$backup->json_backup = ''; $backup->json_backup = '';
$backup->save(); $backup->save();
$backup->storeRemotely($this->generateVendorHtml($entity), $entity->vendor); $backup->storeRemotely($this->generateHtml($entity), $entity->vendor);
return; return;
@ -132,79 +130,44 @@ class ActivityRepository extends BaseRepository
return false; return false;
} }
private function generateVendorHtml($entity)
{
$entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->vendor->getSetting('purchase_order_design_id'));
$design = Design::withTrashed()->find($entity_design_id);
if (! $entity->invitations()->exists() || ! $design) {
return '';
}
$entity->load('vendor.company', 'invitations');
$html = new VendorHtmlEngine($entity->invitations->first()->load('purchase_order', 'contact'));
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'vendor' => $entity->vendor,
'entity' => $entity,
'pdf_variables' => (array) $entity->company->settings->pdf_variables,
'$product' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'options' => [
'all_pages_header' => $entity->vendor->getSetting('all_pages_header'),
'all_pages_footer' => $entity->vendor->getSetting('all_pages_footer'),
'vendor' => $entity->vendor,
'entity' => $entity,
],
'process_markdown' => $entity->vendor->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$html = $maker->design($template)
->build()
->getCompiledHTML(true);
$maker = null;
$state = null;
return $html;
}
private function generateHtml($entity) private function generateHtml($entity)
{ {
$entity_design_id = ''; $entity_design_id = '';
$entity_type = ''; $entity_type = '';
$settings = $entity->client ? $entity->client->getMergedSettings() : $entity->vendor->getMergedSettings();
if ($entity instanceof Invoice) { if ($entity instanceof Invoice) {
$entity_type = 'invoice'; $entity_type = 'invoice';
$entity_design_id = 'invoice_design_id'; $entity_design_id = 'invoice_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof RecurringInvoice) { } elseif ($entity instanceof RecurringInvoice) {
$entity_type = 'recurring_invoice'; $entity_type = 'recurring_invoice';
$entity_design_id = 'invoice_design_id'; $entity_design_id = 'invoice_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof Quote) { } elseif ($entity instanceof Quote) {
$entity_type = 'quote'; $entity_type = 'quote';
$entity_design_id = 'quote_design_id'; $entity_design_id = 'quote_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof Credit) { } elseif ($entity instanceof Credit) {
$entity_type = 'credit'; $entity_type = 'product';
$entity->load('client.company', 'invitations');
$entity_design_id = 'credit_design_id'; $entity_design_id = 'credit_design_id';
$document_type = 'credit';
} elseif ($entity instanceof PurchaseOrder) {
$entity_type = 'purchase_order';
$entity_design_id = 'purchase_order_design_id';
$document_type = 'purchase_order';
$entity->load('vendor.company', 'invitations');
} }
$entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id)); $entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($settings->{$entity_design_id});
$design = Design::withTrashed()->find($entity_design_id); $design = Design::withTrashed()->find($entity_design_id);
@ -212,45 +175,13 @@ class ActivityRepository extends BaseRepository
return ''; return '';
} }
$entity->load('client.company', 'invitations'); $ps = new PdfService($entity->invitations()->first(), $document_type, [
'client' => $entity->client ?? false,
'vendor' => $entity->vendor ?? false,
"{$entity_type}s" => [$entity],
]);
$html = new HtmlEngine($entity->invitations->first()->load($entity_type, 'contact')); return $ps->boot()->getHtml();
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'client' => $entity->client,
'entity' => $entity,
'pdf_variables' => (array) $entity->company->settings->pdf_variables,
'$product' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'options' => [
'all_pages_header' => $entity->client->getSetting('all_pages_header'),
'all_pages_footer' => $entity->client->getSetting('all_pages_footer'),
'client' => $entity->client,
'entity' => $entity,
],
'process_markdown' => $entity->client->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$html = $maker->design($template)
->build()
->getCompiledHTML(true);
$maker = null;
$state = null;
return $html;
} }
} }

View File

@ -12,29 +12,27 @@
namespace App\Services\Client; namespace App\Services\Client;
use App\Factory\InvoiceFactory; use App\Utils\Number;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\InvoiceItemFactory;
use App\Models\Client; use App\Models\Client;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Utils\Number; use Illuminate\Support\Carbon;
use App\Factory\InvoiceFactory;
use App\Utils\Traits\MakesHash;
use App\Utils\PhantomJS\Phantom; use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; use App\Utils\Traits\Pdf\PdfMaker;
use App\Factory\InvoiceItemFactory;
use App\Factory\InvoiceInvitationFactory;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
class Statement class Statement
{ {
use PdfMakerTrait; use PdfMaker;
use MakesHash; use MakesHash;
use MakesDates; use MakesDates;
@ -89,52 +87,33 @@ class Statement
return $pdf; return $pdf;
} }
if ($this->getDesign()->is_custom) {
$this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true);
$template = new PdfMakerDesign(\App\Services\PdfMaker\Design::CUSTOM, $this->options); $variables['values']['$show_paid_stamp'] = 'none';
} else {
$template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options);
}
$variables['values']['$show_paid_stamp'] = 'none'; //do not show paid stamp on statement $options = [
// 'client' => $this->entity->client,
$state = [ // 'entity' => $this->entity,
'template' => $template->elements([ // 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
'client' => $this->client, // '$product' => $this->getDesign()->design->product,
'entity' => $this->entity, // 'variables' => $variables,
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, 'invoices' => $this->getInvoices()->cursor(),
'$product' => $this->getDesign()->design->product, 'payments' => $this->getPayments()->cursor(),
'variables' => $variables, 'credits' => $this->getCredits()->cursor(),
'invoices' => $this->getInvoices()->cursor(), 'aging' => $this->getAging(),
'payments' => $this->getPayments()->cursor(), 'unapplied' => $this->getUnapplied()->cursor()
'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(),
'unapplied' => $this->getUnapplied()->cursor(),
], \App\Services\PdfMaker\Design::STATEMENT),
'variables' => $variables,
'options' => [
],
'process_markdown' => $this->entity->client->company->markdown_enabled,
]; ];
$maker = new PdfMaker($state); $ps = new \App\Services\Pdf\PdfService($invitation, 'statement', array_merge($options, $this->options));
$pdf = $ps->boot();
$ps->config->pdf_variables = (array) $this->entity->company->settings->pdf_variables;
$ps->html_variables = $variables;
$ps->config->design = $this->getDesign();
$maker $ps->designer->buildFromPartials((array)$ps->config->design->design);
->design($template) $ps->builder->build();
->build(); $pdf = $ps->getPdf();
$pdf = null;
$html = $maker->getCompiledHTML(true);
// nlog($html);
$pdf = $this->convertToPdf($html);
$this->setVariables($variables);
$maker = null;
$state = null;
return $pdf; return $pdf;
@ -219,7 +198,7 @@ class Statement
protected function setupEntity(): self protected function setupEntity(): self
{ {
if ($this->getInvoices()->count() >= 1) { if ($this->getInvoices()->count() >= 1) {
$this->entity = $this->getInvoices()->first();//@phpstan-ignore-line $this->entity = $this->getInvoices()->first(); //@phpstan-ignore-line
} }
else { else {
$this->entity = $this->client->invoices()->whereHas('invitations')->first(); $this->entity = $this->client->invoices()->whereHas('invitations')->first();
@ -235,7 +214,7 @@ class Statement
$this->entity = \App\Models\Invoice::factory()->make(); //@phpstan-ignore-line $this->entity = \App\Models\Invoice::factory()->make(); //@phpstan-ignore-line
$this->entity->client = \App\Models\Client::factory()->make(['settings' => $settings]); //@phpstan-ignore-line $this->entity->client = \App\Models\Client::factory()->make(['settings' => $settings]); //@phpstan-ignore-line
$this->entity->client->setRelation('company', $this->client->company); $this->entity->client->setRelation('company', $this->client->company);
$this->entity->invitation = \App\Models\InvoiceInvitation::factory()->make(); //@phpstan-ignore-line $this->entity->setRelation('invitations', \App\Models\InvoiceInvitation::factory()->make()); //@phpstan-ignore-line
$this->entity->setRelation('company', $this->client->company); $this->entity->setRelation('company', $this->client->company);
$this->entity->setRelation('user', $this->client->user); $this->entity->setRelation('user', $this->client->user);
@ -540,8 +519,8 @@ class Statement
{ {
$id = 1; $id = 1;
if (! empty($this->client->getSetting('entity_design_id'))) { if (! empty($this->client->getSetting('statement_design_id'))) {
$id = (int) $this->client->getSetting('entity_design_id'); $id = (int) $this->client->getSetting('statement_design_id');
} }
return Design::withTrashed()->find($id); return Design::withTrashed()->find($id);

View File

@ -12,18 +12,14 @@
namespace App\Services\Invoice; namespace App\Services\Invoice;
use App\Models\ClientContact;
use App\Models\Design; use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Services\Template\TemplateService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Utils\PhantomJS\Phantom; use App\Models\ClientContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Pdf\PdfMaker; use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use App\Services\Template\TemplateService;
class GenerateDeliveryNote class GenerateDeliveryNote
{ {
@ -65,62 +61,11 @@ class GenerateDeliveryNote
$invitation = $this->invoice->invitations->first(); $invitation = $this->invoice->invitations->first();
// return (new \App\Services\Pdf\PdfService($invitation, 'delivery_note'))->boot()->getPdf();
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->generate($this->invoice->invitations->first());
}
$design = Design::withTrashed()->find($design_id); $design = Design::withTrashed()->find($design_id);
$html = new HtmlEngine($invitation);
if ($design->is_custom) { $ps = new \App\Services\Pdf\PdfService($invitation, 'delivery_note');
$options = ['custom_partials' => json_decode(json_encode($design->design), true)];
$template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$variables = $html->generateLabelsAndValues(); return $ps->boot()->getPdf();
$variables['labels']['$entity_label'] = ctrans('texts.delivery_note');
$variables['labels']['$invoice.date_label'] = ctrans('texts.date');
$variables['labels']['$invoice.number_label'] = ctrans('texts.number');
$state = [
'template' => $template->elements([
'client' => $this->invoice->client,
'entity' => $this->invoice,
'pdf_variables' => (array) $this->invoice->company->settings->pdf_variables,
'contact' => $this->contact,
], 'delivery_note'),
'variables' => $variables,
'options' => [
'client' => $this->invoice->client,
'entity' => $this->invoice,
'contact' => $this->contact,
],
'process_markdown' => $this->invoice->client->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
} else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML());
}
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
$maker = null;
$state = null;
return $pdf;
} }
} }

View File

@ -0,0 +1,86 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Pdf;
use App\Models\Design;
use Illuminate\Support\Str;
class DesignExtractor
{
private ?string $html = null;
public function __construct(private ?string $design = null, private array $options = [])
{
if($design) {
$design = strtolower($design);
Str::endsWith('.html', $design) ? $this->design = $design : $this->design = "{$design}.html";
}
$this->options = $options;
}
public function setHtml(string $html): self
{
$this->html = $html;
return $this;
}
private function getHtml(): string
{
nlog($this->design);
if($this->html) {
return $this->html;
}
$this->html = file_get_contents( config('ninja.designs.base_path') . $this->design );
return $this->html;
}
public function getSectionHTML(string $section, $id = true): ?string
{
$document = new \DOMDocument();
$document->validateOnParse = true;
@$document->loadHTML($this->getHtml());
if ($id) {
$element = $document->getElementById($section);
} else {
$elements = $document->getElementsByTagName($section);
$element = $elements[0];
}
if ($element) {
$_document = new \DOMDocument();
$_document->preserveWhiteSpace = false;
$_document->formatOutput = true;
$_document->appendChild(
$_document->importNode($element, true)
);
$html = $_document->saveHTML();
return str_replace('%24', '$', $html);
}
return '';
}
}

File diff suppressed because it is too large Load Diff

View File

@ -400,18 +400,6 @@ class PdfConfiguration
$precision = strlen($parts[1]); $precision = strlen($parts[1]);
} }
/* 08-02-2023 special if block to render $0.5 to $0.50*/
// if ($v < 1 && strlen($v) == 3) {
// $precision = 2;
// } elseif ($v < 1) {
// $precision = strlen($v) - strrpos($v, '.') - 1;
// } elseif ($v > 1) {
// $precision = strlen($v) - strrpos($v, '.') - 1;
// }
// if (is_array($parts) && $parts[0] != 0) {
// $precision = 2;
// }
//04-04-2023 if currency = JPY override precision to 0 //04-04-2023 if currency = JPY override precision to 0
if ($this->currency->code == 'JPY') { if ($this->currency->code == 'JPY') {
$precision = 0; $precision = 0;

View File

@ -42,18 +42,20 @@ class PdfMock
private string $entity_string = 'invoice'; private string $entity_string = 'invoice';
private PdfService $pdf_service;
public function __construct(public array $request, public Company $company) public function __construct(public array $request, public Company $company)
{ {
} }
public function getPdf(): mixed public function setPdfService(): self
{ {
//need to resolve the pdf type here, ie product / purchase order //need to resolve the pdf type here, ie product / purchase order
$document_type = $this->request['entity_type'] == 'purchase_order' ? 'purchase_order' : 'product'; $document_type = $this->request['entity_type'] == 'purchase_order' ? 'purchase_order' : 'product';
$pdf_service = new PdfService($this->mock->invitation, $document_type); $this->pdf_service = new PdfService($this->mock->invitation, $document_type);
$pdf_config = (new PdfConfiguration($pdf_service)); $pdf_config = (new PdfConfiguration($this->pdf_service));
$pdf_config->entity = $this->mock; $pdf_config->entity = $this->mock;
$pdf_config->entity_string = $this->request['entity_type']; $pdf_config->entity_string = $this->request['entity_type'];
$this->entity_string = $this->request['entity_type']; $this->entity_string = $this->request['entity_type'];
@ -76,29 +78,44 @@ class PdfMock
$pdf_config->design = Design::withTrashed()->find($this->decodePrimaryKey($pdf_config->entity_design_id)); $pdf_config->design = Design::withTrashed()->find($this->decodePrimaryKey($pdf_config->entity_design_id));
} }
$pdf_service->config = $pdf_config; $this->pdf_service->config = $pdf_config;
if (isset($this->request['design'])) { if (isset($this->request['design']) && is_array($this->request['design'])) {
$pdf_designer = (new PdfDesigner($pdf_service))->buildFromPartials($this->request['design']); $pdf_designer = (new PdfDesigner($this->pdf_service))->buildFromPartials($this->request['design']);
} else { } else {
$pdf_designer = (new PdfDesigner($pdf_service))->build(); $pdf_designer = (new PdfDesigner($this->pdf_service))->build();
} }
$pdf_service->designer = $pdf_designer; $this->pdf_service->designer = $pdf_designer;
$pdf_service->html_variables = $document_type == 'purchase_order' ? $this->getVendorStubVariables() : $this->getStubVariables(); $this->pdf_service->html_variables = $document_type == 'purchase_order' ? $this->getVendorStubVariables() : $this->getStubVariables();
$pdf_builder = (new PdfBuilder($pdf_service))->build(); $pdf_builder = (new PdfBuilder($this->pdf_service))->build();
$pdf_service->builder = $pdf_builder; $this->pdf_service->builder = $pdf_builder;
$html = $pdf_service->getHtml(); return $this;
}
public function getPdf(): mixed
{
$html = $this->pdf_service->getHtml();
return $this->pdf_service->resolvePdfEngine($html);
}
public function getHtml(): string
{
return $this->pdf_service->getHtml();
return $pdf_service->resolvePdfEngine($html);
} }
public function build(): self public function build(): self
{ {
$this->mock = $this->initEntity(); $this->mock = $this->initEntity();
$this->setPdfService();
return $this; return $this;
} }

View File

@ -143,10 +143,9 @@ class PdfService
$this->config = (new PdfConfiguration($this))->init(); $this->config = (new PdfConfiguration($this))->init();
$this->html_variables = ($this->invitation instanceof \App\Models\PurchaseOrderInvitation) ?
$this->html_variables = $this->config->client ? (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues() :
(new HtmlEngine($this->invitation))->generateLabelsAndValues() : (new HtmlEngine($this->invitation))->generateLabelsAndValues();
(new VendorHtmlEngine($this->invitation))->generateLabelsAndValues();
$this->designer = (new PdfDesigner($this))->build(); $this->designer = (new PdfDesigner($this))->build();

View File

@ -22,6 +22,7 @@ use App\Utils\Traits\MakesInvoiceValues;
use DOMDocument; use DOMDocument;
use Illuminate\Support\Str; use Illuminate\Support\Str;
/** @deprecated */
class Design extends BaseDesign class Design extends BaseDesign
{ {
use MakesInvoiceValues; use MakesInvoiceValues;
@ -327,9 +328,9 @@ class Design extends BaseDesign
['element' => 'div', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']], ['element' => 'div', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'div', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']], ['element' => 'div', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'div', 'show_empty' => false, 'elements' => [ ['element' => 'div', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], ['element' => 'p', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], ['element' => 'p', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], ['element' => 'p', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
]], ]],
['element' => 'div', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false], ['element' => 'div', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
]; ];
@ -344,7 +345,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['client_details']; $variables = $this->context['pdf_variables']['client_details'];
foreach ($variables as $variable) { foreach ($variables as $variable) {
$elements[] = ['element' => 'div', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
} }
return $elements; return $elements;
@ -405,7 +406,6 @@ class Design extends BaseDesign
$_variable = explode('.', $variable)[1]; $_variable = explode('.', $variable)[1];
$_customs = ['custom1', 'custom2', 'custom3', 'custom4']; $_customs = ['custom1', 'custom2', 'custom3', 'custom4'];
/* 2/7/2022 don't show custom values if they are empty */
$var = str_replace("custom", "custom_value", $_variable); $var = str_replace("custom", "custom_value", $_variable);
if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) {

View File

@ -15,6 +15,7 @@ namespace App\Services\PdfMaker;
use DOMDocument; use DOMDocument;
use DOMXPath; use DOMXPath;
/** @deprecated */
trait PdfMakerUtilities trait PdfMakerUtilities
{ {
private function initializeDomDocument() private function initializeDomDocument()

View File

@ -11,114 +11,11 @@
namespace App\Utils\PhantomJS; namespace App\Utils\PhantomJS;
use App\Exceptions\PhantomPDFFailure;
use App\Jobs\Util\SystemLogger;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Models\SystemLog;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\CurlUtils; use App\Utils\CurlUtils;
use App\Utils\HtmlEngine;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class Phantom class Phantom
{ {
use MakesHash;
use PageNumbering;
/**
* Generate a PDF from the
* Phantom JS API.
*
* @param $invitation
*/
public function generate($invitation, $return_pdf = false)
{
$entity = false;
$path = '';
if ($invitation instanceof InvoiceInvitation) {
$entity = 'invoice';
$entity_design_id = 'invoice_design_id';
} elseif ($invitation instanceof CreditInvitation) {
$entity = 'credit';
$entity_design_id = 'credit_design_id';
} elseif ($invitation instanceof QuoteInvitation) {
$entity = 'quote';
$entity_design_id = 'quote_design_id';
} elseif ($invitation instanceof RecurringInvoiceInvitation) {
$entity = 'recurring_invoice';
$entity_design_id = 'invoice_design_id';
}
$entity_obj = $invitation->{$entity};
if ($entity == 'invoice') {
$path = $entity_obj->client->invoice_filepath($invitation);
}
if ($entity == 'quote') {
$path = $entity_obj->client->quote_filepath($invitation);
}
if ($entity == 'credit') {
$path = $entity_obj->client->credit_filepath($invitation);
}
if ($entity == 'recurring_invoice') {
$path = $entity_obj->client->recurring_invoice_filepath($invitation);
}
$file_path = $path.$entity_obj->numberFormatter().'.pdf';
$url = config('ninja.app_url').'/phantom/'.$entity.'/'.$invitation->key.'?phantomjs_secret='.config('ninja.phantomjs_secret');
info($url);
$key = config('ninja.phantomjs_key');
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/";
$pdf = CurlUtils::post($phantom_url, json_encode([
'url' => $url,
'renderType' => 'pdf',
'outputAsJson' => false,
'renderSettings' => [
'emulateMedia' => 'print',
'pdfOptions' => [
'preferCSSPageSize' => true,
'printBackground' => true,
],
],
]));
$this->checkMime($pdf, $invitation, $entity);
$numbered_pdf = $this->pageNumbering($pdf, $invitation->company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
if (! Storage::disk(config('filesystems.default'))->exists($path)) {
Storage::disk(config('filesystems.default'))->makeDirectory($path);
}
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
if ($return_pdf) {
return $pdf;
}
return $file_path;
}
public function convertHtmlToPdf($html) public function convertHtmlToPdf($html)
{ {
@ -143,95 +40,4 @@ class Phantom
return $response; return $response;
} }
/* Check if the returning PDF is valid. */ }
private function checkMime($pdf, $invitation, $entity)
{
$finfo = new \finfo(FILEINFO_MIME);
if ($finfo->buffer($pdf) != 'application/pdf; charset=binary') {
SystemLogger::dispatch(
$pdf,
SystemLog::CATEGORY_PDF,
SystemLog::EVENT_PDF_RESPONSE,
SystemLog::TYPE_PDF_FAILURE,
$invitation->contact->client,
$invitation->company,
);
throw new PhantomPDFFailure('There was an error generating the PDF with Phantom JS');
} else {
SystemLogger::dispatch(
'Entity PDF generated sucessfully => '.$invitation->{$entity}->number,
SystemLog::CATEGORY_PDF,
SystemLog::EVENT_PDF_RESPONSE,
SystemLog::TYPE_PDF_SUCCESS,
$invitation->contact->client,
$invitation->company,
);
}
}
public function displayInvitation(string $entity, string $invitation_key)
{
$key = $entity.'_id';
$invitation_instance = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
$invitation = $invitation_instance::where('key', $invitation_key)->first();
$entity_obj = $invitation->{$entity};
$entity_obj->load('client');
App::setLocale($invitation->contact->preferredLocale());
$entity_design_id = $entity.'_design_id';
if ($entity == 'recurring_invoice') {
$entity_design_id = 'invoice_design_id';
}
$design_id = $entity_obj->design_id ? $entity_obj->design_id : $this->decodePrimaryKey($entity_obj->client->getSetting($entity_design_id));
$design = Design::withTrashed()->find($design_id);
$html = new HtmlEngine($invitation);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'client' => $entity_obj->client,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'$product' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'options' => [
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
'client' => $entity_obj->client,
'entity' => $entity_obj,
],
'process_markdown' => $entity_obj->client->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$data['html'] = $maker->design($template)
->build()
->getCompiledHTML(true);
if (config('ninja.log_pdf_html')) {
info($data['html']);
}
return view('pdf.html', $data);
}
}

View File

@ -91,7 +91,7 @@ trait PdfMaker
} }
$html = str_ireplace(['file:/', 'iframe', '<embed', '&lt;embed', '&lt;object', '<object', '127.0.0.1', 'localhost', '<?xml encoding="UTF-8">', '/etc/'], '', $html); $html = str_ireplace(['file:/', 'iframe', '<embed', '&lt;embed', '&lt;object', '<object', '127.0.0.1', 'localhost', '<?xml encoding="UTF-8">', '/etc/'], '', $html);
// nlog($html);
$generated = $pdf $generated = $pdf
->setHtml($html) ->setHtml($html)
->generate(); ->generate();

View File

@ -136,6 +136,10 @@ class VendorHtmlEngine
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')]; $data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.date')]; $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.date')];
$data['$show_shipping_address'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'flex' : 'none', 'label' => ''];
$data['$show_shipping_address_block'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 'block' : 'none', 'label' => ''];
$data['$show_shipping_address_visibility'] = ['value' => strlen($this->entity->client->shipping_address1 ?? '') > 0 && $this->settings->show_shipping_address ? 1 : 0, 'label' => ''];
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.due_date')]; $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.due_date')];
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->vendor->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
@ -154,7 +158,7 @@ class VendorHtmlEngine
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')]; $data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.number')]; $data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.number')];
$data['$number_short'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_number_short')]; $data['$number_short'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_number_short')];
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?? ''), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')]; $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?? ''), $this->company) ?: '', 'label' => ctrans('texts.purchase_order_terms')];
$data['$terms'] = &$data['$entity.terms']; $data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_purchase_order')), 'label' => ctrans('texts.view_purchase_order')]; $data['$view_link'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_purchase_order')), 'label' => ctrans('texts.view_purchase_order')];
$data['$viewLink'] = &$data['$view_link']; $data['$viewLink'] = &$data['$view_link'];
@ -428,6 +432,9 @@ class VendorHtmlEngine
$data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')]; $data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')];
$data['$shipping'] = ['value' => '', 'label' => ctrans('texts.ship_to')];
$data['$ship_to'] = &$data['$shipping'];
if ($this->entity->client()->exists()) { if ($this->entity->client()->exists()) {
$data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->entity->client->custom_value1, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')]; $data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->entity->client->custom_value1, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')];
$data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->entity->client->custom_value2, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')]; $data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->entity->client->custom_value2, $this->entity->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')];

View File

@ -12,7 +12,6 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Design; use App\Models\Design;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -49,9 +48,9 @@ class DesignSeeder extends Seeder
} }
} }
foreach (Design::all() as $design) { foreach (Design::where('is_custom', false)->get() as $design) {
$template = new PdfMakerDesign(strtolower($design->name));
$template->document(); $template = new \App\Services\Pdf\DesignExtractor($design->name);
$design_object = new \stdClass; $design_object = new \stdClass;
$design_object->includes = $template->getSectionHTML('style'); $design_object->includes = $template->getSectionHTML('style');

View File

@ -15,17 +15,17 @@
zoom: 80%; zoom: 80%;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
html { html {
/* width: 210mm; */
/* height: 200mm; */
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -40,11 +40,11 @@
padding: 0; padding: 0;
} }
#spacer-table > * > tr > td { #spacer-table>*>tr>td {
padding: 0; padding: 0;
} }
#spacer-table{ #spacer-table {
width: 100%; width: 100%;
} }
@ -61,14 +61,16 @@
} }
#header, #header-spacer { #header,
#header-spacer {
height: 160px; height: 160px;
padding-top: 2rem; padding-top: 1rem;
padding-bottom: 2rem; padding-bottom: 1rem;
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.company-logo { .company-logo {
max-width: $company_logo_size; max-width: $company_logo_size;
object-fit: contain; object-fit: contain;
@ -89,11 +91,19 @@
} }
#client-details { #client-details {
padding-right:1rem; padding-right: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
padding-left: 1.5rem; padding-left: 1rem;
}
#vendor-details {
padding-right: 1rem;
display: flex;
flex-direction: column;
line-height: var(--line-height) !important;
padding-left: 1rem;
} }
#shipping-details { #shipping-details {
@ -102,16 +112,16 @@
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
} }
#client-details > :first-child { #client-details> :first-child {
font-weight: bold; font-weight: bold;
} }
.client-entity-wrapper { .client-entity-wrapper {
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
} }
.client-wrapper-left-side { .client-wrapper-left-side {
display: flex; display: flex;
} }
@ -127,7 +137,7 @@
color: white !important; color: white !important;
} }
#entity-details > tr, #entity-details>tr,
#entity-details th { #entity-details th {
font-weight: normal; font-weight: normal;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
@ -142,8 +152,8 @@
margin-bottom: 0px; margin-bottom: 0px;
} }
[data-ref="table"]:last-child{ [data-ref="table"]:last-child {
margin-bottom:0; margin-bottom: 0;
} }
.task-time-details { .task-time-details {
@ -152,36 +162,46 @@
color: grey; color: grey;
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
padding: 1.5rem; padding-top: 1.5rem;
padding-bottom: 1.5rem;
padding-left: 0;
padding-right: 0;
font-size: 1rem; font-size: 1rem;
} }
[data-ref="table"] > thead > tr > th:last-child {
text-align: right; [data-ref="table"]>tbody>tr>td {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
padding-left: 0;
padding-right: 0;
} }
[data-ref="table"] > tbody > tr > td { th.left-radius {
padding: 1.5rem; padding-left: 1rem !important;
} }
th.right-radius { th.right-radius {
text-align:right !important; text-align: right !important;
padding-right: 1rem !important;
} }
td.right-radius { td.right-radius {
text-align: right !important; text-align: right !important;
padding-right: 1rem !important;
} }
td.left-radius{ td.left-radius {
font-weight: bold; font-weight: bold;
padding-left: 1rem !important;
} }
[data-ref="table"] > tbody > tr:nth-child(odd) { [data-ref="table"]>tbody>tr:nth-child(odd) {
background-color: #ebebeb; background-color: #ebebeb;
} }
@ -199,43 +219,27 @@
gap: 80px; gap: 80px;
} }
#table-totals .totals-table-right-side > * { #table-totals .totals-table-right-side>* {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
#table-totals > .totals-table-right-side > * > :nth-child(1) { #table-totals>.totals-table-right-side>*> :nth-child(1) {
text-align: $dir_text_align; text-align: $dir_text_align;
margin-top: .75rem; margin-top: .75rem;
} }
#table-totals > .totals-table-right-side > * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
} }
#table-totals > .totals-table-right-side > * > :nth-child(2) { #table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right; text-align: right;
} }
#table-totals #table-totals>*> :last-child {
> *
[data-element='product-table-balance-due-label'],
#table-totals
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
font-size: 1.4rem;
}
#table-totals
> *
[data-element='product-table-balance-due'] {
color: var(--primary-color);
}
#table-totals > * > :last-child {
text-align: right; text-align: right;
padding-right: 1.5rem; padding-right: 1.5rem;
} }
@ -243,7 +247,7 @@
.entity-label { .entity-label {
text-transform: uppercase; text-transform: uppercase;
color: var(--primary-color); color: var(--primary-color);
padding-left: 1.5rem; padding-left: 1rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
@ -259,7 +263,8 @@
color: white; color: white;
} }
#footer, #footer-spacer { #footer,
#footer-spacer {
height: 160px; height: 160px;
padding: 1rem 1rem; padding: 1rem 1rem;
margin-top: 1rem; margin-top: 1rem;
@ -269,7 +274,7 @@
padding-top: 0.5rem padding-top: 0.5rem
} }
[data-ref="footer_content"]{ [data-ref="footer_content"] {
padding-right: 1rem; padding-right: 1rem;
margin-right: 1rem; margin-right: 1rem;
} }
@ -304,23 +309,28 @@
white-space: nowrap; white-space: nowrap;
} }
#statement-invoice-table-totals > p { #statement-invoice-table-totals>p {
margin-right: 2rem; margin-right: 2rem;
margin-top: 1rem; margin-top: 1rem;
} }
[data-ref='product_table-product.description-th'] { [data-ref='product_table-product.description-th'],
width:30%; [data-ref='product_table-product.description-td'] {
overflow-wrap: break-word; width: 30%;
overflow-wrap: break-word;
padding-right: 1rem !important;
} }
.left-radius { [data-ref='product_table-product.tax1-th'],
padding-left: 1rem; [data-ref='product_table-product.tax1-td'],
text-align: right; [data-ref='product_table-product.discount-th'],
[data-ref='product_table-product.discount-td'] {
width: 9%;
overflow-wrap: break-word;
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -331,24 +341,24 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -359,14 +369,12 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
@ -401,69 +409,66 @@
<div id="header"> <div id="header">
<div class="logo-container"> <div class="logo-container">
<img class="company-logo" src="$company.logo" alt="$company.name logo"/> <img class="company-logo" src="$company.logo" alt="$company.name logo" />
</div> </div>
<div id="company-details"></div> <div id="company-details"></div>
<div id="company-address"></div> <div id="company-address"></div>
</div> </div>
<div id="body"> <div id="body">
<table id="spacer-table" cellspacing="0" > <table id="spacer-table" cellspacing="0">
<thead> <thead>
<tr> <tr>
<td> <td>
<div id="header-spacer"></div> <div id="header-spacer"></div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div class="client-entity-wrapper"> <div class="client-entity-wrapper">
<div class="client-wrapper-left-side"> <div class="client-wrapper-left-side">
<div> <div>
<h4 class="entity-label" style="margin-top:0px; margin-bottom:10px;">$entity_label</h4> <h4 class="entity-label" style="margin-top:0px; margin-bottom:10px;">$entity_label</h4>
<div id="client-details" cellspacing="0" cellpadding="0" ></div> <div id="client-details" cellspacing="0" cellpadding="0"></div>
<div id="vendor-details" cellspacing="0" cellpadding="0"></div> <div id="vendor-details" cellspacing="0" cellpadding="0"></div>
</div>
<div>
<h4 class="entity-label" style="opacity: 0; margin-top:0px; margin-bottom:10px;">&</h4>
<div id="shipping-details" cellspacing="0" cellpadding="0"></div>
</div>
</div> </div>
<div>
<h4 class="entity-label" style="opacity: 0; margin-top:0px; margin-bottom:10px;">&</h4> <div class="entity-details-wrapper-right-side">
<div id="shipping-details" cellspacing="0" cellpadding="0"></div> <h4 class="entity-label" style="margin-top:0px; margin-bottom:10px; color:transparent;">
$entity_label</h4>
<div class="entity-details-wrapper">
<table id="entity-details" dir="$dir" cellspacing="0" cellpadding="0"></table>
</div>
</div> </div>
</div> </div>
<div class="entity-details-wrapper-right-side"> <table id="product-table" cellspacing="0" data-ref="table"></table>
<h4 class="entity-label" style="margin-top:0px; margin-bottom:10px; color:transparent;">$entity_label</h4> <table id="task-table" cellspacing="0" data-ref="table"></table>
<div class="entity-details-wrapper"> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="entity-details" dir="$dir" cellspacing="0" cellpadding="0"></table> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
</div> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
</div> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
</div> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<table id="product-table" cellspacing="0" data-ref="table"></table> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> </td>
</tr>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
</td>
</tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td> <td>
<div id="footer-spacer"></div> <div id="footer-spacer"></div>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
@ -474,4 +479,4 @@ $entity_images
<div style="width: 100%;"> <div style="width: 100%;">
<p data-ref="footer_content">$entity_footer</p> <p data-ref="footer_content">$entity_footer</p>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<style id="style"> <style id="style">
@import url($font_url); @import url($font_url);
:root { :root {
--primary-color: $primary_color; --primary-color: $primary_color;
--secondary-color: $secondary_color; --secondary-color: $secondary_color;
@ -9,7 +9,7 @@
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
@ -22,7 +22,9 @@
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
@ -41,20 +43,29 @@
.header-container { .header-container {
display: grid; display: grid;
grid-template-columns: 1.8fr 1fr 1fr; grid-template-columns: 1fr auto 1fr;
grid-gap: 20px; align-items: start;
gap: 1rem;
width: 100%;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.company-logo { .company-logo-container {
justify-self: start;
align-self: start;
}
.company-logo {
max-width: $company_logo_size; max-width: $company_logo_size;
} }
.header-container > span { .header-container>span {
display: block; display: block;
} }
#company-details { #company-details {
justify-self: center;
align-self: start;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: #AAA9A9; color: #AAA9A9;
@ -62,6 +73,8 @@
} }
#company-address { #company-address {
justify-self: end;
align-self: start;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: #b1b1b1; color: #b1b1b1;
@ -73,6 +86,10 @@
font-weight: bold; font-weight: bold;
} }
.entity-issued-to:not(:empty)::after {
content: ":";
}
.client-and-entity-wrapper { .client-and-entity-wrapper {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -84,10 +101,9 @@
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
vertical-align: top; vertical-align: top;
margin-left: 1rem;
} }
#client-details > p:nth-child(2) { #client-details>p:nth-child(2) {
color: var(--primary-color); color: var(--primary-color);
font-size: 120%; font-size: 120%;
} }
@ -107,21 +123,23 @@
text-align: left; text-align: left;
} }
#entity-details p { margin-right: 20px; } #entity-details p {
margin-right: 20px;
}
#entity-details th { #entity-details th {
font-weight: normal; font-weight: normal;
padding-bottom: .5rem; padding-bottom: .5rem;
} }
#entity-details > tbody > tr > th:nth-child(2) { #entity-details>tbody>tr>th:nth-child(2) {
text-align: right; text-align: right;
padding-left: 10px; padding-left: 10px;
} }
[data-ref="table"] { [data-ref="table"] {
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 5px; margin-bottom: 5px;
min-width: 100%; min-width: 100%;
table-layout: fixed; table-layout: fixed;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -133,12 +151,12 @@
color: grey; color: grey;
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
background: var(--secondary-color); background: var(--secondary-color);
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
padding: 1rem; padding: 1rem;
color: white; color: white;
font-weight: semibold; font-weight: semibold;
@ -157,27 +175,19 @@
text-align: right; text-align: right;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr:nth-child(odd) > td { [data-ref="table"]>tbody>tr:nth-child(odd)>td {
background: #F7F7F7; background: #F7F7F7;
} }
[data-ref="table"] > tbody > tr:nth-child(even) > td { [data-ref="table"]>tbody>tr:nth-child(even)>td {
background: #f7f7f7; background: #f7f7f7;
} }
[data-element='product-table-balance-due-label'], #table-totals>*:last-child {
[data-element='product-table-balance-due'],
[data-element='task-table-balance-due-label'],
[data-element='task-table-balance-due'] {
color: var(--secondary-color) !important;
font-weight: bold !important;
}
#table-totals > *:last-child {
border-bottom-left-radius: 1rem; border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem; border-bottom-right-radius: 1rem;
} }
@ -189,7 +199,6 @@
gap: 80px; gap: 80px;
padding-top: 0.5rem; padding-top: 0.5rem;
padding-bottom: 0.8rem; padding-bottom: 0.8rem;
padding-left: 0.7rem;
/*page-break-inside:auto; this may cause weird breaking*/ /*page-break-inside:auto; this may cause weird breaking*/
overflow: visible !important; overflow: visible !important;
} }
@ -205,7 +214,7 @@
padding-left: 7px; padding-left: 7px;
} }
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
@ -216,31 +225,11 @@
padding-right: 17px; padding-right: 17px;
} }
#table-totals #table-totals>*> :last-child {
> *
[data-element='product-table-balance-due-label'],
#table-totals
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
font-size: 1.2rem;
}
#table-totals
> *
[data-element='product-table-balance-due'] {
color: red;
}
#table-totals > * > :last-child {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-footer"] {
padding-left: 0.8rem
}
#footer { #footer {
margin-top: 30px; margin-top: 30px;
} }
@ -251,23 +240,26 @@
margin-bottom: 0; margin-bottom: 0;
} }
[data-ref="totals_table-outstanding-label"]{ [data-ref="totals_table-outstanding-label"] {
background-color: var(--secondary-color); background-color: var(--secondary-color);
color: white; color: white;
font-size:120%; font-size: 120%;
font-weight:bold; font-weight: bold;
padding: 1rem; padding: 1rem;
border-top-left-radius: 7px; border-top-left-radius: 7px;
border-bottom-left-radius: 7px; border-bottom-left-radius: 7px;
white-space: nowrap;
} }
[data-ref="totals_table-outstanding"] { [data-ref="totals_table-outstanding"] {
background-color: var(--secondary-color); background-color: var(--secondary-color);
color: white; color: white;
font-size:120%; font-size: 120%;
font-weight:bold; font-weight: bold;
padding: 1rem; padding: 1rem;
border-top-right-radius: 7px; border-top-right-radius: 7px;
border-bottom-right-radius: 7px; border-bottom-right-radius: 7px;
white-space: nowrap;
} }
[data-ref="statement-totals"] { [data-ref="statement-totals"] {
@ -286,23 +278,26 @@
.repeating-footer-space { .repeating-footer-space {
height: 10px; height: 10px;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
} }
[data-element='product_table-product.description-td'], td { [data-element='product_table-product.description-td'],
min-width:100%; td {
min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -313,25 +308,25 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
margin-top: 0.1em; margin-top: 0.1em;
@ -341,14 +336,12 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
@ -383,57 +376,57 @@
</style> </style>
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div id="body"> <div id="body">
<div class="header-container"> <div class="header-container">
<img <div class="company-logo-container">
src="$company.logo" <img class="company-logo" src="$company.logo" alt="$company.name logo">
class="company-logo" </div>
alt="$company.name logo" <div id="company-details"></div>
/> <div id="company-address"></div>
<div id="company-details"></div> </div>
<div id="company-address"></div> <div class="client-and-entity-wrapper">
</div> <div id="client-details">
<div class="client-and-entity-wrapper"> <p class="entity-issued-to">$entity_issued_to_label</p>
<div id="client-details"><p class="entity-issued-to">$entity_issued_to_label:</p></div> </div>
<div id="vendor-details"></div> <div id="vendor-details"></div>
<div id="shipping-details"></div> <div id="shipping-details"></div>
<div class="entity-details-wrapper"> <div class="entity-details-wrapper">
<table id="entity-details" cellspacing="0" dir="$dir"></table> <table id="entity-details" cellspacing="0" dir="$dir"></table>
</div> </div>
</div> </div>
<table id="product-table" cellspacing="0" data-ref="table"></table> <table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table> <table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div> <div id="table-totals" cellspacing="0">$status_logo</div>
</div> </div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td> <td>
<div class="repeating-footer-space">&nbsp;</div> <div class="repeating-footer-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
<div class="repeating-header" id="header"></div> <div class="repeating-header" id="header"></div>
@ -443,4 +436,4 @@ $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -42,7 +42,6 @@
.header-wrapper { .header-wrapper {
display: grid; display: grid;
margin-top: 2rem; margin-top: 2rem;
gap: 20px;
grid-template-columns: 2fr 1fr 1fr; grid-template-columns: 2fr 1fr 1fr;
grid-template-areas: "a b c"; grid-template-areas: "a b c";
grid-auto-columns: minmax(0, 5fr); grid-auto-columns: minmax(0, 5fr);
@ -53,16 +52,20 @@
.header-wrapper2 { .header-wrapper2 {
display: grid; display: grid;
margin-top: 2rem; margin-top: 2rem;
gap: 20px;
grid-template-columns: 2fr 2fr auto; grid-template-columns: 2fr 2fr auto;
grid-template-areas: "a b c"; grid-template-areas: "a b c";
grid-auto-columns: minmax(0, 5fr); grid-auto-columns: minmax(0, 5fr);
grid-auto-flow: column; grid-auto-flow: column;
justify-content: left; justify-content: space-between;
}
.header-wrapper2 .entity-container {
justify-self: end;
align-self: start;
} }
.company-logo { .company-logo {
/* max-width: 65%;*/
max-width: $company_logo_size; max-width: $company_logo_size;
} }
@ -87,16 +90,17 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
margin-left: 20px;
} }
.header-wrapper #company-details { .header-wrapper #company-details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
margin-left: 20px;
} }
.header-wrapper #entity-details { .header-wrapper #entity-details {
padding-right: 0.5rem;
text-align: left; text-align: left;
line-height: var(--line-height); line-height: var(--line-height);
width: 100%; width: 100%;
@ -113,6 +117,10 @@
background-color: #e6e6e6; background-color: #e6e6e6;
} }
.entity-label {
padding-left: 1rem;
}
#entity-details { #entity-details {
text-align: left; text-align: left;
width: 100%; width: 100%;
@ -121,7 +129,7 @@
#entity-details th { #entity-details th {
font-weight: normal; font-weight: normal;
line-height: 1.5rem; line-height: 1.5rem;
padding-right: 2rem; padding-left: 1rem;
} }
#client-details { #client-details {
@ -166,7 +174,7 @@
td.left-radius { td.left-radius {
padding-left: 1rem !important; padding-left: 1rem !important;
} }
th.right-radius { th.right-radius {
text-align: right !important; text-align: right !important;
} }
@ -201,6 +209,7 @@
text-align: left; text-align: left;
margin-top: .25rem; margin-top: .25rem;
padding-left: 7px; padding-left: 7px;
} }
#table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
@ -211,7 +220,7 @@
#table-totals>.totals-table-right-side>*> :nth-child(2) { #table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right; text-align: right;
padding-right: 0px; padding-right: 0.75rem;
} }
#table-totals>* [data-element='total-table-balance-due-label'], #table-totals>* [data-element='total-table-balance-due-label'],
@ -225,7 +234,6 @@
} }
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {
padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
} }
@ -350,10 +358,10 @@
.pqrcode {} .pqrcode {}
#qr-bill{ #qr-bill {
width:100% !important; width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
/** Hide company logo **/ /** Hide company logo **/
@ -405,7 +413,7 @@
<div id="client-details"></div> <div id="client-details"></div>
<div id="vendor-details"></div> <div id="vendor-details"></div>
<div id="shipping-details"></div> <div id="shipping-details"></div>
<div> <div class="entity-container">
<p class="entity-label" <p class="entity-label"
style="font-size:32px; font-weight: bold; color:$primary_color;">$entity_label</p> style="font-size:32px; font-weight: bold; color:$primary_color;">$entity_label</p>
<table id="entity-details" cellspacing="0" cellpadding="0" dir="ltr"></table> <table id="entity-details" cellspacing="0" cellpadding="0" dir="ltr"></table>
@ -443,6 +451,6 @@
<div id="footer" style=""> <div id="footer" style="">
<div style="width: 100%;"> <div style="width: 100%;">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>
</div> </div>

View File

@ -40,19 +40,21 @@
padding: 0; padding: 0;
} }
#qr-bill{ #qr-bill {
width:100% !important; width: 100% !important;
} }
.header-container { .header-container {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: minmax(0, 1.5fr) auto minmax(0, 1fr);
justify-content: space-between; align-items: start;
width:100%; gap: 1rem;
width: 100%;
} }
.company-logo-container { .company-logo-container {
display: inline-block; justify-self: start;
align-self: start;
} }
.company-logo { .company-logo {
@ -60,6 +62,8 @@
} }
#company-details { #company-details {
justify-self: center;
align-self: start;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
@ -70,6 +74,8 @@
} }
#company-address { #company-address {
justify-self: end;
align-self: start;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
@ -85,9 +91,11 @@
} }
.client-and-entity-wrapper { .client-and-entity-wrapper {
padding: 1rem; padding-top: 1rem;
padding-bottom: 1rem;
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: minmax(0, 2fr) auto minmax(0, 1fr);
align-items: start;
border-top: 1px solid #d8d8d8; border-top: 1px solid #d8d8d8;
border-bottom: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8;
} }
@ -97,6 +105,9 @@
text-align: left; text-align: left;
margin-right: 20px; margin-right: 20px;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
justify-self: start;
align-self: start;
padding-left: 1rem;
} }
#entity-details>tr, #entity-details>tr,
@ -110,7 +121,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
padding-right: 30px; justify-self: center;
align-self: start;
} }
#client-details> :first-child { #client-details> :first-child {
@ -121,6 +133,9 @@
display: $show_shipping_address; display: $show_shipping_address;
flex-direction: column; flex-direction: column;
line-height: var(--line-height); line-height: var(--line-height);
justify-self: end;
align-self: start;
padding-left: 1rem;
} }
[data-ref="table"] { [data-ref="table"] {
@ -153,7 +168,7 @@
padding: 1rem 1rem; padding: 1rem 1rem;
} }
th.right-radius { th.right-radius {
padding-right: 1rem; padding-right: 1rem;
text-align: right; text-align: right;
} }
@ -162,10 +177,10 @@
text-align: right; text-align: right;
} }
[data-ref='product_table-product.item-td']{ [data-ref='product_table-product.item-td'] {
color: var(--primary-color); color: var(--primary-color);
} }
[data-ref="table"]>tbody>tr:nth-child(odd) { [data-ref="table"]>tbody>tr:nth-child(odd) {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
@ -176,9 +191,8 @@
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: 0rem; padding-top: 0rem;
padding-right: 1rem; padding-right: 1rem;
padding-left: 1rem;
gap: 80px; gap: 80px;
page-break-inside: avoid; page-break-inside: auto;
overflow: visible !important; overflow: visible !important;
} }
@ -202,15 +216,6 @@
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
} }
#table-totals>* [data-element='product-table-balance-due-label'],
#table-totals>* [data-element='product-table-balance-due'] {
font-weight: bold;
}
#table-totals>* [data-element='product-table-balance-due'] {
color: var(--primary-color);
}
#table-totals>*> :last-child { #table-totals>*> :last-child {
text-align: right; text-align: right;
padding-right: 0.5rem; padding-right: 0.5rem;
@ -218,7 +223,6 @@
#footer { #footer {
margin-top: 10px; margin-top: 10px;
margin-left: 1rem;
} }
/** Markdown-specific styles. **/ /** Markdown-specific styles. **/
@ -260,7 +264,8 @@
bottom: 0; bottom: 0;
} }
[data-ref='product_table-product.description-td'], td { [data-ref='product_table-product.description-td'],
td {
min-width: 100%; min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -363,7 +368,7 @@
</div> </div>
<p class="entity-label">$entity_label</p> <p class="entity-label">$entity_label</p>
<div class="client-and-entity-wrapper"> <div class="client-and-entity-wrapper">
<div> <div class="entity-details-container">
<table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table> <table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table>
</div> </div>
<div id="client-details"></div> <div id="client-details"></div>
@ -381,10 +386,15 @@
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
</div> </div>
</td> </td>
</tr> </tr>
<tr>
<td>
<div id="table-totals" cellspacing="0">$status_logo</div>
</td>
</tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>

View File

@ -9,7 +9,7 @@
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
@ -21,7 +21,9 @@
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
@ -40,35 +42,35 @@
.header-wrapper { .header-wrapper {
display: grid; display: grid;
grid-template-rows:0.5fr; grid-template-rows: 0.5fr;
grid-template-columns: auto auto auto auto; grid-template-columns: auto auto auto auto auto;
grid-template-areas: "a b c d e"; grid-template-areas: "a b c d e";
grid-auto-columns: minmax(0, 1fr); grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column; grid-auto-flow: column;
justify-content:left; align-items: start;
gap: 20px; gap: 0px;
line-height: var(--line-height); line-height: var(--line-height);
} }
.company-logo { .company-logo {
max-width: $company_logo_size; max-width: $company_logo_size;
float:right; float: right;
} }
,logo-wrapper { .logo-wrapper {
grid-area: e; grid-area: e;
align-content: right; justify-self: end;
border:1px solid #000; align-self: start;
} }
#entity-details { #entity-details {
width: 100%; width: 100%;
white-space: nowrap; white-space: nowrap;
margin-right: 3rem; text-align: right;
} }
#entity-details p { #entity-details p {
margin-top: 5px; margin-top: 5px;
} }
.header-wrapper #client-details, .header-wrapper #client-details,
@ -84,51 +86,72 @@
font-weight: bold; font-weight: bold;
} }
.header-wrapper #client-details > *:first-child { .header-wrapper #client-details>*:first-child {
font-weight: bold; font-weight: bold;
} }
.header-wrapper .company-info-wrapper > * { .header-wrapper .company-info-wrapper>* {
margin-bottom: 1rem; margin-bottom: 1rem;
grid-row-end: 4; grid-row-end: 4;
} }
.entity-label-wrapper { .entity-label-wrapper {
/* display: flex; */
display: grid; display: grid;
grid-template-columns: 3fr 1fr; grid-template-columns: 2fr 1fr;
width: 100%;
word-break: break-all;
box-sizing: border-box;
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1rem;
} }
.entity-label-wrapper .entity-label > * { .entity-label {
display: flex;
}
.entity-label h4 {
margin-top: 0rem;
margin-bottom: 0rem;
}
.entity-details-wrapper {
flex-shrink: 0;
}
.entity-label-wrapper .entity-label>* {
font-size: 3rem; font-size: 3rem;
} }
.entity-label-wrapper .entity-label > *:first-child { .entity-label-wrapper .entity-label>*:first-child {
text-transform: uppercase; text-transform: uppercase;
white-space: nowrap;
} }
.entity-label-wrapper .entity-label > *:last-child { .entity-label-wrapper .entity-label>*:last-child {
color: var(--primary-color); color: var(--primary-color);
font-style: italic; font-style: italic;
}
.entity-label-wrapper #entity-details {
text-align: left;
} }
#shipping-details { #shipping-details {
display: $show_shipping_address; display: $show_shipping_address;
flex-direction: column; flex-direction: column;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
margin-left: 1rem;
} }
.entity-label-wrapper #entity-details > tr, .entity-label-wrapper #entity-details>tr,
.entity-label-wrapper #entity-details th { .entity-label-wrapper #entity-details th {
font-weight: normal; font-weight: normal;
line-height: var(--line-height);
padding-left: 1rem;
} }
[data-ref="table"] { [data-ref="table"] {
margin-bottom: 5px; margin-bottom: 5px;
min-width: 100%; min-width: 100%;
table-layout: fixed; table-layout: fixed;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -136,7 +159,7 @@
[data-ref="table"]:not(:empty) { [data-ref="table"]:not(:empty) {
border-top: 5px solid var(--primary-color); border-top: 5px solid var(--primary-color);
margin-top: 3rem; margin-top: 1rem;
} }
.task-time-details { .task-time-details {
@ -152,15 +175,15 @@
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[data-ref='product_table-product.item-td']{ [data-ref='product_table-product.item-td'] {
color: var(--primary-color); color: var(--primary-color);
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
font-size: 1.1rem; font-size: 1.1rem;
padding: 1rem; padding: 1rem;
} }
@ -173,11 +196,11 @@
text-align: right !important; text-align: right !important;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr:nth-child(odd) { [data-ref="table"]>tbody>tr:nth-child(odd) {
background-color: #e8e8e8; background-color: #e8e8e8;
} }
@ -187,8 +210,7 @@
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: 1rem; padding-top: 1rem;
margin-right: .75rem; margin-right: .75rem;
gap: 80px; page-break-inside: auto;
page-break-inside:auto;
overflow: visible !important; overflow: visible !important;
} }
@ -202,7 +224,7 @@
margin-top: .75rem; margin-top: .75rem;
} }
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
@ -212,25 +234,15 @@
text-align: right; text-align: right;
} }
#table-totals #table-totals>*>* {}
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
color: var(--primary-color);
}
#table-totals > * > * { #table-totals>*> :last-child {
padding-left: 0.5rem;
}
#table-totals > * > :last-child {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {
padding-left: 0.5rem; padding-right: 0.8rem;
padding-right:0.8rem;
} }
#footer { #footer {
@ -259,23 +271,26 @@
.repeating-footer-space { .repeating-footer-space {
height: 10px; height: 10px;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
} }
[data-element='product_table-product.description-td'], td { [data-element='product_table-product.description-td'],
min-width:100%; td {
min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -286,25 +301,25 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -315,14 +330,12 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
@ -356,66 +369,62 @@
</style> </style>
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div id="body"> <div id="body">
<div class="header-wrapper"> <div class="header-wrapper">
<div id="client-details"></div> <div id="client-details"></div>
<div id="vendor-details"></div> <div id="vendor-details"></div>
<div id="shipping-details"></div> <div id="shipping-details"></div>
<div class="company-info-wrapper"> <div class="company-info-wrapper">
<div id="company-details"></div> <div id="company-details"></div>
<div id="company-address"></div> <div id="company-address"></div>
</div> </div>
<div class="logo-wrapper"> <div class="logo-wrapper">
<img <img class="company-logo" src="$company.logo" alt="$company.name logo" />
class="company-logo" </div>
src="$company.logo"
alt="$company.name logo"
/>
</div>
</div>
<div class="entity-label-wrapper">
<h1 class="entity-label">
<span>$entity_label</span>&nbsp;
<span>#$entity_number</span>
</h1>
<div class="entity-details-wrapper">
<table id="entity-details" cellspacing="0" dir="$dir"></table>
</div> </div>
</div> <div class="entity-label-wrapper">
<table id="product-table" cellspacing="0" data-ref="table"></table> <div class="entity-label">
<table id="task-table" cellspacing="0" data-ref="table"></table> <h4>$entity_label</h4>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <h4>#$entity_number</h4>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> </div>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <div class="entity-details-wrapper">
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <table id="entity-details" cellspacing="0" dir="$dir"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> </div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> </div>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <table id="task-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<div id="table-totals" cellspacing="0">$status_logo</div> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
</div> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
</td> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
</tr> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
</tbody> <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<tfoot> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<tr> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<td> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div class="repeating-footer-space">&nbsp;</div> <div id="table-totals" cellspacing="0">$status_logo</div>
</td> </div>
</tr> </td>
</tfoot> </tr>
</tbody>
<tfoot>
<tr>
<td>
<div class="repeating-footer-space">&nbsp;</div>
</td>
</tr>
</tfoot>
</table> </table>
<div class="repeating-header" id="header"></div> <div class="repeating-header" id="header"></div>
@ -424,6 +433,6 @@
$entity_images $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -9,8 +9,8 @@
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -20,7 +20,9 @@
zoom: 80%; zoom: 80%;
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
@ -35,7 +37,7 @@
} }
.company-logo { .company-logo {
/* max-width: 55%;*/ /* max-width: 55%;*/
max-width: $company_logo_size; max-width: $company_logo_size;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -47,15 +49,13 @@
border-bottom: 4px solid; border-bottom: 4px solid;
} }
.company-logo-wrapper { .company-logo-wrapper {}
}
.right-radius { .right-radius {
padding-right: 1rem; padding-right: 1rem;
text-align:right; text-align: right;
} }
.client-entity-wrapper { .client-entity-wrapper {
width: 100%; width: 100%;
margin-top: 1rem; margin-top: 1rem;
@ -74,19 +74,19 @@
table-layout: fixed; table-layout: fixed;
} }
#entity-details p { #entity-details p {
margin-right: 0px; margin-right: 0px;
margin-top: 0px; margin-top: 0px;
white-space: nowrap; white-space: nowrap;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
} }
.client-entity-wrapper .wrapper-info-text { .client-entity-wrapper .wrapper-info-text {
display: block; display: block;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: normal; font-weight: normal;
} }
.client-entity-wrapper .shipping-info-text { .client-entity-wrapper .shipping-info-text {
display: block; display: block;
font-size: 1.5rem; font-size: 1.5rem;
@ -94,7 +94,10 @@
display: $show_shipping_address; display: $show_shipping_address;
} }
.wrapper-right-side { .wrapper-right-side {}
.text-with-client {
margin-right: 1px;
} }
.client-entity-wrapper .wrapper-left-side #client-details, .client-entity-wrapper .wrapper-left-side #client-details,
@ -122,17 +125,16 @@
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
} }
.client-entity-wrapper #entity-details > tr, .client-entity-wrapper #entity-details>tr,
.client-entity-wrapper #entity-details th { .client-entity-wrapper #entity-details th {
font-weight: normal; font-weight: normal;
padding-right:8px; padding-right: 8px;
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
} }
[data-ref="table"] { [data-ref="table"] {
margin-top: 3rem; margin-top: 3rem;
margin-bottom: 5 margin-bottom: 5px;
px;
min-width: 100%; min-width: 100%;
table-layout: fixed; table-layout: fixed;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -144,11 +146,11 @@
color: grey; color: grey;
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
font-size: 1.1rem; font-size: 1.1rem;
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
padding-left: 1rem; padding-left: 1rem;
@ -156,22 +158,22 @@
font-weight: bold; font-weight: bold;
} }
[data-ref="table"] > thead > tr > th:last-child { [data-ref="table"]>thead>tr>th:last-child {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
border-bottom: 1pt solid; border-bottom: 1pt solid;
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr:first-child > td { [data-ref="table"]>tbody>tr:first-child>td {
border-top: 1pt solid !important; border-top: 1pt solid !important;
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr > td:last-child { [data-ref="table"]>tbody>tr>td:last-child {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
@ -185,10 +187,8 @@
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: 0.5rem; padding-top: 0.5rem;
padding-left: 1rem;
margin-right: .75rem;
gap: 80px; gap: 80px;
page-break-inside:auto; page-break-inside: auto;
overflow: visible !important; overflow: visible !important;
} }
@ -202,7 +202,7 @@
margin-top: .75rem; margin-top: .75rem;
} }
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
@ -210,31 +210,15 @@
#table-totals>.totals-table-right-side>*> :nth-child(2) { #table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right; text-align: right;
margin-right: 1rem;
} }
#table-totals #table-totals>*> :last-child {
> *
[data-element='product-table-balance-due-label'],
#table-totals
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
}
#table-totals
> *
[data-element='product-table-balance-due'] {
color: var(--primary-color);
}
#table-totals > * > :last-child {
text-align: right; text-align: right;
padding-right: 0.5rem; margin-right: .75rem;
} }
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {}
padding-left: 1rem
}
#footer { #footer {
margin-top: 30px; margin-top: 30px;
@ -262,27 +246,31 @@
.repeating-footer-space { .repeating-footer-space {
height: 10px; height: 10px;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
} }
[data-ref='product_table-product.description-td'], td { [data-ref='product_table-product.description-td'],
min-width:100%; td {
min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[data-ref='task_table-task.description-th'] { [data-ref='task_table-task.description-th'] {
overflow-wrap: break-word; overflow-wrap: break-word;
min-width: 100px !important; min-width: 100px !important;
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -293,25 +281,25 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -322,14 +310,12 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
@ -363,66 +349,66 @@
</style> </style>
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div id="body"> <div id="body">
<div class="company-logo-wrapper"> <div class="company-logo-wrapper">
<img class="company-logo" src="$company.logo" alt="$company.name logo"> <img class="company-logo" src="$company.logo" alt="$company.name logo">
</div>
<hr class="double-border">
<div class="client-entity-wrapper" style="">
<div class="wrapper-left-side">
<div class="text-with-client">
<h2 class="wrapper-info-text">$to_label</h2>
<div id="client-details"></div>
<div id="vendor-details"></div>
</div>
<div class="shipping-info" style="">
<h2 class="shipping-info-text">$shipping_label</h2>
<div id="shipping-details"></div>
</div>
<div class="company-info" style="">
<h2 class="wrapper-info-text">$from_label</h2>
<div id="company-details"></div>
<div id="company-address"></div>
</div>
<div class="wrapper-right-side">
<h2 class="wrapper-info-text">$details_label</h2>
<table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table>
</div> </div>
<hr class="double-border">
<div class="client-entity-wrapper" style="">
<div class="wrapper-left-side">
<div class="text-with-client">
<h2 class="wrapper-info-text">$to_label</h2>
<div id="client-details"></div>
<div id="vendor-details"></div>
</div>
<div class="shipping-info" style="">
<h2 class="shipping-info-text">$shipping_label</h2>
<div id="shipping-details"></div>
</div>
<div class="company-info" style="">
<h2 class="wrapper-info-text">$from_label</h2>
<div id="company-details"></div>
<div id="company-address"></div>
</div>
<div class="wrapper-right-side">
<h2 class="wrapper-info-text">$details_label</h2>
<table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table>
</div>
</div>
</div>
<table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
</div> </div>
</div> </td>
<table id="product-table" cellspacing="0" data-ref="table"></table> </tr>
<table id="task-table" cellspacing="0" data-ref="table"></table> </tbody>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <tfoot>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> <tr>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <td>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <div class="repeating-footer-space">&nbsp;</div>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> </td>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> </tr>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> </tfoot>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<div class="repeating-footer-space">&nbsp;</div>
</td>
</tr>
</tfoot>
</table> </table>
<div class="repeating-header" id="header"></div> <div class="repeating-header" id="header"></div>
@ -431,8 +417,6 @@
$entity_images $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div>
</div>

View File

@ -4,12 +4,12 @@
:root { :root {
--primary-color: $primary_color; --primary-color: $primary_color;
--secondary-color: $secondary_color; --secondary-color: $secondary_color;
--line-height: 1.6; --line-height: 1.6;
} }
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
@ -20,7 +20,9 @@
zoom: 80%; zoom: 80%;
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
@ -37,7 +39,7 @@
.header-wrapper { .header-wrapper {
display: grid; display: grid;
grid-template-columns: 0.5fr 1.5fr; grid-template-columns: 0.5fr 1.5fr;
gap: 20px; gap: 0px;
line-height: var(--line-height); line-height: var(--line-height);
} }
@ -55,7 +57,7 @@
padding-left: 1rem; padding-left: 1rem;
} }
.header-wrapper .header-left-side-wrapper > * { .header-wrapper .header-left-side-wrapper>* {
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
} }
@ -63,6 +65,7 @@
.header-wrapper .header-left-side-wrapper #company-address { .header-wrapper .header-left-side-wrapper #company-address {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-right: 1rem;
} }
.header-wrapper .header-right-side-wrapper { .header-wrapper .header-right-side-wrapper {
@ -75,20 +78,37 @@
grid-template-areas: "a b c"; grid-template-areas: "a b c";
grid-auto-columns: minmax(0, 1fr); grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column; grid-auto-flow: column;
justify-content:left; align-items: start;
} }
.header-wrapper .header-right-side-wrapper #client-details { .header-wrapper .header-right-side-wrapper #client-details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 0.8rem; margin-top: 0.8rem;
grid-area: a;
justify-self: left !important;
align-self: start !important;
} }
.header-wrapper .header-right-side-wrapper #shipping-details { .header-wrapper .header-right-side-wrapper #shipping-details {
display: $show_shipping_address; display: $show_shipping_address_block;
flex-direction: column; flex-direction: column;
margin-top: 0.8rem; margin-top: 0.8rem;
grid-area: b;
}
.header-wrapper .header-right-side-wrapper-left-shipping {
padding-left: 1rem;
border-left: 1px solid #303030;
float: left;
}
.header-wrapper .header-right-side-wrapper-right {
grid-area: c;
justify-self: end;
align-self: start;
justify-content: end;
float: right;
} }
.shipping-text-label { .shipping-text-label {
@ -105,6 +125,8 @@
.company-logo { .company-logo {
max-width: $company_logo_size; max-width: $company_logo_size;
float: right;
object-fit: contain;
} }
.entity-label { .entity-label {
@ -113,7 +135,7 @@
margin: 2rem 0; margin: 2rem 0;
} }
.entity-details-wrapper > * { .entity-details-wrapper>* {
margin-right: 1.5rem; margin-right: 1.5rem;
direction: $dir; direction: $dir;
} }
@ -129,10 +151,12 @@
text-transform: uppercase; text-transform: uppercase;
} }
.entity-details-wrapper .entity-property-label:not(:empty)::after {
[data-element='entity-details-wrapper-invoice-number-label'], content: ":";
.entity-details-wrapper }
[data-element='entity-details-wrapper-amount-due'] {
.entity-details-wrapper [data-element='entity-details-wrapper-invoice-number-label'],
.entity-details-wrapper [data-element='entity-details-wrapper-amount-due'] {
color: var(--primary-color); color: var(--primary-color);
font-weight: bold; font-weight: bold;
} }
@ -142,12 +166,12 @@
} }
td.left-radius { td.left-radius {
text-align:left !important; text-align: left !important;
padding-left: 1rem !important; padding-left: 1rem !important;
} }
th.left-radius { th.left-radius {
text-align:left !important; text-align: left !important;
padding-left: 0.5rem !important; padding-left: 0.5rem !important;
} }
@ -165,25 +189,25 @@
color: grey; color: grey;
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: bold;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
font-size: 1.1rem; font-size: 1.1rem;
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
padding-left: 1rem; padding-left: 1rem;
border-left: 1px solid; border-left: 1px solid;
} }
[data-ref="table"] > thead > tr > th:nth-last-child(2) { [data-ref="table"]>thead>tr>th:nth-last-child(2) {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
padding-left: 1rem; padding-left: 1rem;
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
@ -193,15 +217,14 @@
.right-radius { .right-radius {
text-align: right; text-align: right;
} }
#table-totals { #table-totals {
margin-top: 1rem; margin-top: 1rem;
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: 0.5rem; padding-top: 0.5rem;
margin-right: 1rem; margin-right: 0rem;
gap: 80px; page-break-inside: auto;
page-break-inside:auto;
overflow: visible !important; overflow: visible !important;
} }
@ -215,7 +238,7 @@
margin-top: .75rem; margin-top: .75rem;
} }
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
@ -225,22 +248,7 @@
text-align: right; text-align: right;
} }
#table-totals #table-totals>*> :last-child {
> *
[data-element='product-table-balance-due-label'],
#table-totals
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
}
#table-totals
> *
[data-element='product-table-balance-due'] {
color: var(--primary-color);
}
#table-totals > * > :last-child {
text-align: right; text-align: right;
padding-right: 0rem; padding-right: 0rem;
} }
@ -257,29 +265,27 @@
[data-ref='task_table-task.service-th'], [data-ref='task_table-task.service-th'],
[data-ref='product_table-product.item-th'] { [data-ref='product_table-product.item-th'] {
padding-left:1rem !important; padding-left: 1rem !important;
width:14%; width: 14%;
} }
[data-ref='task_table-task.description-th'],
[data-ref='product_table-product.description-th'],
[data-ref='product_table-product.description-td'], [data-ref='product_table-product.description-td'],
[data-ref="task_table-task.description-td"] { [data-ref="task_table-task.description-td"] {
min-width:100px;
overflow-wrap: break-word;
}
[data-ref='task_table-task.description-th'] {
overflow-wrap: break-word;
min-width: 100px !important; min-width: 100px !important;
;
overflow-wrap: break-word;
} }
[data-ref="product_table-product.unit_cost-td"], [data-ref="product_table-product.unit_cost-td"],
[data-ref="product_table-product.unit_cost-th"], [data-ref="product_table-product.unit_cost-th"],
[data-ref='task_table-task.cost-th'], [data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.cost-td']{ [data-ref='task_table-task.cost-td'] {
text-align: center !important; text-align: center !important;
width: 10%; width: 10%;
padding-left:0 !important; padding-left: 0 !important;
padding-right:0 !important; padding-right: 0 !important;
} }
[data-ref="product_table-product.quantity-th"], [data-ref="product_table-product.quantity-th"],
@ -287,19 +293,27 @@
[data-ref='task_table-task.hours-th'], [data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.hours-td'] { [data-ref='task_table-task.hours-td'] {
width: 10%; width: 10%;
text-align: center !important; text-align: center !important;
padding-left:0 !important; padding-left: 0 !important;
padding-right:0 !important; padding-right: 0 !important;
}
[data-ref="task_table-task.discount-th"],
[data-ref="task_table-task.discount-td"],
[data-ref="product_table-product.discount-th"],
[data-ref="product_table-product.discount-td"] {
text-align: left !important;
width: 9%;
} }
[data-ref="task_table-task.tax1-th"], [data-ref="task_table-task.tax1-th"],
[data-ref="task_table-task.tax1-td"], [data-ref="task_table-task.tax1-td"],
[data-ref="product_table-product.tax1-th"], [data-ref="product_table-product.tax1-th"],
[data-ref="product_table-product.tax1-td"] { [data-ref="product_table-product.tax1-td"] {
text-align: center; text-align: center !important;
width: 9%; width: 9%;
padding-left:0 !important; padding-left: 0 !important;
padding-right:0 !important; padding-right: 0 !important;
} }
[data-ref="product_table-product.line_total-th"], [data-ref="product_table-product.line_total-th"],
@ -308,24 +322,21 @@
width: 13%; width: 13%;
} }
[data-ref="task_table-task.line_total-th"],
[data-ref="task_table-task.line_total-th"],
[data-ref="task_table-task.line_total-td"] { [data-ref="task_table-task.line_total-td"] {
text-align: right; text-align: right;
width: 13%; width: 13%;
} }
[data-ref="totals_table-outstanding-label"] { [data-ref="totals_table-outstanding-label"] {
font-weight:bold; font-weight: bold;
font-size:120%; font-size: 120%;
} }
[data-ref="totals_table-outstanding"] { [data-ref="totals_table-outstanding"] {
color: var(--primary-color); color: var(--primary-color);
font-weight:bold; font-weight: bold;
font-size:120%; font-size: 120%;
} }
[data-ref="statement-totals"] { [data-ref="statement-totals"] {
@ -344,17 +355,19 @@
.repeating-footer-space { .repeating-footer-space {
height: 0; height: 0;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -365,24 +378,24 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -393,13 +406,12 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{
width:100% !important; .pqrcode {}
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
@ -433,90 +445,84 @@
</style> </style>
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div id="body"> <div id="body">
<div class="header-wrapper"> <div class="header-wrapper">
<div class="header-left-side-wrapper"> <div class="header-left-side-wrapper">
<p class="header-text-label">$from_label:</p> <p class="header-text-label">$from_label:</p>
<div id="company-details"></div> <div id="company-details"></div>
<div id="company-address"></div> <div id="company-address"></div>
</div> </div>
<div class="header-right-side-wrapper"> <div class="header-right-side-wrapper">
<div class="header-right-side-wrapper-left"> <div class="header-right-side-wrapper-left">
<p class="header-text-label">$to_label:</p> <p class="header-text-label">$to_label:</p>
<div id="client-details"></div> <div id="client-details"></div>
<div id="vendor-details"></div> <div id="vendor-details"></div>
</div> </div>
<div class="header-right-side-wrapper-left"> <div class="header-right-side-wrapper-left-shipping">
<p class="shipping-text-label">$shipping_label:</p> <p class="shipping-text-label">$shipping_label:</p>
<div id="shipping-details"></div> <div id="shipping-details"></div>
</div>
<div class="header-right-side-wrapper-right">
<img class="company-logo" src="$company.logo" alt="$company.name logo" />
</div>
</div>
</div> </div>
<div class="header-right-side-wrapper-right"> <h1 class="entity-label">$entity_label</h1>
<img <div class="entity-details-wrapper">
class="company-logo" <div>
src="$company.logo" <span class="entity-property-label"
alt="$company.name logo" data-element="entity-details-wrapper-invoice-number-label">
/> $entity_number_label
</div> </span>
</div> <span class="entity-property-value">$entity_number</span>
</div> </div>
<h1 class="entity-label">$entity_label</h1> <div>
<div class="entity-details-wrapper"> <span class="entity-property-label">$date_label</span>
<div> <span class="entity-property-value">$date</span>
<span class="entity-property-label" data-element="entity-details-wrapper-invoice-number-label"> </div>
$entity_number_label: <div>
</span> <span class="entity-property-label">$payment_due_label</span>
<span class="entity-property-value">$entity_number</span> <span class="entity-property-value">$payment_due</span>
</div> </div>
<div> <div>
<span class="entity-property-label">$date_label:</span> <span class="entity-property-label">$amount_due_label</span>
<span class="entity-property-value">$date</span> <span class="entity-property-value" data-element="entity-details-wrapper-amount-due"
</div> style="float:right;">$amount_due</span>
<div> </div>
<span class="entity-property-label">$payment_due_label:</span> </div>
<span class="entity-property-value">$payment_due</span> <table id="product-table" cellspacing="0" data-ref="table"></table>
</div> <table id="task-table" cellspacing="0" data-ref="table"></table>
<div> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<span class="entity-property-label">$amount_due_label:</span> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<span <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
class="entity-property-value" <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
data-element="entity-details-wrapper-amount-due" <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
>$amount_due</span <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
</div> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
</div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<table id="product-table" cellspacing="0" data-ref="table"></table> <div id="table-totals" cellspacing="0">$status_logo</div>
<table id="task-table" cellspacing="0" data-ref="table"></table> </div>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> </td>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> </tr>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> </tbody>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <tfoot>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> <tr>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> <td>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div class="repeating-footer-space">&nbsp;</div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> </td>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> </tr>
<div id="table-totals" cellspacing="0">$status_logo</div> </tfoot>
</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<div class="repeating-footer-space">&nbsp;</div>
</td>
</tr>
</tfoot>
</table> </table>
<div class="repeating-header" id="header"></div> <div class="repeating-header" id="header"></div>
@ -525,8 +531,7 @@
$entity_images $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div>
</div>

View File

@ -1,488 +1,502 @@
<style id="style"> <style id="style">
@import url($font_url); @import url($font_url);
:root { :root {
--primary-color: $primary_color; --primary-color: $primary_color;
--secondary-color: $secondary_color; --secondary-color: $secondary_color;
} }
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
font-family: $font_name, Helvetica, sans-serif; font-family: $font_name, Helvetica, sans-serif;
font-size: $font_size !important; font-size: $font_size !important;
zoom: 80%; zoom: 80%;
} }
table tr td, table tr, th { table tr td,
font-size: $font_size !important; table tr,
} th {
font-size: $font_size !important;
body, html { }
margin: 0;
padding: 0; body,
} html {
margin: 0;
@page { padding: 0;
margin: 0 !important; }
size: $page_size $page_layout;
} @page {
margin: 0 !important;
p { size: $page_size $page_layout;
margin: 0; }
padding: 0;
} p {
margin: 0;
#spacer-table > * > tr > td { padding: 0;
padding: 0; }
}
#spacer-table>*>tr>td {
#spacer-table{ padding: 0;
width: 100%; }
}
#spacer-table {
#header { width: 100%;
background-color: var(--primary-color); }
color: white;
display: grid; #header {
grid-template-columns: 1.5fr 1fr; background-color: var(--primary-color);
position:fixed; color: white;
top: 0; display: grid;
width: 100%; grid-template-columns: 1.5fr 1fr;
} position: fixed;
top: 0;
#header, #header-spacer { width: 100%;
height: 160px; }
padding: 1rem;
margin-bottom: 1rem; #header,
} #header-spacer {
height: 160px;
.company-name { padding: 1rem;
text-align: left; margin-bottom: 1rem;
margin-left: 1rem; }
}
.company-name {
#header .company-name { text-align: left;
font-size: 2rem; margin-left: 1rem;
} }
#entity-details { #header .company-name {
text-align: left; font-size: 2rem;
color: #fff4e9 !important; }
line-height: 1.6;
} #entity-details {
margin-top: 1rem;
#entity-details > tr, text-align: left;
#entity-details th { color: #fff4e9 !important;
font-weight: normal; line-height: 1.6 !important;
} }
.logo-client-wrapper { #entity-details>tr,
margin-left: 2rem; #entity-details th {
display: grid; font-weight: normal;
grid-template-columns: 1fr auto auto; }
margin-top: 1rem;
margin-bottom: 1rem; .logo-client-wrapper {
} margin-left: 2rem;
display: grid;
.company-logo { grid-template-columns: 2fr auto auto;
max-width: $company_logo_size; margin-top: 1rem;
} margin-bottom: 1rem;
}
#client-details {
display: flex; .company-logo {
flex-direction: column; max-width: $company_logo_size;
margin-left: 10px; }
margin-right: 10px;
#client-details {
} display: flex;
flex-direction: column;
#shipping-details { margin-left: 10px;
display: $show_shipping_address; margin-right: 2rem;
flex-direction: column; float: right;
margin-left: 10px;
margin-right: 10px; }
}
#shipping-details {
#client-details > * { display: $show_shipping_address;
margin-bottom: 0.5rem; flex-direction: column;
} padding-left: 10px;
padding-right: 2rem;
.table-wrapper { float: right;
margin: 0rem 2rem; }
}
#shipping-details>* {
[data-ref="table"] { margin-bottom: 0.5rem;
min-width: 100%; }
table-layout: fixed;
overflow-wrap: break-word; #client-details>* {
} margin-bottom: 0.5rem;
}
.task-time-details {
display: block; .table-wrapper {
margin-top: 5px; margin: 0rem 2rem;
color: grey; }
}
[data-ref="delivery_note-client.city_state_postal"] p {
[data-ref="table"] > thead { display: inline-block;
text-align: left; white-space: nowrap;
width: 100%; }
}
[data-ref="table"] {
[data-ref="table"] th + th { min-width: 100%;
border-left: 2px solid white; table-layout: fixed;
} overflow-wrap: break-word;
}
[data-ref="table"] > thead > tr > th {
padding: 0.8rem; .task-time-details {
background-color: var(--secondary-color); display: block;
color: white; margin-top: 5px;
} color: grey;
}
.right-radius {
text-align: right !important; [data-ref="table"]>thead {
} text-align: left;
width: 100%;
[data-ref="table"] > tbody > tr > td { }
border-bottom: 1px solid var(--secondary-color);
padding: 0.8rem; [data-ref="table"] th+th {
} border-left: 2px solid white;
}
td.left-radius {
font-weight: bold; [data-ref="table"]>thead>tr>th {
} padding: 0.8rem;
background-color: var(--secondary-color);
color: white;
#footer { }
background-color: var(--primary-color);
width: 100%; .right-radius {
position: fixed; text-align: right !important;
bottom: 0; }
padding-left: 1rem;
} [data-ref="table"]>tbody>tr>td {
border-bottom: 1px solid var(--secondary-color);
padding: 0.8rem;
#footer, #footer-spacer { }
height: 220px;
padding: 0rem 0rem; td.left-radius {
margin-top: 0rem; font-weight: bold;
} }
.footer-content {
display: flex; #footer {
width: calc(100% - 2rem); background-color: var(--primary-color);
margin: 0 1rem; width: 100%;
color: #fff4e9; position: fixed;
justify-content: space-between; bottom: 0;
align-items: flex-start; padding-left: 1rem;
} }
/* Main footer text area */
.footer-content > div:first-child { #footer,
width: 50%; #footer-spacer {
margin-right: 2rem; height: 220px;
} padding: 0rem 0rem;
margin-top: 0rem;
/* Company details/address wrapper */ }
.footer-company-details-address-wrapper {
width: 50%; .footer-content {
display: grid; display: flex;
grid-template-columns: 1fr 1fr; width: calc(100% - 2rem);
gap: 1rem; margin: 0 1rem;
margin-top: 1rem; color: #fff4e9;
} justify-content: space-between;
align-items: flex-start;
#company-details, }
#company-address {
display: flex; /* Main footer text area */
flex-direction: column; .footer-content>div:first-child {
gap: 0.5rem; width: 50%;
} margin-right: 2rem;
}
#company-address > *,
#company-details > * { /* Company details/address wrapper */
} .footer-company-details-address-wrapper {
width: 50%;
#table-totals { display: grid;
page-break-inside: avoid; grid-template-columns: 1fr 1fr;
} gap: 1rem;
margin-top: 1rem;
#table-totals { }
display: grid;
grid-template-columns: 2fr 1fr; #company-details,
padding-top: 0.5rem; #company-address {
padding-left: 2.5rem; display: flex;
padding-right: 3rem; flex-direction: column;
gap: 80px; }
}
#company-address>*,
#table-totals .totals-table-right-side > * { #company-details>* {
display: grid; }
grid-template-columns: 1fr 1fr;
} #table-totals {
page-break-inside: avoid;
#table-totals > .totals-table-right-side > * > :nth-child(1) { }
text-align: "left";
margin-top: 0.75rem; #table-totals {
} display: grid;
grid-template-columns: 2fr 1fr;
#table-totals padding-top: 0.5rem;
> .totals-table-right-side padding-left: 2rem;
> * padding-right: 3rem;
> :not([hidden]) gap: 80px;
~ :not([hidden]) { }
--tw-space-y-reverse: 0;
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); #table-totals .totals-table-right-side>* {
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); display: grid;
} grid-template-columns: 1fr 1fr;
}
#table-totals > .totals-table-right-side > * > :nth-child(2) {
text-align: right; #table-totals>.totals-table-right-side>*> :nth-child(1) {
} text-align: "left";
margin-top: 0.75rem;
#table-totals > * [data-element="product-table-balance-due-label"], }
#table-totals > * [data-element="product-table-balance-due"] {
font-weight: bold; #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
font-size: 1.3rem; --tw-space-y-reverse: 0;
} margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
[data-ref="total_table-footer"] { }
margin-top: 1rem;
margin-bottom: 1rem; #table-totals>.totals-table-right-side>*> :nth-child(2) {
} text-align: right;
}
table {
width: 100%; [data-ref="total_table-footer"] {
} margin-top: 1rem;
margin-bottom: 1rem;
table[data-ref="table"] th, }
table[data-ref="table"] td {
padding: 0.2rem 0.4rem; table {
text-align: left; width: 100%;
vertical-align: top; }
border-top: 1px solid #dee2e6;
} table[data-ref="table"] th,
table[data-ref="table"] td {
/** Markdown-specific styles. **/ padding: 0.2rem 0.4rem;
#product-table h3, text-align: left;
#task-table h3, vertical-align: top;
#delivery-note-table h3 { border-top: 1px solid #dee2e6;
font-size: 1rem; }
margin-bottom: 0;
} /** Markdown-specific styles. **/
#product-table h3,
[data-ref="statement-totals"] { #task-table h3,
margin-top: 1rem; #delivery-note-table h3 {
margin-bottom: 1rem; font-size: 1rem;
text-align: right; margin-bottom: 0;
margin-right: .75rem; }
}
[data-ref="statement-totals"] {
[data-ref*=".line_total-td"] { margin-top: 1rem;
white-space: nowrap; margin-bottom: 1rem;
} text-align: right;
margin-right: .75rem;
}
[data-ref='product_table-product.description-th'] {
min-width: 100px !important; [data-ref*=".line_total-td"] {
overflow-wrap: break-word; white-space: nowrap;
} }
[data-ref='product_table-product.item-td']{
font-weight: bold; [data-ref='product_table-product.description-th'],
} [data-ref='product_table-product.description-td'] {
min-width: 150px !important;
[data-ref='product_table-product.item-th']{ overflow-wrap: break-word;
width: 10%; }
}
[data-ref='product_table-product.item-td'] {
[data-ref='product_table-product.unit_cost-th'], font-weight: bold;
[data-ref='product_table-product.quantity-th'], }
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'], [data-ref='product_table-product.item-th'] {
[data-ref='product_table-product.product3-th'], width: 10%;
[data-ref='product_table-product.product4-th'], }
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.discount-th'], [data-ref='product_table-product.unit_cost-th'],
[data-ref='task_table-task.cost-th'], [data-ref='product_table-product.quantity-th'],
[data-ref='task_table-task.quantity-th'], [data-ref='product_table-product.product1-th'],
[data-ref='task_table-task.task1-th'], [data-ref='product_table-product.product2-th'],
[data-ref='task_table-task.task2-th'], [data-ref='product_table-product.product3-th'],
[data-ref='task_table-task.task3-th'], [data-ref='product_table-product.product4-th'],
[data-ref='task_table-task.task4-th'] { [data-ref='task_table-task.hours-th'],
width: 10%; [data-ref='task_table-task.cost-th'],
} [data-ref='task_table-task.quantity-th'],
[data-ref='task_table-task.task1-th'],
[data-ref='product_table-product.tax1-th'], [data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.tax1-th'] { [data-ref='task_table-task.task3-th'],
width: 10%; [data-ref='task_table-task.task4-th'] {
} width: 10%;
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'], [data-ref='product_table-product.tax1-th'],
[data-ref='task_table-task.line_total-th'], [data-ref='task_table-task.tax1-th'] {
[data-ref='task_table-task.line_total-td'] { width: 7%;
width: 12%; }
text-align: right;
} [data-ref='product_table-product.discount-th'],
[data-ref='task_table-task.discount-th'] {
[data-ref='task_table-task.description-th'] { width: 8%;
overflow-wrap: break-word; }
min-width: 100px !important;
} [data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'],
[data-ref='task_table-task.service-th']{ [data-ref='task_table-task.line_total-th'],
width: 10%; [data-ref='task_table-task.line_total-td'] {
} width: 12%;
text-align: right;
}
.stamp {
transform: rotate(12deg); [data-ref='task_table-task.description-th'] {
color: #555; overflow-wrap: break-word;
font-size: 3rem; min-width: 100px !important;
font-weight: 700; }
border: 0.25rem solid #555;
display: inline-block; [data-ref='task_table-task.service-th'] {
padding: 0.25rem 1rem; width: 10%;
text-transform: uppercase; }
border-radius: 1rem;
font-family: 'Courier';
mix-blend-mode: multiply; .stamp {
z-index:200 !important; transform: rotate(12deg);
position: fixed; color: #555;
text-align: center; font-size: 3rem;
} font-weight: 700;
border: 0.25rem solid #555;
.is-paid { display: inline-block;
color: #D23; padding: 0.25rem 1rem;
border: 1rem double #D23; text-transform: uppercase;
transform: rotate(-5deg); border-radius: 1rem;
font-size: 6rem; font-family: 'Courier';
font-family: "Open sans", Helvetica, Arial, sans-serif; mix-blend-mode: multiply;
border-radius: 0; z-index: 200 !important;
padding: 0.5rem; position: fixed;
opacity: 0.2; text-align: center;
z-index:200 !important; }
position: fixed;
display: $show_paid_stamp; .is-paid {
color: #D23;
} border: 1rem double #D23;
transform: rotate(-5deg);
.project-header { font-size: 6rem;
font-size: 1.2em; font-family: "Open sans", Helvetica, Arial, sans-serif;
margin-top: 0.1em; border-radius: 0;
margin-bottom: 0; padding: 0.5rem;
padding-bottom: 0; opacity: 0.2;
margin-left: 0; z-index: 200 !important;
margin-right: 0; position: fixed;
font-weight: bold; display: $show_paid_stamp;
color: #505050;
} }
.pqrcode { .project-header {
font-size: 1.2em;
} margin-top: 0.1em;
margin-bottom: 0;
#qr-bill{ padding-bottom: 0;
width:100% !important; margin-left: 0;
} margin-right: 0;
/** Useful snippets, uncomment to enable. **/ font-weight: bold;
color: #505050;
/** Hide company logo **/ }
/* .company-logo { display: none } */
.pqrcode {}
/* Hide company details */
/* #company-details > * { display: none } */ #qr-bill {
width: 100% !important;
/* Hide company address */ }
/* #company-address > * { display: none } */
/** Useful snippets, uncomment to enable. **/
/* Hide public notes */
/* [data-ref="total_table-public_notes"] { display: none } */ /** Hide company logo **/
/* .company-logo { display: none } */
/* Hide terms label */
/* [data-ref="total_table-terms-label"] { display: none } */ /* Hide company details */
/* #company-details > * { display: none } */
/* Hide totals table */
/* #table-totals { display: none } */ /* Hide company address */
/* #company-address > * { display: none } */
/* Hide totals table left side */
/* #table-totals div:first-child > * { display: none !important } */ /* Hide public notes */
/* [data-ref="total_table-public_notes"] { display: none } */
/* Hide totals table right side */
/* .totals-table-right-side { display: none } */ /* Hide terms label */
/* [data-ref="total_table-terms-label"] { display: none } */
/** For more info, please check our docs: https://invoiceninja.github.io **/
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/ /* Hide totals table */
/* #table-totals { display: none } */
/* Hide totals table left side */
/* #table-totals div:first-child > * { display: none !important } */
/* Hide totals table right side */
/* .totals-table-right-side { display: none } */
/** For more info, please check our docs: https://invoiceninja.github.io **/
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
</style> </style>
<div id="header"> <div id="header">
<div id="company-name-wrapper">
<h1 class="company-name">$company.name</h1> <h1 class="company-name">$company.name</h1>
</div>
<div id="entity-details-wrapper">
<table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table> <table id="entity-details" cellspacing="0" cellpadding="0" dir="$dir"></table>
</div>
</div> </div>
<div id="body"> <div id="body">
<table id="spacer-table" cellspacing="0"> <table id="spacer-table" cellspacing="0">
<thead> <thead>
<tr> <tr>
<td> <td>
<div id="header-spacer"></div> <div id="header-spacer"></div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div class="logo-client-wrapper"> <div class="logo-client-wrapper">
<img class="company-logo" src="$company.logo" alt="$company.name logo"/> <img class="company-logo" src="$company.logo" alt="$company.name logo" />
<div id="client-details"></div> <div id="shipping-details"></div>
<div id="vendor-details"></div> <div id="client-details"></div>
<div id="shipping-details"></div> <div id="vendor-details"></div>
</div> </div>
<div class="table-wrapper"> <div class="table-wrapper">
<table id="product-table" cellspacing="0" data-ref="table"></table> <table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table> <table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
</div> </div>
<div id="table-totals" cellspacing="0">$status_logo</div> <div id="table-totals" cellspacing="0">$status_logo</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td> <td>
<div id="footer-spacer"></div> <div id="footer-spacer"></div>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
$entity_images $entity_images
<div id="footer"> <div id="footer">
<div class="footer-content"> <div class="footer-content">
<div class="footer-text"> <div class="footer-text">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div>
<div class="footer-company-details-address-wrapper">
<div id="company-details"></div>
<div id="company-address"></div>
</div>
</div> </div>
</div> <div class="footer-company-details-address-wrapper">
<div id="company-details"></div>
<div id="company-address"></div>
</div>
</div>
</div>

View File

@ -48,7 +48,7 @@
.client-wrapper { .client-wrapper {
display: grid; display: grid;
grid-template-columns: 1fr auto 1fr; grid-template-columns: auto 1fr auto;
border: 0px solid #000; border: 0px solid #000;
} }
@ -64,7 +64,8 @@
#entity-container { #entity-container {
border: 0px solid #000; border: 0px solid #000;
width: 100%; margin-right: 0;
margin-left: auto;
} }
#entity-details { #entity-details {
@ -72,8 +73,7 @@
line-height: var(--line-height) !important; line-height: var(--line-height) !important;
white-space: nowrap; white-space: nowrap;
border: 0px solid #000; border: 0px solid #000;
margin-right: 0; float: right;
margin-left: auto;
} }
#entity-details>tr, #entity-details>tr,
@ -102,6 +102,8 @@
line-height: var(--line-height); line-height: var(--line-height);
white-space: nowrap; white-space: nowrap;
border: 0px solid #000; border: 0px solid #000;
margin-left: auto;
margin-right: auto;
} }
[data-ref="table"] { [data-ref="table"] {
@ -109,7 +111,6 @@
min-width: 100%; min-width: 100%;
table-layout: fixed; table-layout: fixed;
overflow-wrap: break-word; overflow-wrap: break-word;
margin-bottom: 5px;
} }
.task-time-details { .task-time-details {
@ -144,8 +145,6 @@
margin-top: 0rem; margin-top: 0rem;
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: .5rem;
padding-left: .5rem;
margin-right: .75rem; margin-right: .75rem;
gap: 80px; gap: 80px;
page-break-inside: auto; page-break-inside: auto;
@ -185,7 +184,7 @@
} }
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {
padding-left: 1rem padding-left: 0rem;
} }
#footer { #footer {
@ -239,54 +238,11 @@
} }
[data-ref='product_table-product.description-td'], [data-ref='product_table-product.description-td'],
[data-ref='task_table-task.description-th']{ [data-ref='task_table-task.description-th'] {
min-width: 100px !important; min-width: 150px !important;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[data-ref='product_table-product.item-th'],
[data-ref='product_table-product.unit_cost-th'],
[data-ref='task_table-task.service-th'],
[data-ref='task_table-task.cost-th']{
width: 12%;
}
[data-ref='product_table-product.quantity-th'],
[data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.quantity-th'],
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
[data-ref='product_table-product.product3-th'],
[data-ref='product_table-product.product4-th'] {
width: 10%;
}
[data-ref='product_table-product.tax1-th'] {
width: 6%;
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'],
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 12%;
text-align: right;
}
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.task1-th'],
[data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.task3-th'],
[data-ref='task_table-task.task4-th'] {
width: 10%;
}
[data-ref='task_table-task.tax1-th'] {
width: 10%;
}
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
@ -339,12 +295,12 @@
.pqrcode {} .pqrcode {}
#qr-bill{ #qr-bill {
width:100% !important; width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/

View File

@ -15,13 +15,15 @@
zoom: 80%; zoom: 80%;
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
@page { @page {
@ -30,7 +32,6 @@
margin-top: 1rem; margin-top: 1rem;
} }
p { p {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -47,10 +48,11 @@
background-color: var(--primary-color); background-color: var(--primary-color);
padding: 0.5rem; padding: 0.5rem;
border-radius: 10px; border-radius: 10px;
align-self: flex-start;
} }
#entity-details p { #entity-details p {
margin-right: 20px; margin-right: 20px;
white-space: nowrap; white-space: nowrap;
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.5rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.5rem * calc(1 - var(--tw-space-y-reverse)));
@ -64,25 +66,26 @@
line-height: 1.2; line-height: 1.2;
} }
.header-wrapper #entity-details > tr, .header-wrapper #entity-details>tr,
.header-wrapper #entity-details th { .header-wrapper #entity-details th {
font-weight: normal; font-weight: normal;
} }
.company-logo { .company-logo {
/* max-width: 65%;*/ /* max-width: 65%;*/
max-width: $company_logo_size; max-width: $company_logo_size;
} }
.contacts-wrapper { .contacts-wrapper {
display: grid; display: grid;
gap: 20px; gap: 0px;
padding: 1rem 0rem 0rem 2rem; padding: 1rem 0rem 0rem 1rem;
grid-template-columns: 1fr 1fr auto; grid-template-columns: 1fr 1fr auto;
grid-template-areas: "a b c"; grid-template-areas: "a b c";
grid-auto-columns: minmax(0, 5fr); grid-auto-columns: minmax(0, 5fr);
grid-auto-flow: column; grid-auto-flow: column;
justify-content: space-between; justify-content: space-between;
margin-left: 1rem;
} }
@ -115,7 +118,7 @@
.contacts-wrapper .company-info { .contacts-wrapper .company-info {
margin-top: 1rem; margin-top: 1rem;
padding: 1rem; padding-top: 1rem;
border-top: 1px solid var(--primary-color); border-top: 1px solid var(--primary-color);
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@ -124,25 +127,37 @@
.contacts-wrapper #client-details { .contacts-wrapper #client-details {
margin-top: 1rem; margin-top: 1rem;
padding: 1rem; padding-top: 1rem;
border-top: 1px solid var(--primary-color);
}
.contacts-wrapper #vendor-details {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--primary-color); border-top: 1px solid var(--primary-color);
} }
.contacts-wrapper #shipping-details { .contacts-wrapper #shipping-details {
margin-top: 1rem; margin-top: 1rem;
padding: 1rem; padding: 1rem;
padding-right: 0rem;
border-top: 1px solid var(--primary-color); border-top: 1px solid var(--primary-color);
} }
.contact-wrapper-left-side, .contact-wrapper-left-side {
border-bottom: 1px solid var(--primary-color);
}
.contact-wrapper-right-side { .contact-wrapper-right-side {
border-bottom: 1px solid var(--primary-color); border-bottom: 1px solid var(--primary-color);
margin-left: 20px;
} }
.shipping-wrapper-right-side { .shipping-wrapper-right-side {
border-bottom: 1px solid var(--primary-color); border-bottom: 1px solid var(--primary-color);
display: $show_shipping_address_block; display: $show_shipping_address_block;
margin-left: 20px;
} }
[data-ref="table"] { [data-ref="table"] {
@ -161,11 +176,11 @@
color: grey; color: grey;
} }
[data-ref="table"] > thead { [data-ref="table"]>thead {
text-align: left; text-align: left;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
font-size: 1.2rem; font-size: 1.2rem;
padding: 1rem; padding: 1rem;
background: var(--primary-color); background: var(--primary-color);
@ -185,17 +200,17 @@
text-align: right; text-align: right;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
background-color: #F7F7F7; background-color: #F7F7F7;
border-bottom: 1px solid var(--primary-color); border-bottom: 1px solid var(--primary-color);
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr > td.left-radius { [data-ref="table"]>tbody>tr>td.left-radius {
color: var(--primary-color); color: var(--primary-color);
} }
[data-ref="table"] > tbody > tr > td.right-radius { [data-ref="table"]>tbody>tr>td.right-radius {
text-align: right; text-align: right;
} }
@ -220,7 +235,7 @@
margin-top: .75rem; margin-top: .75rem;
} }
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) { #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
@ -228,25 +243,6 @@
#table-totals>.totals-table-right-side>*> :nth-child(2) { #table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right; text-align: right;
}
#table-totals
> *
[data-element='product-table-balance-due-label'],
#table-totals
> *
[data-element='product-table-balance-due'] {
font-weight: bold;
}
#table-totals
> *
[data-element='product-table-balance-due'] {
color: var(--primary-color);
}
#table-totals > * > :last-child {
text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
@ -259,7 +255,7 @@
margin-left: -10px; margin-left: -10px;
} }
#header > * { #header>* {
padding: 10px; padding: 10px;
} }
@ -288,9 +284,9 @@
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
margin-left: -10px; margin-left: -10px;
} }
#footer-colors > * { #footer-colors>* {
padding: 10px; padding: 10px;
} }
@ -308,28 +304,31 @@
.repeating-header, .repeating-header,
.repeating-header-space { .repeating-header-space {
height: 20px; height: 20px;
page-break-inside:avoid; page-break-inside: avoid;
page-break-before:avoid; page-break-before: avoid;
page-break-after:avoid; page-break-after: avoid;
} }
.repeating-footer, .repeating-footer,
.repeating-footer-space { .repeating-footer-space {
height: 20px; height: 20px;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
} }
[data-element='product_table-product.description-td'], td { [data-element='product_table-product.description-td'],
min-width:100%; td {
min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[data-ref="shipping_address-label"] { [data-ref="shipping_address-label"] {
@ -337,7 +336,7 @@
} }
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -348,25 +347,25 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -377,16 +376,14 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
/** Hide company logo **/ /** Hide company logo **/
@ -417,67 +414,67 @@
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/ /** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
</style> </style>
<div id="body"> <div id="body">
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div class="header-wrapper"> <div class="header-wrapper">
<div> <div>
<img class="company-logo" src="$company.logo" alt="$company.name logo"> <img class="company-logo" src="$company.logo" alt="$company.name logo">
</div> </div>
<div class="entity-details-wrapper"> <div class="entity-details-wrapper">
<table id="entity-details" cellspacing="0" dir="$dir"></table> <table id="entity-details" cellspacing="0" dir="$dir"></table>
</div> </div>
</div>
<div class="contacts-wrapper">
<div class="contact-wrapper-left-side">
<p class="contact-label">$from_label:</p>
<div class="company-info">
<div id="company-details"></div>
<div id="company-address"></div>
</div> </div>
</div> <div class="contacts-wrapper">
<div class="contact-wrapper-right-side"> <div class="contact-wrapper-left-side">
<p class="contact-label">$to_label:</p> <p class="contact-label">$from_label:</p>
<div id="client-details"></div> <div class="company-info">
<div id="vendor-details"></div> <div id="company-details"></div>
</div> <div id="company-address"></div>
</div>
</div>
<div class="contact-wrapper-right-side">
<p class="contact-label">$to_label:</p>
<div id="client-details"></div>
<div id="vendor-details"></div>
</div>
<div class="shipping-wrapper-right-side"> <div class="shipping-wrapper-right-side">
<p class="shipping-label">$shipping_label:</p> <p class="shipping-label">$shipping_label:</p>
<div id="shipping-details"></div> <div id="shipping-details"></div>
</div> </div>
</div> </div>
<table id="product-table" cellspacing="0" data-ref="table"></table> <table id="product-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table> <table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div> <div id="table-totals" cellspacing="0">$status_logo</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td> <td>
<div class="repeating-footer-space">&nbsp;</div> <div class="repeating-footer-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
<div class="repeating-header"> <div class="repeating-header">
<div id="header"> <div id="header">
@ -494,7 +491,7 @@
$entity_images $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<div data-ref="total_table-footer">$entity_footer</div> <div data-ref="total_table-footer">$entity_footer</div>
<div id="footer-colors"> <div id="footer-colors">
@ -508,4 +505,4 @@ $entity_images
<div style="background-color: #009B8F"><!-- 8 --></div> <div style="background-color: #009B8F"><!-- 8 --></div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
html { html {
width: 210mm; width: 210mm;
height: 200mm; height: 200mm;
} }
body { body {
@ -20,16 +20,19 @@
margin: 0; margin: 0;
} }
table tr td, table tr, th { table tr td,
table tr,
th {
font-size: $font_size !important; font-size: $font_size !important;
} }
@page { @page {
margin: 0; margin: 0;
size: $page_size $page_layout; size: $page_size $page_layout;
} }
@media print { @media print {
.header-wrapper, .header-wrapper,
.body-wrapper { .body-wrapper {
margin: $global_margin; margin: $global_margin;
@ -85,7 +88,8 @@
.hero-section { .hero-section {
margin-top: 5px; margin-top: 5px;
min-width: 100% !important; min-width: 100% !important;
background: url('$tech_hero_image'); /** If you want to replace the image, this is the place to do it. */ background: url('$tech_hero_image');
/** If you want to replace the image, this is the place to do it. */
-webkit-background-size: cover; -webkit-background-size: cover;
-moz-background-size: cover; -moz-background-size: cover;
-o-background-size: cover; -o-background-size: cover;
@ -163,7 +167,7 @@
margin-top: 3rem; margin-top: 3rem;
} }
[data-ref="table"] > thead > tr > th { [data-ref="table"]>thead>tr>th {
text-transform: uppercase; text-transform: uppercase;
font-weight: normal; font-weight: normal;
padding: 1rem; padding: 1rem;
@ -172,20 +176,20 @@
font-size: 1.1rem; font-size: 1.1rem;
} }
[data-ref="table"] > thead > tr > th:last-child { [data-ref="table"]>thead>tr>th:last-child {
text-align: right; text-align: right;
} }
[data-ref="table"] > tbody > tr > td { [data-ref="table"]>tbody>tr>td {
border-bottom: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
padding: 1rem; padding: 1rem;
} }
[data-ref="table"] > tbody > tr > td:first-child { [data-ref="table"]>tbody>tr>td:first-child {
color: var(--primary-color); color: var(--primary-color);
} }
[data-ref="table"] > tbody > tr > td:last-child { [data-ref="table"]>tbody>tr>td:last-child {
text-align: right; text-align: right;
} }
@ -207,7 +211,7 @@
margin-right: .5rem; margin-right: .5rem;
} }
#table-totals .totals-table-right-side > * { #table-totals .totals-table-right-side>* {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
@ -217,21 +221,17 @@
padding: 7px; padding: 7px;
} }
#table-totals > .totals-table-right-side > * > :nth-child(2) { #table-totals>.totals-table-right-side>*> :nth-child(2) {
text-align: right; text-align: right;
padding: 7px; padding: 7px;
} }
#table-totals #table-totals>* [data-element='total-table-balance-due-label'],
> * #table-totals>* [data-element='total-table-balance-due'] {
[data-element='total-table-balance-due-label'],
#table-totals
> *
[data-element='total-table-balance-due'] {
font-weight: bold; font-weight: bold;
} }
#table-totals > * > :last-child { #table-totals>*> :last-child {
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
@ -267,10 +267,12 @@
.repeating-footer-space { .repeating-footer-space {
height: 10px; height: 10px;
} }
.repeating-header { .repeating-header {
position: fixed; position: fixed;
top: 0; top: 0;
} }
.repeating-footer { .repeating-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
@ -278,63 +280,18 @@
[data-ref='product_table-product.description-td'], [data-ref='product_table-product.description-td'],
[data-ref='task_table-task.description-td']{ [data-ref='task_table-task.description-td'] {
min-width: 100px !important; min-width: 150px !important;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[data-ref='product_table-product.item-th'], [data-ref='product_table-product.item-th'],
[data-ref='task_table-task.service-th']{ [data-ref='task_table-task.service-th'] {
width: 10%; width: 10%;
} }
[data-ref='product_table-product.unit_cost-th'],
[data-ref='task_table-task.cost-th'] {
width: 13%;
}
[data-ref='product_table-product.quantity-th'],
[data-ref='task_table-task.hours-th'],
[data-ref='product_table-product.product1-th'],
[data-ref='product_table-product.product2-th'],
[data-ref='product_table-product.product3-th'],
[data-ref='product_table-product.product4-th'] {
width: 8%;
}
[data-ref='product_table-product.tax1-th'] {
width: 6%;
}
[data-ref='product_table-product.line_total-th'],
[data-ref='product_table-product.line_total-td'] {
width: 13%;
text-align: right;
}
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.quantity-th'],
[data-ref='task_table-task.task1-th'],
[data-ref='task_table-task.task2-th'],
[data-ref='task_table-task.task3-th'],
[data-ref='task_table-task.task4-th'] {
width: 10%;
}
[data-ref='task_table-task.tax1-th'] {
width: 10%;
}
[data-ref='task_table-task.line_total-th'],
[data-ref='task_table-task.line_total-td'] {
width: 13%;
text-align: right !important;
}
.stamp { .stamp {
transform: rotate(12deg); transform: rotate(12deg);
color: #555; color: #555;
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
@ -345,25 +302,25 @@
border-radius: 1rem; border-radius: 1rem;
font-family: 'Courier'; font-family: 'Courier';
mix-blend-mode: multiply; mix-blend-mode: multiply;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
text-align: center; text-align: center;
} }
.is-paid { .is-paid {
color: #D23; color: #D23;
border: 1rem double #D23; border: 1rem double #D23;
transform: rotate(-5deg); transform: rotate(-5deg);
font-size: 6rem; font-size: 6rem;
font-family: "Open sans", Helvetica, Arial, sans-serif; font-family: "Open sans", Helvetica, Arial, sans-serif;
border-radius: 0; border-radius: 0;
padding: 0.5rem; padding: 0.5rem;
opacity: 0.2; opacity: 0.2;
z-index:200 !important; z-index: 200 !important;
position: fixed; position: fixed;
display: $show_paid_stamp; display: $show_paid_stamp;
} }
.project-header { .project-header {
font-size: 1.2em; font-size: 1.2em;
@ -374,16 +331,14 @@
margin-right: 0; margin-right: 0;
font-weight: bold; font-weight: bold;
color: #505050; color: #505050;
}
.pqrcode {
} }
#qr-bill{ .pqrcode {}
width:100% !important;
#qr-bill {
width: 100% !important;
} }
/** Useful snippets, uncomment to enable. **/ /** Useful snippets, uncomment to enable. **/
/** Hide company logo **/ /** Hide company logo **/
@ -415,88 +370,88 @@
</style> </style>
<table style="min-width: 100%"> <table style="min-width: 100%">
<thead> <thead>
<tr> <tr>
<td> <td>
<div class="repeating-header-space">&nbsp;</div> <div class="repeating-header-space">&nbsp;</div>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<div id="body"> <div id="body">
<div class="header-wrapper"> <div class="header-wrapper">
<div class="logo-and-partial-entity-info"> <div class="logo-and-partial-entity-info">
<div class="company-logo-wrapper"> <div class="company-logo-wrapper">
<img class="company-logo" src="$company.logo" alt="$company.name logo"> <img class="company-logo" src="$company.logo" alt="$company.name logo">
</div>
</div>
<div class="spacer"></div>
<div class="top-right-side-section">
<table style="white-space: nowrap;" cellspacing="10">
<tr>
<td>$entity_number_label:</td>
<td class="header-invoice-number">$entity_number</td>
</tr>
<tr>
<td>$date_label </td>
<td>$date</td>
</tr>
<tr class="header-payment-due-label">
<td>$payment_due_label </td>
<td>$due_date</td>
</tr>
<tr>
<td class="header-amount-due-label">$amount_due_label</td>
<td class="header-amount-due-value">$balance_due</td>
</tr>
</table>
</div> </div>
</div> </div>
<div class="spacer"></div> <div class="hero-section">
<div class="top-right-side-section"> <div class="hero-contact-section">
<table style="white-space: nowrap;" cellspacing="10"> <div class="client-details">
<tr> <span class="client-details-to-label">$to_label:</span>
<td>$entity_number_label:</td> <div id="client-details"></div>
<td class="header-invoice-number">$entity_number</td> <div id="vendor-details"></div>
</tr> <span class="shipping-to-label">$shipping_label:</span>
<tr> <div id="shipping-details"></div>
<td>$date_label:</td> </div>
<td>$date</td> <div class="company-details">
</tr> <span class="client-details-to-label">$from_label:</span>
<tr class="header-payment-due-label"> <div class="company-details-wrapper">
<td>$payment_due_label:</td> <div id="company-details"></div>
<td>$due_date</td> <div id="company-address"></div>
</tr> </div>
<tr> </div>
<td class="header-amount-due-label">$amount_due_label:</td>
<td class="header-amount-due-value">$balance_due</td>
</tr>
</table>
</div>
</div>
<div class="hero-section">
<div class="hero-contact-section">
<div class="client-details">
<span class="client-details-to-label">$to_label:</span>
<div id="client-details"></div>
<div id="vendor-details"></div>
<span class="shipping-to-label">$shipping_label:</span>
<div id="shipping-details"></div>
</div>
<div class="company-details">
<span class="client-details-to-label">$from_label:</span>
<div class="company-details-wrapper">
<div id="company-details"></div>
<div id="company-address"></div>
</div> </div>
</div> </div>
</div> <div class="body-wrapper">
</div> <table id="product-table" cellspacing="0" data-ref="table"></table>
<div class="body-wrapper"> <table id="task-table" cellspacing="0" data-ref="table"></table>
<table id="product-table" cellspacing="0" data-ref="table"></table> <table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
<table id="task-table" cellspacing="0" data-ref="table"></table> <table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table> <div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table> <table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div> <div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table> <table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div> <div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table> <table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div> <div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table> <div id="table-totals" cellspacing="0">$status_logo</div>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div> </div>
<div id="table-totals" cellspacing="0">$status_logo</div> </div>
</div> </td>
</div> </tr>
</td> </tbody>
</tr> <tfoot>
</tbody> <tr>
<tfoot> <td>
<tr> <div class="repeating-footer-space">&nbsp;</div>
<td> </td>
<div class="repeating-footer-space">&nbsp;</div> </tr>
</td> </tfoot>
</tr>
</tfoot>
</table> </table>
<div class="repeating-header" id="header"></div> <div class="repeating-header" id="header"></div>
@ -504,8 +459,6 @@
$entity_images $entity_images
<div class="repeating-footerx" id="footer"> <div class="repeating-footerx" id="footer">
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div>
</div>

View File

@ -97,7 +97,8 @@
<div class="two-col-grid" style=""> <div class="two-col-grid" style="">
<ninja> <ninja>
<div class="pull-left"> <div class="pull-left">
<h1 class="primary-color-highlight" style="margin-top:0;">$receipt_label {%if payments|length == 1%}#$number{% endif %}</h2> <h1 class="primary-color-highlight" style="margin-top:0;">$receipt_label {%if payments|length ==
1%}#$number{% endif %}</h2>
</div> </div>
</ninja> </ninja>
<div class="pull-right"><img src="$company.logo" class="company-logo"></div> <div class="pull-right"><img src="$company.logo" class="company-logo"></div>

View File

@ -15,7 +15,6 @@ use App\Http\Controllers\QuoteController;
use App\Http\Controllers\RecurringInvoiceController; use App\Http\Controllers\RecurringInvoiceController;
use App\Models\Account; use App\Models\Account;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('client', [ContactLoginController::class, 'showLoginForm'])->name('client.catchall')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); //catch all Route::get('client', [ContactLoginController::class, 'showLoginForm'])->name('client.catchall')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); //catch all
@ -146,24 +145,6 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe'); Route::get('unsubscribe/{entity}/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'unsubscribe'])->name('unsubscribe');
}); });
// Route::get('route/{hash}', function ($hash) {
// $route = '/';
// try {
// $route = decrypt($hash);
// }
// catch (\Exception $e) {
// abort(404);
// }
// return redirect($route);
// })->middleware('throttle:404');
Route::get('phantom/{entity}/{invitation_key}', [Phantom::class, 'displayInvitation'])->middleware(['invite_db', 'phantom_secret'])->name('phantom_view');
Route::get('blade/', [Phantom::class, 'blade'])->name('blade');
Route::get('.env', function () { Route::get('.env', function () {
})->middleware('throttle:honeypot'); })->middleware('throttle:honeypot');

View File

@ -1,319 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature\PdfMaker;
use App\Services\PdfMaker\Design;
use App\Services\PdfMaker\PdfMaker;
use Tests\TestCase;
class PdfMakerTest extends TestCase
{
public $state = [
'template' => [],
'variables' => [
'labels' => [],
'values' => [],
],
];
public function testDesignLoadsCorrectly()
{
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($this->state);
$maker->design($design);
$this->assertInstanceOf(Design::class, $maker->design);
}
public function testHtmlDesignLoadsCorrectly()
{
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($this->state);
$maker
->design($design)
->build();
$this->assertStringContainsString('Template: Example', $maker->getCompiledHTML());
}
public function testGetSectionUtility()
{
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($this->state);
$maker
->design($design)
->build();
$this->assertEquals('table', $maker->getSectionNode('product-table')->nodeName);
}
public function testTableAttributesAreInjected()
{
$state = [
'template' => [
'product-table' => [
'id' => 'product-table',
'properties' => [
'class' => 'my-awesome-class',
'style' => 'margin-top: 10px;',
'script' => '',
],
],
],
'variables' => [
'labels' => [],
'values' => [],
],
];
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
$this->assertStringContainsString('my-awesome-class', $maker->getSection('product-table', 'class'));
// $this->assertStringContainsString('margin-top: 10px', $maker->getSection('product-table', 'style'));
// $this->assertStringContainsString('console.log(1)', $maker->getSection('product-table', 'script'));
}
public function testVariablesAreReplaced()
{
$state = [
'template' => [
'product-table' => [
'id' => 'product-table',
],
],
'variables' => [
'labels' => [],
'values' => [
'$company.name' => 'Invoice Ninja',
],
],
];
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
$this->assertStringContainsString('Invoice Ninja', $maker->getCompiledHTML());
$this->assertStringContainsString('Invoice Ninja', $maker->getSection('header'));
}
public function testElementContentIsGenerated()
{
$state = [
'template' => [
'product-table' => [
'id' => 'product-table',
'properties' => [],
'elements' => [
['element' => 'thead', 'content' => '', 'elements' => [
['element' => 'th', 'content' => 'Company'],
['element' => 'th', 'content' => 'Contact'],
['element' => 'th', 'content' => 'Country', 'properties' => [
'colspan' => 3,
]],
]],
['element' => 'tr', 'content' => '', 'elements' => [
['element' => 'td', 'content' => '$company'],
['element' => 'td', 'content' => '$email'],
['element' => 'td', 'content' => '$country', 'elements' => [
['element' => 'a', 'content' => 'Click here for a link', 'properties' => [
'href' => 'https://github.com/invoiceninja/invoiceninja',
]],
]],
]],
],
],
],
'variables' => [
'labels' => [],
'values' => [
'$company' => 'Invoice Ninja',
'$email' => 'contact@invoiceninja.com',
'$country' => 'UK',
],
],
];
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
$compiled = 'contact@invoiceninja.com';
$this->assertStringContainsString($compiled, $maker->getCompiledHTML());
}
public function testConditionalRenderingOfElements()
{
$design1 = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker1 = new PdfMaker([
'template' => [
'header' => [
'id' => 'header',
'properties' => [],
],
],
]);
$maker1
->design($design1)
->build();
$output1 = $maker1->getCompiledHTML();
$this->assertStringContainsString('<div id="header">', $output1);
$design2 = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker2 = new PdfMaker([
'template' => [
'header' => [
'id' => 'header',
'properties' => ['hidden' => 'true'],
],
],
]);
$maker2
->design($design2)
->build();
$output2 = $maker2->getCompiledHTML();
$this->assertStringContainsString('<div id="header" hidden="true">$company.name</div>', $output2);
$this->assertNotSame($output1, $output2);
}
public function testGeneratingPdf()
{
$state = [
'template' => [
'header' => [
'id' => 'header',
'properties' => ['class' => 'text-white bg-blue-600 p-2'],
],
'product-table' => [
'id' => 'product-table',
'properties' => ['class' => 'table-auto'],
'elements' => [
['element' => 'thead', 'content' => '', 'elements' => [
['element' => 'tr', 'content' => '', 'elements' => [
['element' => 'th', 'content' => 'Title', 'properties' => ['class' => 'px-4 py-2']],
['element' => 'th', 'content' => 'Author', 'properties' => ['class' => 'px-4 py-2']],
['element' => 'th', 'content' => 'Views', 'properties' => ['class' => 'px-4 py-2']],
]],
]],
['element' => 'tbody', 'content' => '', 'elements' => [
['element' => 'tr', 'content' => '', 'elements' => [
['element' => 'td', 'content' => 'An amazing guy', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => 'David Bomba', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => '1M', 'properties' => ['class' => 'border px-4 py-2']],
]],
['element' => 'tr', 'content' => '', 'elements' => [
['element' => 'td', 'content' => 'Flutter master', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => 'Hillel Coren', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => '1M', 'properties' => ['class' => 'border px-4 py-2']],
]],
['element' => 'tr', 'content' => '', 'elements' => [
['element' => 'td', 'content' => 'Bosssssssss', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => 'Shalom Stark', 'properties' => ['class' => 'border px-4 py-2']],
['element' => 'td', 'content' => '1M', 'properties' => ['class' => 'border px-4 py-2']],
]],
['element' => 'tr', 'content' => '', 'order' => 4, 'elements' => [
['element' => 'td', 'content' => 'Three amazing guys', 'properties' => ['class' => 'border px-4 py-2', 'colspan' => '100%']],
]],
]],
],
],
],
'variables' => [
'labels' => [],
'values' => [
'$title' => 'Invoice Ninja',
],
],
];
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
$this->assertTrue(true);
}
public function testGetSectionHTMLWorks()
{
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$html = $design
->document()
->getSectionHTML('product-table');
$this->assertStringContainsString('id="product-table"', $html);
}
public function testWrapperHTMLWorks()
{
$design = new Design('example', ['custom_path' => base_path('tests/Feature/PdfMaker/')]);
$state = [
'template' => [
'product-table' => [
'id' => 'product-table',
'elements' => [
['element' => 'p', 'content' => 'Example paragraph'],
],
],
],
'variables' => [
'labels' => [],
'values' => [],
],
'options' => [
'all_pages_header' => true,
'all_pages_footer' => true,
],
];
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
// exec('echo "" > storage/logs/laravel.log');
// nlog($maker->getCompiledHTML(true));
$this->assertTrue(true);
}
}

View File

@ -17,9 +17,6 @@ use App\Models\Design;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Project; use App\Models\Project;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker;
use App\Services\Template\TemplateMock; use App\Services\Template\TemplateMock;
use App\Services\Template\TemplateService; use App\Services\Template\TemplateService;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
@ -819,80 +816,4 @@ class TemplateTest extends TestCase
// nlog("Plain PDF Gen Time: " . $end-$start); // nlog("Plain PDF Gen Time: " . $end-$start);
} }
public function testTemplateGeneration()
{
$entity_obj = $this->invoice;
$design = new Design();
$design->design = json_decode(json_encode($this->invoice->company->settings->pdf_variables), true);
$design->name = 'test';
$design->is_active = true;
$design->is_template = true;
$design->is_custom = true;
$design->user_id = $this->invoice->user_id;
$design->company_id = $this->invoice->company_id;
$design_object = new \stdClass();
$design_object->includes = '';
$design_object->header = '';
$design_object->body = $this->body;
$design_object->product = '';
$design_object->task = '';
$design_object->footer = '';
$design->design = $design_object;
$design->save();
$start = microtime(true);
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($entity_obj->client->locale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first());
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => $entity_obj->client,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
'client' => $entity_obj->client,
'entity' => [$entity_obj],
'invoices' => [$entity_obj],
'variables' => $variables,
],
'process_markdown' => $entity_obj->client->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($template)
->build();
$html = $maker->getCompiledHTML(true);
$end = microtime(true);
$this->assertNotNull($html);
$this->assertStringContainsStringIgnoringCase($this->company->settings->name, $html);
// nlog("Twig Solo Gen Time: ". $end - $start);
}
} }

View File

@ -17,9 +17,6 @@ use App\Models\Invoice;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Models\Quote; use App\Models\Quote;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Tests\MockAccountData; use Tests\MockAccountData;
@ -49,52 +46,4 @@ class HtmlGenerationTest extends TestCase
$this->assertNotNull($html); $this->assertNotNull($html);
} }
private function generateHtml($entity)
{
$entity_design_id = '';
if ($entity instanceof Invoice || $entity instanceof RecurringInvoice) {
$entity_design_id = 'invoice_design_id';
} elseif ($entity instanceof Quote) {
$entity_design_id = 'quote_design_id';
} elseif ($entity instanceof Credit) {
$entity_design_id = 'credit_design_id';
} elseif ($entity instanceof PurchaseOrder) {
$entity_design_id = 'purchase_order_design_id';
}
$entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id));
$design = Design::find($entity_design_id);
$html = new HtmlEngine($entity->invitations->first());
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'client' => $entity->client,
'entity' => $entity,
'pdf_variables' => (array) $entity->company->settings->pdf_variables,
'$product' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'options' => [
'all_pages_header' => $entity->client->getSetting('all_pages_header'),
'all_pages_footer' => $entity->client->getSetting('all_pages_footer'),
],
];
$maker = new PdfMakerService($state);
return $maker->design($template)
->build()
->getCompiledHTML(true);
}
} }

View File

@ -12,10 +12,18 @@
namespace Tests\Pdf; namespace Tests\Pdf;
use App\Services\Pdf\PdfConfiguration;
use App\Services\Pdf\PdfService;
use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
use App\Models\Client;
use App\Models\Vendor;
use App\Models\Company;
use App\Models\Invoice;
use Tests\MockAccountData;
use App\Models\ClientContact;
use App\Models\VendorContact;
use App\Services\Pdf\PdfService;
use App\DataMapper\CompanySettings;
use App\Models\PurchaseOrder;
use App\Services\Pdf\PdfConfiguration;
/** /**
* *
@ -25,11 +33,433 @@ class PdfServiceTest extends TestCase
{ {
use MockAccountData; use MockAccountData;
private string $max_pdf_variables = '{"client_details":["$client.name","$contact.full_name","$client.address1","$client.city_state_postal","$client.number","$client.vat_number","$client.postal_city_state","$client.website","$client.country","$client.custom3","$client.id_number","$client.phone","$client.address2","$client.custom1","$contact.custom1"],"vendor_details":["$vendor.name","$vendor.number","$vendor.vat_number","$vendor.address1","$vendor.address2","$vendor.city_state_postal","$vendor.country","$vendor.phone","$contact.email","$vendor.id_number","$vendor.website","$vendor.custom2","$vendor.custom1","$vendor.custom4","$vendor.custom3","$contact.phone","$contact.full_name","$contact.custom2","$contact.custom1"],"purchase_order_details":["$purchase_order.number","$purchase_order.date","$purchase_order.total","$purchase_order.balance_due","$purchase_order.due_date","$purchase_order.po_number","$purchase_order.custom1","$purchase_order.custom2","$purchase_order.custom3"],"company_details":["$company.name","$company.email","$company.phone","$company.id_number","$company.vat_number","$company.website","$company.address2","$company.address1","$company.city_state_postal","$company.postal_city_state","$company.custom1","$company.custom3"],"company_address":["$company.address1","$company.city_state_postal","$company.country","$company.id_number","$company.vat_number","$company.website","$company.email","$company.name","$company.custom1"],"invoice_details":["$invoice.number","$invoice.date","$invoice.balance","$invoice.custom1","$invoice.due_date","$invoice.project","$invoice.balance_due","$invoice.custom3","$invoice.po_number","$invoice.custom2","$invoice.amount","$invoice.custom4"],"quote_details":["$quote.number","$quote.custom1","$quote.po_number","$quote.date","$quote.valid_until","$quote.total","$quote.custom2","$quote.custom3","$quote.custom4"],"credit_details":["$credit.number","$credit.balance","$credit.po_number","$credit.date","$credit.valid_until","$credit.total","$credit.custom1","$credit.custom2","$credit.custom3"],"product_columns":["$product.item","$product.product1","$product.description","$product.product2","$product.tax","$product.line_total","$product.quantity","$product.unit_cost","$product.discount","$product.product3","$product.product4","$product.gross_line_total"],"product_quote_columns":["$product.item","$product.description","$product.unit_cost","$product.quantity","$product.discount","$product.tax","$product.line_total"],"task_columns":["$task.service","$task.description","$task.rate","$task.hours","$task.discount","$task.line_total","$task.tax","$task.tax_amount","$task.task2","$task.task1","$task.task3"],"total_columns":["$total","$line_taxes","$total_taxes","$discount","$custom_surcharge1","$outstanding","$net_subtotal","$custom_surcharge2","$custom_surcharge3","$subtotal","$paid_to_date"],"statement_invoice_columns":["$invoice.number","$invoice.date","$due_date","$total","$balance"],"statement_payment_columns":["$invoice.number","$payment.date","$method","$statement_amount"],"statement_credit_columns":["$credit.number","$credit.date","$total","$credit.balance"],"statement_details":["$statement_date","$balance"],"delivery_note_columns":["$product.item","$product.description","$product.quantity"],"statement_unapplied_columns":["$payment.number","$payment.date","$payment.amount","$payment.payment_balance"]}';
private string $min_pdf_variables = '{"client_details":["$client.name","$client.vat_number","$client.address1","$client.city_state_postal","$client.country"],"vendor_details":["$vendor.name","$vendor.vat_number","$vendor.address1","$vendor.city_state_postal","$vendor.country"],"purchase_order_details":["$purchase_order.number","$purchase_order.date","$purchase_order.total"],"company_details":["$company.name","$company.address1","$company.city_state_postal"],"company_address":["$company.name","$company.website"],"invoice_details":["$invoice.number","$invoice.date","$invoice.due_date","$invoice.balance"],"quote_details":["$quote.number","$quote.date","$quote.valid_until"],"credit_details":["$credit.date","$credit.number","$credit.balance"],"product_columns":["$product.item","$product.description","$product.line_total"],"product_quote_columns":["$product.item","$product.description","$product.unit_cost","$product.quantity","$product.discount","$product.tax","$product.line_total"],"task_columns":["$task.description","$task.rate","$task.line_total"],"total_columns":["$total","$total_taxes","$outstanding"],"statement_invoice_columns":["$invoice.number","$invoice.date","$due_date","$total","$balance"],"statement_payment_columns":["$invoice.number","$payment.date","$method","$statement_amount"],"statement_credit_columns":["$credit.number","$credit.date","$total","$credit.balance"],"statement_details":["$statement_date","$balance"],"delivery_note_columns":["$product.item","$product.description","$product.quantity"],"statement_unapplied_columns":["$payment.number","$payment.date","$payment.amount","$payment.payment_balance"]}';
private string $fake_email;
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->makeTestData(); $this->makeTestData();
$this->fake_email = $this->faker->email();
}
private function stubInvoice($settings, array $company_props = [])
{
$company = Company::factory()->create(array_merge([
'account_id' => $this->account->id,
'settings' => $settings
], $company_props));
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id
]);
$contact = ClientContact::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'client_id' => $client->id,
'is_primary' => true,
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'john@doe.com',
'phone' => '1234567890',
'send_email' => true,
]);
$invoice = Invoice::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'client_id' => $client->id,
'status_id' => Invoice::STATUS_DRAFT,
]);
$invoice = $invoice->calc()->getInvoice();
$invoice = $invoice->service()->createInvitations()->markSent()->save();
$invoice = $invoice->fresh();
return $invoice;
}
private function stubPurchaseOrder($settings, array $company_props = [])
{
$company = Company::factory()->create(array_merge([
'account_id' => $this->account->id,
'settings' => $settings
], $company_props));
$vendor = Vendor::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id
]);
$contact = VendorContact::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'vendor_id' => $vendor->id,
'is_primary' => true,
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'john@doe.com',
'phone' => '1234567890',
'send_email' => true,
]);
$po = PurchaseOrder::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'vendor_id' => $vendor->id,
'status_id' => PurchaseOrder::STATUS_DRAFT,
]);
$po = $po->calc()->getInvoice();
$po = $po->service()->createInvitations()->markSent()->save();
$po = $po->fresh();
return $po;
}
public function testPurchaseOrderGeneration()
{
$settings = CompanySettings::defaults();
$settings->pdf_variables = json_decode($this->max_pdf_variables);
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.com';
$settings->name = 'Invoice Ninja';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = $this->fake_email;
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->hide_empty_columns_on_pdf = true;
$po = $this->stubPurchaseOrder($settings, ['markdown_enabled' => true]);
$items = $po->line_items;
$first_item = $items[0];
$first_item->notes = $this->faker->paragraphs(2, true);
$items[] = $first_item;
$new_item = $items[0];
$new_item->notes = '**Bold** _Italic_ [Link](https://www.google.com)
+ this
+ and that
+ is something to think about';
$items[] = $new_item;
$po->line_items = $items;
$po->calc()->getPurchaseOrder();
$this->assertGreaterThan(0, $po->invitations()->count());
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($po) {
$po->design_id = $design->id;
$po->save();
$po = $po->fresh();
$service = (new PdfService($po->invitations()->first(), 'purchase_order'))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/po_' . $design->name.'.pdf', $pdf);
});
}
public function testMarkdownEnabled()
{
$settings = CompanySettings::defaults();
$settings->pdf_variables = json_decode($this->max_pdf_variables);
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.com';
$settings->name = 'Invoice Ninja';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = $this->fake_email;
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->hide_empty_columns_on_pdf = true;
$invoice = $this->stubInvoice($settings, ['markdown_enabled' => true]);
$items = $invoice->line_items;
$first_item = $items[0];
$first_item->notes = $this->faker->paragraphs(2, true);
$items[] = $first_item;
$new_item = $items[0];
$new_item->notes = '**Bold** _Italic_ [Link](https://www.google.com)
+ this
+ and that
+ is something to think about';
$items[] = $new_item;
$invoice->line_items = $items;
$invoice->calc()->getInvoice();
$this->assertGreaterThan(0, $invoice->invitations()->count());
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) {
$invoice->design_id = $design->id;
$invoice->save();
$invoice = $invoice->fresh();
$service = (new PdfService($invoice->invitations()->first()))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/markdown_' . $design->name.'.pdf', $pdf);
});
}
public function testLargeDescriptionField()
{
$settings = CompanySettings::defaults();
$settings->pdf_variables = json_decode($this->max_pdf_variables);
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.com';
$settings->name = 'Invoice Ninja';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = $this->fake_email;
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->hide_empty_columns_on_pdf = true;
$invoice = $this->stubInvoice($settings);
$items = $invoice->line_items;
$items[0]->notes = $this->faker->text(500);
$invoice->line_items = $items;
$invoice->save();
$this->assertGreaterThan(0, $invoice->invitations()->count());
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) {
$invoice->design_id = $design->id;
$invoice->save();
$invoice = $invoice->fresh();
$service = (new PdfService($invoice->invitations()->first()))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/desc_' . $design->name.'.pdf', $pdf);
});
}
public function testMaxInvoiceFields()
{
$settings = CompanySettings::defaults();
$settings->pdf_variables = json_decode($this->max_pdf_variables);
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.com';
$settings->name = 'Invoice Ninja';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = $this->fake_email;
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->hide_empty_columns_on_pdf = true;
$invoice = $this->stubInvoice($settings);
$this->assertGreaterThan(0, $invoice->invitations()->count());
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use($invoice) {
$invoice->design_id = $design->id;
$invoice->save();
$invoice = $invoice->fresh();
$service = (new PdfService($invoice->invitations()->first()))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/max_fields_' . $design->name.'.pdf', $pdf);
});
}
public function testMinInvoiceFields()
{
$settings = CompanySettings::defaults();
$settings->pdf_variables = json_decode($this->min_pdf_variables);
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
$settings->website = 'www.invoiceninja.com';
$settings->name = 'Invoice Ninja';
$settings->address1 = 'Address 1';
$settings->address2 = 'Address 2';
$settings->city = 'City';
$settings->state = 'State';
$settings->postal_code = 'Postal Code';
$settings->phone = '555-343-2323';
$settings->email = $this->fake_email;
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$settings->timezone_id = '1';
$settings->entity_send_time = 0;
$settings->hide_empty_columns_on_pdf = true;
$invoice = $this->stubInvoice($settings);
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) use ($invoice) {
$invoice->design_id = $design->id;
$invoice->save();
$invoice = $invoice->fresh();
$service = (new PdfService($invoice->invitations->first()))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/min_fields_' . $design->name.'.pdf', $pdf);
});
}
public function testStatementPdfGeneration()
{
$pdf = $this->client->service()->statement([
'client_id' => $this->client->hashed_id,
'start_date' => '2000-01-01',
'end_date' => '2023-01-01',
'show_aging_table' => true,
'show_payments_table' => true,
'status' => 'all'
]);
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/statement.pdf', $pdf);
}
public function testMultiDesignGeneration()
{
if (config('ninja.testvars.travis')) {
$this->markTestSkipped();
}
\App\Models\Design::where('is_custom',false)->cursor()->each(function ($design){
$this->invoice->design_id = $design->id;
$this->invoice->save();
$this->invoice = $this->invoice->fresh();
$invitation = $this->invoice->invitations->first();
$service = (new PdfService($invitation))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/' . $design->name.'.pdf', $pdf);
});
\App\Models\Design::where('is_custom', false)->cursor()->each(function ($design) {
$this->invoice->design_id = $design->id;
$this->invoice->save();
$this->invoice = $this->invoice->fresh();
$invitation = $this->invoice->invitations->first();
$service = (new PdfService($invitation, 'delivery_note'))->boot();
$pdf = $service->getPdf();
$this->assertNotNull($pdf);
\Illuminate\Support\Facades\Storage::put('/pdf/dn_' . $design->name.'.pdf', $pdf);
});
} }
public function testPdfGeneration() public function testPdfGeneration()