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();
} }
if($entity_obj->client){
$entity_obj->load('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],
} ]);
$design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); $ps->boot()
->designer
->buildFromPartials($request->design['design']);
$state = [ $ps->builder
'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(); ->build();
if (request()->query('html') == 'true') { if ($request->query('html') == 'true') {
return $ps->getHtml();
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 */
$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));
$entity_string = 'invoice';
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 */ /** @var \App\Models\InvoiceInvitation $invitation */
$invitation = InvoiceInvitation::where('company_id', $company->id)->orderBy('id', 'desc')->first(); $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([
'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(); ->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));
return response()->streamDownload(function () use ($pdf) {
echo $pdf;
}, 'preview.pdf', [
'Content-Disposition' => 'inline',
'Content-Type' => 'application/pdf',
'Cache-Control:' => 'no-cache',
]);
} }
/** @var \App\Models\User $user */
$user = auth()->user();
/** @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 */ $pdf = (new PdfMock($request, $company))->build();
$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);
}
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([
'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']);
$maker = new PdfMaker($state);
$maker
->design($design)
->build(); ->build();
if (request()->query('html') == 'true') { if ($request->query('html') == 'true') {
return $maker->getCompiledHTML(); return $ps->getHtml();
} }
//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')));
if (! is_object($design_object)) { $design_object = json_decode(json_encode(request()->input('design')), true);
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([
'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(); ->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,
'$product' => $this->getDesign()->design->product,
'variables' => $variables,
'invoices' => $this->getInvoices()->cursor(), 'invoices' => $this->getInvoices()->cursor(),
'payments' => $this->getPayments()->cursor(), 'payments' => $this->getPayments()->cursor(),
'credits' => $this->getCredits()->cursor(), 'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(), 'aging' => $this->getAging(),
'unapplied' => $this->getUnapplied()->cursor(), '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();
$maker $ps->config->pdf_variables = (array) $this->entity->company->settings->pdf_variables;
->design($template) $ps->html_variables = $variables;
->build(); $ps->config->design = $this->getDesign();
$pdf = null; $ps->designer->buildFromPartials((array)$ps->config->design->design);
$html = $maker->getCompiledHTML(true); $ps->builder->build();
$pdf = $ps->getPdf();
// nlog($html);
$pdf = $this->convertToPdf($html);
$this->setVariables($variables);
$maker = null;
$state = null;
return $pdf; return $pdf;
@ -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,30 +78,45 @@ 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

@ -18,14 +18,14 @@
} }
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;
} }
@ -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;
@ -93,7 +95,15 @@
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 {
@ -157,28 +167,38 @@
} }
[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 { [data-ref="table"]>tbody>tr>td {
padding: 1.5rem; padding-top: 1.5rem;
padding-bottom: 1.5rem;
padding-left: 0;
padding-right: 0;
}
th.left-radius {
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) {
@ -219,22 +239,6 @@
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;
font-size: 1.4rem;
}
#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: 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;
@ -309,14 +314,19 @@
margin-top: 1rem; margin-top: 1rem;
} }
[data-ref='product_table-product.description-th'] { [data-ref='product_table-product.description-th'],
[data-ref='product_table-product.description-td'] {
width: 30%; width: 30%;
overflow-wrap: break-word; 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 {
@ -361,9 +371,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
@ -432,7 +440,8 @@
</div> </div>
<div class="entity-details-wrapper-right-side"> <div class="entity-details-wrapper-right-side">
<h4 class="entity-label" style="margin-top:0px; margin-bottom:10px; color:transparent;">$entity_label</h4> <h4 class="entity-label" style="margin-top:0px; margin-bottom:10px; color:transparent;">
$entity_label</h4>
<div class="entity-details-wrapper"> <div class="entity-details-wrapper">
<table id="entity-details" dir="$dir" cellspacing="0" cellpadding="0"></table> <table id="entity-details" dir="$dir" cellspacing="0" cellpadding="0"></table>
</div> </div>
@ -440,14 +449,10 @@
</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>

View File

@ -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,11 +43,18 @@
.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-container {
justify-self: start;
align-self: start;
}
.company-logo { .company-logo {
max-width: $company_logo_size; max-width: $company_logo_size;
} }
@ -55,6 +64,8 @@
} }
#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,7 +101,6 @@
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) {
@ -107,7 +123,9 @@
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;
@ -169,14 +187,6 @@
background: #f7f7f7; background: #f7f7f7;
} }
[data-element='product-table-balance-due-label'],
[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 { #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;
} }
@ -216,31 +225,11 @@
padding-right: 17px; padding-right: 17px;
} }
#table-totals
> *
[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 { #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;
} }
@ -259,7 +248,9 @@
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;
@ -268,6 +259,7 @@
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,16 +278,19 @@
.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'],
td {
min-width: 100%; min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -343,9 +338,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
@ -395,16 +388,16 @@
<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-details"></div>
<div id="company-address"></div> <div id="company-address"></div>
</div> </div>
<div class="client-and-entity-wrapper"> <div class="client-and-entity-wrapper">
<div id="client-details"><p class="entity-issued-to">$entity_issued_to_label:</p></div> <div id="client-details">
<p class="entity-issued-to">$entity_issued_to_label</p>
</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">

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 {
@ -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;
} }
@ -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>

View File

@ -46,13 +46,15 @@
.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;
gap: 1rem;
width: 100%; 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"] {
@ -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

@ -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;
} }
@ -41,12 +43,12 @@
.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);
} }
@ -55,16 +57,16 @@
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 {
@ -94,37 +96,58 @@
} }
.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 {
display: flex;
}
.entity-label h4 {
margin-top: 0rem;
margin-bottom: 0rem;
}
.entity-details-wrapper {
flex-shrink: 0;
} }
.entity-label-wrapper .entity-label>* { .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"] {
@ -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 {
@ -187,7 +210,6 @@
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;
} }
@ -212,16 +234,7 @@
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 > * > * {
padding-left: 0.5rem;
}
#table-totals>*> :last-child { #table-totals>*> :last-child {
text-align: right; text-align: right;
@ -229,7 +242,6 @@
} }
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {
padding-left: 0.5rem;
padding-right: 0.8rem; padding-right: 0.8rem;
} }
@ -259,16 +271,19 @@
.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'],
td {
min-width: 100%; min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -317,9 +332,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
@ -377,18 +390,14 @@
<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"
src="$company.logo"
alt="$company.name logo"
/>
</div> </div>
</div> </div>
<div class="entity-label-wrapper"> <div class="entity-label-wrapper">
<h1 class="entity-label"> <div class="entity-label">
<span>$entity_label</span>&nbsp; <h4>$entity_label</h4>
<span>#$entity_number</span> <h4>#$entity_number</h4>
</h1> </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>

View File

@ -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;
} }
@ -47,9 +49,7 @@
border-bottom: 4px solid; border-bottom: 4px solid;
} }
.company-logo-wrapper { .company-logo-wrapper {}
}
.right-radius { .right-radius {
padding-right: 1rem; padding-right: 1rem;
@ -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,
@ -131,8 +134,7 @@
[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;
@ -185,8 +187,6 @@
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;
@ -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
> *
[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; 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,16 +246,19 @@
.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'],
td {
min-width: 100%; min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -281,6 +268,7 @@
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;
@ -324,9 +312,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
@ -434,5 +420,3 @@ $entity_images
<p data-ref="total_table-footer">$entity_footer</p> <p data-ref="total_table-footer">$entity_footer</p>
</div> </div>

View File

@ -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);
} }
@ -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 {
@ -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;
} }
@ -199,8 +223,7 @@
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;
} }
@ -225,21 +248,6 @@
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 { #table-totals>*> :last-child {
text-align: right; text-align: right;
padding-right: 0rem; padding-right: 0rem;
@ -261,15 +269,13 @@
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"],
@ -292,11 +298,19 @@
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;
@ -308,9 +322,6 @@
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;
@ -344,10 +355,12 @@
.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;
@ -395,9 +408,8 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
} }
@ -456,42 +468,36 @@
<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>
<div class="header-right-side-wrapper-right"> <div class="header-right-side-wrapper-right">
<img <img class="company-logo" src="$company.logo" alt="$company.name logo" />
class="company-logo"
src="$company.logo"
alt="$company.name logo"
/>
</div> </div>
</div> </div>
</div> </div>
<h1 class="entity-label">$entity_label</h1> <h1 class="entity-label">$entity_label</h1>
<div class="entity-details-wrapper"> <div class="entity-details-wrapper">
<div> <div>
<span class="entity-property-label" data-element="entity-details-wrapper-invoice-number-label"> <span class="entity-property-label"
$entity_number_label: data-element="entity-details-wrapper-invoice-number-label">
$entity_number_label
</span> </span>
<span class="entity-property-value">$entity_number</span> <span class="entity-property-value">$entity_number</span>
</div> </div>
<div> <div>
<span class="entity-property-label">$date_label:</span> <span class="entity-property-label">$date_label</span>
<span class="entity-property-value">$date</span> <span class="entity-property-value">$date</span>
</div> </div>
<div> <div>
<span class="entity-property-label">$payment_due_label:</span> <span class="entity-property-label">$payment_due_label</span>
<span class="entity-property-value">$payment_due</span> <span class="entity-property-value">$payment_due</span>
</div> </div>
<div> <div>
<span class="entity-property-label">$amount_due_label:</span> <span class="entity-property-label">$amount_due_label</span>
<span <span class="entity-property-value" data-element="entity-details-wrapper-amount-due"
class="entity-property-value" style="float:right;">$amount_due</span>
data-element="entity-details-wrapper-amount-due"
>$amount_due</span
>
</div> </div>
</div> </div>
<table id="product-table" cellspacing="0" data-ref="table"></table> <table id="product-table" cellspacing="0" data-ref="table"></table>
@ -529,4 +535,3 @@ $entity_images
</div> </div>

View File

@ -19,11 +19,14 @@
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;
} }
body, html { body,
html {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -56,7 +59,8 @@
width: 100%; width: 100%;
} }
#header, #header-spacer { #header,
#header-spacer {
height: 160px; height: 160px;
padding: 1rem; padding: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@ -72,9 +76,10 @@
} }
#entity-details { #entity-details {
margin-top: 1rem;
text-align: left; text-align: left;
color: #fff4e9 !important; color: #fff4e9 !important;
line-height: 1.6; line-height: 1.6 !important;
} }
#entity-details>tr, #entity-details>tr,
@ -85,7 +90,7 @@
.logo-client-wrapper { .logo-client-wrapper {
margin-left: 2rem; margin-left: 2rem;
display: grid; display: grid;
grid-template-columns: 1fr auto auto; grid-template-columns: 2fr auto auto;
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -98,15 +103,21 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 2rem;
float: right;
} }
#shipping-details { #shipping-details {
display: $show_shipping_address; display: $show_shipping_address;
flex-direction: column; flex-direction: column;
margin-left: 10px; padding-left: 10px;
margin-right: 10px; padding-right: 2rem;
float: right;
}
#shipping-details>* {
margin-bottom: 0.5rem;
} }
#client-details>* { #client-details>* {
@ -117,6 +128,11 @@
margin: 0rem 2rem; margin: 0rem 2rem;
} }
[data-ref="delivery_note-client.city_state_postal"] p {
display: inline-block;
white-space: nowrap;
}
[data-ref="table"] { [data-ref="table"] {
min-width: 100%; min-width: 100%;
table-layout: fixed; table-layout: fixed;
@ -167,7 +183,8 @@
} }
#footer, #footer-spacer { #footer,
#footer-spacer {
height: 220px; height: 220px;
padding: 0rem 0rem; padding: 0rem 0rem;
margin-top: 0rem; margin-top: 0rem;
@ -201,7 +218,6 @@
#company-address { #company-address {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem;
} }
#company-address>*, #company-address>*,
@ -216,7 +232,7 @@
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2fr 1fr;
padding-top: 0.5rem; padding-top: 0.5rem;
padding-left: 2.5rem; padding-left: 2rem;
padding-right: 3rem; padding-right: 3rem;
gap: 80px; gap: 80px;
} }
@ -231,11 +247,7 @@
margin-top: 0.75rem; margin-top: 0.75rem;
} }
#table-totals #table-totals>.totals-table-right-side>*> :not([hidden])~ :not([hidden]) {
> .totals-table-right-side
> *
> :not([hidden])
~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
@ -245,12 +257,6 @@
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;
font-size: 1.3rem;
}
[data-ref="total_table-footer"] { [data-ref="total_table-footer"] {
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@ -288,8 +294,9 @@
} }
[data-ref='product_table-product.description-th'] { [data-ref='product_table-product.description-th'],
min-width: 100px !important; [data-ref='product_table-product.description-td'] {
min-width: 150px !important;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
@ -308,7 +315,6 @@
[data-ref='product_table-product.product3-th'], [data-ref='product_table-product.product3-th'],
[data-ref='product_table-product.product4-th'], [data-ref='product_table-product.product4-th'],
[data-ref='task_table-task.hours-th'], [data-ref='task_table-task.hours-th'],
[data-ref='task_table-task.discount-th'],
[data-ref='task_table-task.cost-th'], [data-ref='task_table-task.cost-th'],
[data-ref='task_table-task.quantity-th'], [data-ref='task_table-task.quantity-th'],
[data-ref='task_table-task.task1-th'], [data-ref='task_table-task.task1-th'],
@ -320,7 +326,12 @@
[data-ref='product_table-product.tax1-th'], [data-ref='product_table-product.tax1-th'],
[data-ref='task_table-task.tax1-th'] { [data-ref='task_table-task.tax1-th'] {
width: 10%; width: 7%;
}
[data-ref='product_table-product.discount-th'],
[data-ref='task_table-task.discount-th'] {
width: 8%;
} }
[data-ref='product_table-product.line_total-th'], [data-ref='product_table-product.line_total-th'],
@ -384,13 +395,12 @@
color: #505050; color: #505050;
} }
.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 **/
@ -421,9 +431,13 @@
/** 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="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>
@ -438,9 +452,9 @@
<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="shipping-details"></div>
<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> </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>

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 {
@ -240,54 +239,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);
color: #555; color: #555;

View File

@ -15,7 +15,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;
} }
@ -30,7 +32,6 @@
margin-top: 1rem; margin-top: 1rem;
} }
p { p {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -47,6 +48,7 @@
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 {
@ -76,13 +78,14 @@
.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"] {
@ -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;
} }
@ -317,16 +313,19 @@
.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'],
td {
min-width: 100%; min-width: 100%;
max-width: 300px; max-width: 300px;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -379,9 +378,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;

View File

@ -20,7 +20,9 @@
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;
} }
@ -30,6 +32,7 @@
} }
@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;
@ -222,12 +226,8 @@
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;
} }
@ -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;
@ -279,7 +281,7 @@
[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;
} }
@ -288,51 +290,6 @@
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;
@ -376,9 +333,7 @@
color: #505050; color: #505050;
} }
.pqrcode { .pqrcode {}
}
#qr-bill { #qr-bill {
width: 100% !important; width: 100% !important;
@ -440,15 +395,15 @@
<td class="header-invoice-number">$entity_number</td> <td class="header-invoice-number">$entity_number</td>
</tr> </tr>
<tr> <tr>
<td>$date_label:</td> <td>$date_label </td>
<td>$date</td> <td>$date</td>
</tr> </tr>
<tr class="header-payment-due-label"> <tr class="header-payment-due-label">
<td>$payment_due_label:</td> <td>$payment_due_label </td>
<td>$due_date</td> <td>$due_date</td>
</tr> </tr>
<tr> <tr>
<td class="header-amount-due-label">$amount_due_label:</td> <td class="header-amount-due-label">$amount_due_label</td>
<td class="header-amount-due-value">$balance_due</td> <td class="header-amount-due-value">$balance_due</td>
</tr> </tr>
</table> </table>
@ -507,5 +462,3 @@ $entity_images
<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()