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()
{
foreach (Design::whereIsCustom(false)->get() as $design) {
$invoice_design = new \App\Services\PdfMaker\Design(strtolower($design->name));
$invoice_design->document();
foreach (Design::where('is_custom', false)->get() as $design) {
$invoice_design = new \App\Services\Pdf\DesignExtractor($design->name);
$design_object = new stdClass();
$design_object->includes = $invoice_design->getSectionHTML('style');

View File

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

View File

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

View File

@ -37,8 +37,6 @@ use Illuminate\Support\Facades\Response;
use App\DataMapper\Analytics\LivePreview;
use App\Repositories\PurchaseOrderRepository;
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;
class PreviewPurchaseOrderController extends BaseController
@ -87,18 +85,17 @@ class PreviewPurchaseOrderController extends BaseController
*/
public function show(ShowPreviewRequest $request)
{
if (request()->has('entity') &&
request()->has('entity_id') &&
! empty(request()->input('entity')) &&
! empty(request()->input('entity_id')) &&
request()->has('body')) {
$design_object = json_decode(json_encode(request()->input('design')));
if ($request->input('entity', false) &&
$request->input('entity_id', false) != '-1' &&
$request->has('body')) {
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);
}
$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) {
return $this->blankEntity();
@ -109,63 +106,36 @@ class PreviewPurchaseOrderController extends BaseController
App::setLocale($entity_obj->company->locale());
$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 = [
'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],
]
];
$ps->builder
->build();
$design = new Design(request()->design['name']);
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
if ($request->query('html') == 'true') {
return $ps->getHtml();
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
$pdf = $ps->getPdf();
/** @var \App\Models\User $user */
$user = auth()->user();
return response()->streamDownload(function () use ($pdf) {
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();
@ -238,177 +208,70 @@ class PreviewPurchaseOrderController extends BaseController
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);
}
$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 = [
'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],
],
];
$ps->builder
->build();
$maker = new PdfMaker($state);
$maker
->design($design)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
return $ps->getHtml();
}
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
$pdf = $ps->getPdf();
/** @var \App\Models\User $user */
$user = auth()->user();
return response()->streamDownload(function () use ($pdf) {
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()
{
/** @var \App\Models\User $user */
nlog("mockEntity");
$start = microtime(true);
$user = auth()->user();
DB::connection($user->company()->db)->beginTransaction();
/** @var \App\Models\Company $company */
$company = $user->company();
/** @var \App\Models\Vendor $vendor */
$vendor = Vendor::factory()->create([
'user_id' => $user->id,
'company_id' => $user->company()->id,
]);
$request = request()->input('design');
$request['entity_type'] = request()->input('entity', 'invoice');
/** @var \App\Models\VendorContact $contact */
$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();
$pdf = (new PdfMock($request, $company))->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
return $pdf->getHtml();
}
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom())->convertHtmlToPdf($maker->getCompiledHTML(true));
}
$pdf = $pdf->getPdf();
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 = Response::make($pdf, 200);
$response->header('Content-Type', 'application/pdf');
$response->header('Server-Timing', (string) (microtime(true) - $start));
return $response;
}
}

View File

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

View File

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

View File

@ -55,8 +55,8 @@ class RecurringInvoicesCron
->whereNull('deleted_at')
->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
$query->where('is_deleted', false)
->whereNull('deleted_at');
})
->whereHas('company', function ($query) {
$query->where('is_disabled', 0)
@ -97,8 +97,9 @@ class RecurringInvoicesCron
->whereNotNull('next_send_date')
->where('next_send_date', '<=', now()->toDateTimeString())
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
$query->where('is_deleted', false)
->whereNull('deleted_at');
})
->whereHas('company', function ($query) {
$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();
}
public function preferredLocale(): string
{
return $this->locale();
}
public function language(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Language::class);

View File

@ -11,23 +11,21 @@
namespace App\Repositories;
use App\Models\Activity;
use App\Models\User;
use App\Models\Quote;
use App\Models\Backup;
use App\Models\CompanyToken;
use App\Models\Credit;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
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\Models\Activity;
use App\Utils\HtmlEngine;
use App\Models\CompanyToken;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\VendorHtmlEngine;
use App\Models\RecurringInvoice;
use App\Services\Pdf\PdfService;
use App\Utils\Traits\MakesInvoiceHtml;
/**
* Class for activity repository.
@ -111,7 +109,7 @@ class ActivityRepository extends BaseRepository
$backup->json_backup = '';
$backup->save();
$backup->storeRemotely($this->generateVendorHtml($entity), $entity->vendor);
$backup->storeRemotely($this->generateHtml($entity), $entity->vendor);
return;
@ -132,79 +130,44 @@ class ActivityRepository extends BaseRepository
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)
{
$entity_design_id = '';
$entity_type = '';
$settings = $entity->client ? $entity->client->getMergedSettings() : $entity->vendor->getMergedSettings();
if ($entity instanceof Invoice) {
$entity_type = 'invoice';
$entity_design_id = 'invoice_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof RecurringInvoice) {
$entity_type = 'recurring_invoice';
$entity_design_id = 'invoice_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof Quote) {
$entity_type = 'quote';
$entity_design_id = 'quote_design_id';
$entity->load('client.company', 'invitations');
$document_type = 'product';
} elseif ($entity instanceof Credit) {
$entity_type = 'credit';
$entity_type = 'product';
$entity->load('client.company', 'invitations');
$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);
@ -212,45 +175,13 @@ class ActivityRepository extends BaseRepository
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;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\InvoiceItemFactory;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Design;
use App\Models\Invoice;
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\Number;
use Illuminate\Support\Carbon;
use App\Factory\InvoiceFactory;
use App\Utils\Traits\MakesHash;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Factory\InvoiceItemFactory;
use App\Factory\InvoiceInvitationFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
class Statement
{
use PdfMakerTrait;
use PdfMaker;
use MakesHash;
use MakesDates;
@ -89,52 +87,33 @@ class Statement
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);
} else {
$template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options);
}
$variables['values']['$show_paid_stamp'] = 'none';
$variables['values']['$show_paid_stamp'] = 'none'; //do not show paid stamp on statement
$state = [
'template' => $template->elements([
'client' => $this->client,
'entity' => $this->entity,
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
'$product' => $this->getDesign()->design->product,
'variables' => $variables,
'invoices' => $this->getInvoices()->cursor(),
'payments' => $this->getPayments()->cursor(),
'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(),
'unapplied' => $this->getUnapplied()->cursor(),
], \App\Services\PdfMaker\Design::STATEMENT),
'variables' => $variables,
'options' => [
],
'process_markdown' => $this->entity->client->company->markdown_enabled,
$options = [
// 'client' => $this->entity->client,
// 'entity' => $this->entity,
// 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
// '$product' => $this->getDesign()->design->product,
// 'variables' => $variables,
'invoices' => $this->getInvoices()->cursor(),
'payments' => $this->getPayments()->cursor(),
'credits' => $this->getCredits()->cursor(),
'aging' => $this->getAging(),
'unapplied' => $this->getUnapplied()->cursor()
];
$maker = new PdfMaker($state);
$ps = new \App\Services\Pdf\PdfService($invitation, 'statement', array_merge($options, $this->options));
$pdf = $ps->boot();
$ps->config->pdf_variables = (array) $this->entity->company->settings->pdf_variables;
$ps->html_variables = $variables;
$ps->config->design = $this->getDesign();
$maker
->design($template)
->build();
$ps->designer->buildFromPartials((array)$ps->config->design->design);
$ps->builder->build();
$pdf = $ps->getPdf();
$pdf = null;
$html = $maker->getCompiledHTML(true);
// nlog($html);
$pdf = $this->convertToPdf($html);
$this->setVariables($variables);
$maker = null;
$state = null;
return $pdf;
@ -219,7 +198,7 @@ class Statement
protected function setupEntity(): self
{
if ($this->getInvoices()->count() >= 1) {
$this->entity = $this->getInvoices()->first();//@phpstan-ignore-line
$this->entity = $this->getInvoices()->first(); //@phpstan-ignore-line
}
else {
$this->entity = $this->client->invoices()->whereHas('invitations')->first();
@ -235,7 +214,7 @@ class Statement
$this->entity = \App\Models\Invoice::factory()->make(); //@phpstan-ignore-line
$this->entity->client = \App\Models\Client::factory()->make(['settings' => $settings]); //@phpstan-ignore-line
$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('user', $this->client->user);
@ -540,8 +519,8 @@ class Statement
{
$id = 1;
if (! empty($this->client->getSetting('entity_design_id'))) {
$id = (int) $this->client->getSetting('entity_design_id');
if (! empty($this->client->getSetting('statement_design_id'))) {
$id = (int) $this->client->getSetting('statement_design_id');
}
return Design::withTrashed()->find($id);

View File

@ -12,18 +12,14 @@
namespace App\Services\Invoice;
use App\Models\ClientContact;
use App\Models\Design;
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\PhantomJS\Phantom;
use App\Models\ClientContact;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Support\Facades\Storage;
use App\Services\Template\TemplateService;
class GenerateDeliveryNote
{
@ -65,62 +61,11 @@ class GenerateDeliveryNote
$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);
$html = new HtmlEngine($invitation);
if ($design->is_custom) {
$options = ['custom_partials' => json_decode(json_encode($design->design), true)];
$template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$ps = new \App\Services\Pdf\PdfService($invitation, 'delivery_note');
$variables = $html->generateLabelsAndValues();
$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;
return $ps->boot()->getPdf();
}
}

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]);
}
/* 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
if ($this->currency->code == 'JPY') {
$precision = 0;

View File

@ -42,18 +42,20 @@ class PdfMock
private string $entity_string = 'invoice';
private PdfService $pdf_service;
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
$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_string = $this->request['entity_type'];
$this->entity_string = $this->request['entity_type'];
@ -76,29 +78,44 @@ class PdfMock
$pdf_config->design = Design::withTrashed()->find($this->decodePrimaryKey($pdf_config->entity_design_id));
}
$pdf_service->config = $pdf_config;
$this->pdf_service->config = $pdf_config;
if (isset($this->request['design'])) {
$pdf_designer = (new PdfDesigner($pdf_service))->buildFromPartials($this->request['design']);
if (isset($this->request['design']) && is_array($this->request['design'])) {
$pdf_designer = (new PdfDesigner($this->pdf_service))->buildFromPartials($this->request['design']);
} 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_service->builder = $pdf_builder;
$pdf_builder = (new PdfBuilder($this->pdf_service))->build();
$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
{
$this->mock = $this->initEntity();
$this->setPdfService();
return $this;
}

View File

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

View File

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

View File

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

View File

@ -11,114 +11,11 @@
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\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\Storage;
use Illuminate\Support\Str;
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)
{
@ -143,95 +40,4 @@ class Phantom
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);
// nlog($html);
$generated = $pdf
->setHtml($html)
->generate();

View File

@ -136,6 +136,10 @@ class VendorHtmlEngine
$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['$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['$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['$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['$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['$view_link'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_purchase_order')), 'label' => ctrans('texts.view_purchase_order')];
$data['$viewLink'] = &$data['$view_link'];
@ -428,6 +432,9 @@ class VendorHtmlEngine
$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()) {
$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')];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -97,7 +97,8 @@
<div class="two-col-grid" style="">
<ninja>
<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>
</ninja>
<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\Models\Account;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use Illuminate\Support\Facades\Route;
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('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 () {
})->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\Payment;
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\TemplateService;
use App\Utils\HtmlEngine;
@ -819,80 +816,4 @@ class TemplateTest extends TestCase
// 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\Quote;
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\Traits\MakesHash;
use Tests\MockAccountData;
@ -49,52 +46,4 @@ class HtmlGenerationTest extends TestCase
$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;
use App\Services\Pdf\PdfConfiguration;
use App\Services\Pdf\PdfService;
use Tests\MockAccountData;
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;
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
{
parent::setUp();
$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()