Repeating header and footers on Invoice PDFs (#3424)

* remove jobs table

* Working on notifications

* Working on notifications

* Fixes for setting group level currency id on new client

* Working on repeating headers

* Use CSS to force headers and footers

* recurring headers and footers

* Preview PDF

* Working on PDF Preview
This commit is contained in:
David Bomba 2020-03-05 18:14:57 +11:00 committed by GitHub
parent 5a7d6c4a7a
commit 7acc6ee300
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 699 additions and 142 deletions

View File

@ -220,8 +220,8 @@ class CompanySettings extends BaseSettings {
public $secondary_font = 'Roboto';
public $hide_paid_to_date = false;
public $embed_documents = false;
public $all_pages_header = true;
public $all_pages_footer = true;
public $all_pages_header = false;
public $all_pages_footer = false;
public $pdf_variables = [];
public static $casts = [

View File

@ -19,7 +19,9 @@ abstract class AbstractDesign
abstract public function body();
abstract public function table();
abstract public function product_table();
abstract public function task_table();
abstract public function footer();

View File

@ -39,7 +39,11 @@ class Bold extends AbstractDesign
{
size: auto;
margin-top: 5mm;
}
}
.text-left .table_header_thead_class {}
.px-12 .text-2xl .px-4 .py-2 .table_header_td_class {}
.bg-gray-200 .py-5 .pl-12 .table_body_td_class {}
</style>
';
}
@ -95,8 +99,10 @@ class Bold extends AbstractDesign
'table_body_td_class' => "bg-gray-200 py-5 pl-12",
];
}
public function task_table() {
}
public function table() {
public function product_table() {
return '
<table class="w-full table-auto mt-8">

View File

@ -106,7 +106,10 @@ class Business extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-20">

View File

@ -106,8 +106,10 @@ class Clean extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-8">
<thead class="text-left">

View File

@ -102,8 +102,11 @@ class Creative extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-12 border-t-4 border-pink-700 bg-white">
<thead class="text-left rounded-lg">

View File

@ -19,8 +19,10 @@ class Custom extends AbstractDesign
private $body;
private $table;
private $product_table;
private $task_table;
private $footer;
private $table_styles;
@ -33,8 +35,10 @@ class Custom extends AbstractDesign
$this->body = $design->body;
$this->table = $design->table;
$this->product_table = $design->product_table;
$this->task_table = $design->task_table;
$this->footer = $design->footer;
$this->table_styles = $design->table_styles;
@ -67,18 +71,23 @@ class Custom extends AbstractDesign
}
public function table()
public function product_table()
{
return $this->table;
return $this->product_table;
}
public function task_table()
{
return $this->task_table;
}
public function footer()
{
return $this->footer;
}
}

View File

@ -26,6 +26,8 @@ class Designer {
protected $entity_string;
protected $entity;
private static $custom_fields = [
'invoice1',
'invoice2',
@ -49,8 +51,9 @@ class Designer {
'company4',
];
public function __construct($design, $input_variables, $entity_string)
public function __construct($entity, $design, $input_variables, $entity_string)
{
$this->entity = $entity;
$this->design = $design;
@ -65,27 +68,63 @@ class Designer {
* formatted HTML
* @return string The HTML design built
*/
public function build($entity):Designer
public function build():Designer
{
$this->exportVariables($entity)
$this->setHtml()
->exportVariables()
->setDesign($this->getSection('include'))
->setDesign($this->getSection('header'))
->setDesign($this->getSection('body'))
->setDesign($this->getTable($entity))
->setDesign($this->getProductTable($this->entity))
->setDesign($this->getSection('footer'));
return $this;
}
public function getTable($entity):string
public function init()
{
$this->setHtml()
->exportVariables();
return $this;
}
public function getHeader()
{
$table_header = $entity->table_header($this->input_variables['product_columns'], $this->design->table_styles());
$table_body = $entity->table_body($this->input_variables['product_columns'], $this->design->table_styles());
$this->setDesign($this->getSection('include'))
->setDesign($this->getSection('header'));
$data = str_replace('$table_header', $table_header, $this->getSection('table'));
return $this;
}
public function getFooter()
{
$this->setDesign($this->getSection('footer'));
return $this;
}
public function getBody()
{
$this->setDesign($this->getSection('include'))
->setDesign($this->getSection('body'))
->setDesign($this->getProductTable());
return $this;
}
public function getProductTable():string
{
$table_header = $this->entity->table_header($this->input_variables['product_columns'], $this->design->table_styles());
$table_body = $this->entity->table_body($this->input_variables['product_columns'], $this->design->table_styles());
$data = str_replace('$table_header', $table_header, $this->getSection('product_table'));
$data = str_replace('$table_body', $table_body, $data);
return $data;
@ -97,6 +136,13 @@ class Designer {
return $this->html;
}
public function setHtml()
{
$this->html = '';
return $this;
}
private function setDesign($section)
{
@ -117,10 +163,10 @@ class Designer {
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{$section}());
}
private function exportVariables($entity)
private function exportVariables()
{
$company = $entity->company;
$company = $this->entity->company;
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
@ -170,35 +216,6 @@ class Designer {
return $output;
}
// private function exportVariables()
// {
// /*
// * $entity_labels
// * $entity_details
// */
// $header = $this->design->header();
// /*
// * $company_logo - full URL
// * $client_details
// */
// $body = $this->design->body();
// /*
// * $table_header
// * $table_body
// * $total_labels
// * $total_values
// */
// $table = $this->design->table();
// /*
// * $company_details
// * $company_address
// */
// $footer = $this->design->footer();
// }
private function clientDetails(Company $company)
{

View File

@ -95,8 +95,10 @@ class Elegant extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mb-6 mt-16">
<thead class="text-left border-dashed border-b border-black">

View File

@ -110,8 +110,10 @@ class Hipster extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-24">
<thead class="text-left">

File diff suppressed because one or more lines are too long

View File

@ -109,8 +109,10 @@ class Photo extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<div class="px-16 py-16">
<table class="w-full table-auto">

View File

@ -96,8 +96,10 @@ class Plain extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-8">
<thead class="text-left bg-gray-300">

View File

@ -104,8 +104,10 @@ class Playful extends AbstractDesign
];
}
public function table() {
public function task_table() {
}
public function product_table() {
return '
<table class="w-full table-auto mt-20 mb-8">
<thead class="text-left bg-teal-600 rounded-lg">

View File

@ -11,6 +11,7 @@
namespace App\Http\Controllers\ClientPortal;
use App\Events\Misc\InvitationWasViewed;
use App\Http\Controllers\Controller;
use App\Models\InvoiceInvitation;
use App\Utils\Traits\MakesDates;

View File

@ -0,0 +1,140 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Designs\Custom;
use App\Designs\Designer;
use App\Factory\InvoiceFactory;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Util\PreviewPdf;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Support\Facades\Storage;
class PreviewController extends BaseController
{
use MakesHash;
use MakesInvoiceHtml;
public function __construct()
{
parent::__construct();
}
/**
* Returns a template filled with entity variables
*
* @return \Illuminate\Http\Response
*
* @OA\Post(
* path="/api/v1/preview",
* operationId="getPreview",
* tags={"preview"},
* summary="Returns a pdf preview",
* description="Returns a pdf preview.",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="entity",
* in="path",
* description="The PDF",
* example="invoice",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Parameter(
* name="entity_id",
* in="path",
* description="The Entity ID",
* example="X9f87dkf",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="The pdf response",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function show()
{
if (request()->has('entity') &&
request()->has('entity_id') &&
request()->has('body'))
{
$invoice_design = new Custom((object)request()->input('body'));
$entity = ucfirst(request()->input('entity'));
$class = "App\Models\\$entity";
$pdf_class = "App\Jobs\\$entity\\Create{$entity}Pdf";
$entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first();
if(!$entity_obj)
return $this->blankEntity();
$entity_obj->load('client');
$designer = new Designer($entity_obj, $invoice_design, $entity_obj->client->getSetting('pdf_variables'), lcfirst($entity));
$html = $this->generateInvoiceHtml($designer->build()->getHtml(), $entity_obj);
$file_path = PreviewPdf::dispatchNow($html, auth()->user()->company());
return response()->download($file_path)->deleteFileAfterSend(true);
}
return $this->blankEntity();
}
private function blankEntity()
{
return response()->json(['message' => 'Blank Entity not implemented.'], 200);
// $invoice_design = new Custom((object)request()->input('body'));
// $file_path = PreviewPdf::dispatchNow(request()->input('body'), auth()->user()->company());
// return response()->download($file_path)->deleteFileAfterSend(true);
}
}

View File

@ -11,10 +11,13 @@
namespace App\Http\Controllers;
use App\Utils\Traits\MakesHash;
use League\CommonMark\CommonMarkConverter;
class TemplateController extends BaseController
{
use MakesHash;
public function __construct()
{
parent::__construct();
@ -100,7 +103,7 @@ class TemplateController extends BaseController
{
if (request()->has('entity') && request()->has('entity_id')) {
$class = 'App\Models\\'.ucfirst(request()->input('entity'));
$entity_obj = $class::whereId(request()->input('entity_id'))->company()->first();
$entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first();
}
$subject = request()->input('subject') ?: '';

View File

@ -88,16 +88,16 @@ class StoreClientRequest extends Request
if(empty($input['group_settings_id']))
{
$input['settings']->currency_id = auth()->user()->company()->settings->currency_id;
$input['settings']->currency_id =(string) auth()->user()->company()->settings->currency_id;
}
else
{
$group_settings = GroupSetting::find($input['group_settings_id']);
if($group_settings && property_exists($group_settings->settings, 'currency_id') && is_int($group_settings->settings->currency_id))
$input['settings']->currency_id = $group_settings->currency_id;
$input['settings']->currency_id = (string)$group_settings->settings->currency_id;
else
$input['settings']->currency_id = auth()->user()->company()->settings->currency_id;
$input['settings']->currency_id = (string)auth()->user()->company()->settings->currency_id;
}
}

View File

@ -86,10 +86,10 @@ class CreateCreditPdf implements ShouldQueue {
$credit_design = new $class();
}
$designer = new Designer($credit_design, $this->credit->client->getSetting('pdf_variables'), 'credit');
$designer = new Designer($this->credit, $credit_design, $this->credit->client->getSetting('pdf_variables'), 'credit');
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->credit)->getHtml(), $this->credit, $this->contact);
$html = $this->generateInvoiceHtml($designer->build()->getHtml(), $this->credit, $this->contact);
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);

View File

@ -84,10 +84,10 @@ class CreateInvoicePdf implements ShouldQueue {
$invoice_design = new $class();
}
$designer = new Designer($invoice_design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
$designer = new Designer($this->invoice, $invoice_design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice, $this->contact);
$html = $this->generateInvoiceHtml($designer->build()->getHtml(), $this->invoice, $this->contact);
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);

View File

@ -65,6 +65,8 @@ class CreateQuotePdf implements ShouldQueue {
MultiDB::setDB($this->company->db);
$settings = $this->quote->client->getMergedSettings();
$this->quote->load('client');
if(!$this->contact)
@ -74,7 +76,6 @@ class CreateQuotePdf implements ShouldQueue {
$path = $this->quote->client->quote_filepath();
$file_path = $path . $this->quote->number . '.pdf';
$design = Design::find($this->quote->client->getSetting('quote_design_id'));
@ -86,16 +87,46 @@ class CreateQuotePdf implements ShouldQueue {
$quote_design = new $class();
}
$designer = new Designer($quote_design, $this->quote->client->getSetting('pdf_variables'), 'quote');
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->quote)->getHtml(), $this->quote, $this->contact);
$designer = new Designer($this->quote, $quote_design, $this->quote->client->getSetting('pdf_variables'), 'quote');
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);
//\Log::error($html);
$pdf = $this->makePdf(null, null, $html);
$all_pages_header = $settings->all_pages_header;
$all_pages_footer = $settings->all_pages_footer;
$quote_number = $this->quote->number;
// if($all_pages_header && $all_pages_footer){
// $all_pages_header = $designer->init()->getHeader()->getHtml();
// $all_pages_footer = $designer->init()->getFooter()->getHtml();
// $design_body = $designer->init()->getBody()->getHtml();
// $quote_number = "header_and_footer";
// }
// elseif($all_pages_header){
// $all_pages_header = $designer->init()->getHeader()->getHtml();
// $design_body = $designer->init()->getBody()->getFooter()->getHtml();
// $quote_number = "header_only";
// }
// elseif($all_pages_footer){
// $all_pages_footer = $designer->init()->getFooter()->getHtml();
// $design_body = $designer->init()->getHeader()->getBody()->getHtml();
// $quote_number = "footer_only";
// }
// else{
$design_body = $designer->build()->getHtml();
//get invoice design
$html = $this->generateInvoiceHtml($design_body, $this->quote, $this->contact);
$pdf = $this->makePdf($all_pages_header, $all_pages_footer, $html);
$file_path = $path . $quote_number . '.pdf';
$instance = Storage::disk($this->disk)->put($file_path, $pdf);

View File

@ -0,0 +1,81 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Util;
use App\Designs\Custom;
use App\Designs\Designer;
use App\Designs\Modern;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Design;
use App\Models\Invoice;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
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;
use Spatie\Browsershot\Browsershot;
class PreviewPdf implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker;
public $invoice;
public $company;
public $contact;
private $disk;
public $design_string;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($design_string, Company $company)
{
$this->company = $company;
$this->design_string = $design_string;
$this->disk = $disk ?? config('filesystems.default');
}
public function handle() {
$path = $this->company->company_key;
//Storage::makeDirectory($path, 0755);
$file_path = $path . '/stream.pdf';
$pdf = $this->makePdf(null, null, $this->design_string);
$instance = Storage::disk('local')->put($file_path, $pdf);
return storage_path('app') .'/'. $file_path;
}
}

View File

@ -47,7 +47,7 @@ class InvitationViewedListener implements ShouldQueue
$notification->is_system = true;
Notification::route('slack', $payment->company->slack_webhook_url)
Notification::route('slack', $invitation->company->slack_webhook_url)
->notify($notification);
}

View File

@ -448,19 +448,25 @@ class Client extends BaseModel implements HasLocalePreference
public function invoice_filepath()
{
return $this->client_hash . '/invoices/';
return $this->company->company_key . '/' . $this->client_hash . '/invoices/';
}
public function quote_filepath()
{
return $this->client_hash . '/quotes/';
return $this->company->company_key . '/' . $this->client_hash . '/quotes/';
}
public function credit_filepath()
{
return $this->client_hash . '/credits/';
return $this->company->company_key . '/' . $this->client_hash . '/credits/';
}
public function company_filepath()
{
return $this->company->company_key . '/';
}
public function setInvoiceDefaults() :Invoice
{
$invoice_factory = InvoiceFactory::create($this->company_id, auth()->user()->id);

View File

@ -87,6 +87,14 @@ class Invoice extends BaseModel
'line_items',
'client_id',
'footer',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_surcharge_tax1',
'custom_surcharge_tax2',
'custom_surcharge_tax3',
'custom_surcharge_tax4',
];
protected $casts = [

View File

@ -11,7 +11,7 @@ use Illuminate\Notifications\Notification;
class EntityViewedNotification extends Notification implements ShouldQueue
{
use Queueable, Dispatchable;
use Queueable;
/**
* Create a new notification instance.
@ -36,6 +36,7 @@ class EntityViewedNotification extends Notification implements ShouldQueue
public function __construct($invitation, $entity_name, $is_system = false, $settings = null)
{
$this->entity_name = $entity_name;
$this->entity = $invitation->{$entity_name};
$this->contact = $invitation->contact;
$this->company = $invitation->company;
@ -89,16 +90,34 @@ class EntityViewedNotification extends Notification implements ShouldQueue
$logo = $this->company->present()->logo();
$amount = Number::formatMoney($this->entity->amount, $this->entity->client);
// return (new SlackMessage)
// ->success()
// ->from(ctrans('texts.notification_bot'))
// ->image($logo)
// ->content(ctrans("texts.notification_{$this->entity_name}_viewed",
// [
// 'amount' => $amount,
// 'client' => $this->contact->present()->name(),
// $this->entity_name => $this->entity->number
// ]));
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image($logo)
->content(ctrans("texts.notification_{$this->entity_name}_viewed",
->from(ctrans('texts.notification_bot'))
->success()
->image('https://app.invoiceninja.com/favicon-v2.png')
->content(ctrans("texts.notification_{$this->entity_name}_viewed",
[
'amount' => $amount,
'client' => $this->contact->present()->name(),
$this->entity_name => $this->entity->number
]));
]))
->attachment(function ($attachment) use($amount){
$attachment->title(ctrans('texts.entity_number_placeholder', ['entity' => ucfirst($this->entity_name), 'entity_number' => $this->entity->number]), $this->invitation->getAdminLink())
->fields([
ctrans('texts.client') => $this->contact->present()->name(),
ctrans('texts.status_viewed') => $this->invitation->viewed_date,
]);
});
}
@ -107,11 +126,6 @@ class EntityViewedNotification extends Notification implements ShouldQueue
{
$amount = Number::formatMoney($this->entity->amount, $this->entity->client);
$subject = ctrans("texts.notification_{$this->entity_name}_viewed_subject",
[
'client' => $this->contact->present()->name(),
$this->entity_name => $this->entity->number,
]);
$data = [
'title' => $subject,
@ -127,5 +141,20 @@ class EntityViewedNotification extends Notification implements ShouldQueue
'logo' => $this->company->present()->logo(),
];
return $data;
}
private function buildSubject()
{
$subject = ctrans("texts.notification_{$this->entity_name}_viewed_subject",
[
'client' => $this->contact->present()->name(),
$this->entity_name => $this->entity->number,
]);
return $subject;
}
}

View File

@ -33,6 +33,7 @@ class InvoiceSentNotification extends Notification implements ShouldQueue
public function __construct($invitation, $company, $is_system = false, $settings = null)
{
$this->invitation = $invitation;
$this->invoice = $invitation->invoice;
$this->contact = $invitation->contact;
$this->company = $company;
@ -131,7 +132,7 @@ class InvoiceSentNotification extends Notification implements ShouldQueue
'invoice' => $this->invoice->number
]))
->attachment(function ($attachment) use($amount){
$attachment->title(ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]), 'http://linky')
$attachment->title(ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]), $this->invitation->getAdminLink())
->fields([
ctrans('texts.client') => $this->contact->present()->name(),
ctrans('texts.amount') => $amount,

View File

@ -127,7 +127,10 @@ class InvoiceTransformer extends EntityTransformer
'custom_surcharge2' => (float)$invoice->custom_surcharge2,
'custom_surcharge3' => (float)$invoice->custom_surcharge3,
'custom_surcharge4' => (float)$invoice->custom_surcharge4,
'custom_surcharge_taxes' => (bool) $invoice->custom_surcharge_taxes,
'custom_surcharge_tax1' => (float) $invoice->custom_surcharge_tax1,
'custom_surcharge_tax2' => (float) $invoice->custom_surcharge_tax2,
'custom_surcharge_tax3' => (float) $invoice->custom_surcharge_tax3,
'custom_surcharge_tax4' => (float) $invoice->custom_surcharge_tax4,
'line_items' => $invoice->line_items ?: (array)[],
'backup' => $invoice->backup ?: '',
'entity_type' => 'invoice',

View File

@ -80,6 +80,10 @@ class PaymentTransformer extends EntityTransformer
'invitation_id' => (string) $payment->invitation_id ?: '',
'private_notes' => (string) $payment->private_notes ?: '',
'number' => (string) $payment->number ?: '',
'custom_value1' => (string) $payment->custom_value1 ?: '',
'custom_value2' => (string) $payment->custom_value2 ?: '',
'custom_value3' => (string) $payment->custom_value3 ?: '',
'custom_value4' => (string) $payment->custom_value4 ?: '',
'client_id' => (string) $this->encodePrimaryKey($payment->client_id),
'client_contact_id' => (string) $this->encodePrimaryKey($payment->client_contact_id),
'company_gateway_id' => (string) $this->encodePrimaryKey($payment->company_gateway_id),

View File

@ -505,7 +505,7 @@ trait MakesInvoiceValues
}
public function table_header(array $columns, array $css) :?string
public function table_header($columns, $css) :?string
{
/* Table Header */
@ -516,7 +516,7 @@ trait MakesInvoiceValues
$column_headers = $this->transformColumnsForHeader($columns);
foreach ($column_headers as $column)
$table_header .= '<td class="'.$css['table_header_td_class'].'">' . ctrans('texts.'.$column.'') . '</td>';
$table_header .= '<td class="table_header_td_class">' . ctrans('texts.'.$column.'') . '</td>';
//$table_header .= '</tr></thead>';
@ -524,7 +524,7 @@ trait MakesInvoiceValues
}
public function table_body(array $columns, array $css) :?string
public function table_body($columns, $css) :?string
{
$table_body = '';
@ -538,7 +538,7 @@ trait MakesInvoiceValues
$table_body .= '<tr class="">';
foreach ($columns as $column) {
$table_body .= '<td class="'.$css['table_body_td_class'].'">'. $item->{$column} . '</td>';
$table_body .= '<td class="table_body_td_class">'. $item->{$column} . '</td>';
}
$table_body .= '</tr>';
@ -555,6 +555,9 @@ trait MakesInvoiceValues
*/
private function transformColumnsForHeader(array $columns) :array
{
if(count($columns) == 0)
return [];
$pre_columns = $columns;
$columns = array_intersect($columns, self::$master_columns);

View File

@ -18,15 +18,44 @@ trait PdfMaker
* @return string The PDF string
*/
public function makePdf($header, $footer, $html) {
return Browsershot::html($html)
//->showBrowserHeaderAndFooter()
//->headerHtml($header)
//->footerHtml($footer)
->deviceScaleFactor(1)
->showBackground()
->waitUntilNetworkIdle(true) ->pdf();
//->margins(10,10,10,10)
//->savePdf('test.pdf');
// if($header && $footer){
// $browser = Browsershot::html($html)
// ->headerHtml($header)
// ->footerHtml($footer);
// }
// elseif($header){
// $browser = Browsershot::html($html)
// ->headerHtml($header);
// }
// else if($footer){
// $browser = Browsershot::html($html)
// ->footerHtml($footer);
// }
// else {
// $browser = Browsershot::html($html);
// }
$browser = Browsershot::html($html);
return $browser->deviceScaleFactor(1)
->showBackground()
->deviceScaleFactor(1)
->waitUntilNetworkIdle(true)
->pdf();
// return Browsershot::html($html)
// //->showBrowserHeaderAndFooter()
// //->headerHtml($header)
// //->footerHtml($footer)
// ->deviceScaleFactor(1)
// ->showBackground()
// ->waitUntilNetworkIdle(true) ->pdf();
// //->margins(10,10,10,10)
// //->savePdf('test.pdf');
}
}

View File

@ -969,6 +969,9 @@ class CreateUsersTable extends Migration
$t->softDeletes('deleted_at', 6);
$t->boolean('is_deleted')->default(false);
$t->boolean('is_manual')->default(false);
$t->decimal('exchange_rate', 16, 6)->default(1);
$t->unsignedInteger('currency_id');
$t->unsignedInteger('exchange_currency_id');
$t->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade')->onUpdate('cascade');

View File

@ -46,7 +46,8 @@ class DesignSeeder extends Seeder
$design_object->header = $invoice_design->header();
$design_object->body = $invoice_design->body();
$design_object->table_styles = $invoice_design->table_styles();
$design_object->table = $invoice_design->table();
$design_object->product_table = $invoice_design->product_table();
$design_object->task_table = $invoice_design->task_table();
$design_object->footer = $invoice_design->footer();
$design->design = $design_object;

View File

@ -3129,8 +3129,8 @@ $LANG = array(
'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice',
'notification_bot' => 'Notification Bot',
'invoice_number_placeholder' => 'Invoice # :invoice',
'entity_number_placeholder' => ':entity # :entity_number',
'email_link_not_working' => 'If button above isn\'t working for you, please click on the link',
);
return $LANG;

View File

@ -116,6 +116,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('refresh', 'Auth\LoginController@refresh');
Route::post('templates', 'TemplateController@show')->name('templates.show');
Route::post('preview', 'PreviewController@show')->name('previews.show');
Route::post('self-update', 'SelfUpdateController@update')->middleware('password_protected');

View File

@ -32,9 +32,9 @@ class DesignTest extends TestCase
$modern = new Modern();
$designer = new Designer($modern, $this->company->settings->pdf_variables, 'quote');
$designer = new Designer($this->invoice, $modern, $this->company->settings->pdf_variables, 'quote');
$html = $designer->build($this->invoice)->getHtml();
$html = $designer->build()->getHtml();
$this->assertNotNull($html);
@ -48,7 +48,7 @@ class DesignTest extends TestCase
$this->invoice->uses_inclusive_taxes = false;
$settings = $this->invoice->client->settings;
$settings->invoice_design_id = "5";
$settings->invoice_design_id = "4";
$this->client->settings = $settings;
$this->client->save();
@ -61,16 +61,16 @@ class DesignTest extends TestCase
$modern = new Modern();
$designer = new Designer($modern, $this->company->settings->pdf_variables, 'quote');
$designer = new Designer($this->quote, $modern, $this->company->settings->pdf_variables, 'quote');
$html = $designer->build($this->quote)->getHtml();
$html = $designer->build()->getHtml();
$this->assertNotNull($html);
//\Log::error($html);
$settings = $this->invoice->client->settings;
$settings->quote_design_id = "6";
$settings->quote_design_id = "4";
$this->quote->client_id = $this->client->id;
$this->quote->setRelation('client', $this->client);
@ -82,19 +82,101 @@ class DesignTest extends TestCase
CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
}
// public function testQuoteDesignWithRepeatingHeader()
// {
// $modern = new Modern();
// $designer = new Designer($this->quote, $modern, $this->company->settings->pdf_variables, 'quote');
// $html = $designer->build()->getHtml();
// $this->assertNotNull($html);
// //\Log::error($html);
// $settings = $this->invoice->client->settings;
// $settings->quote_design_id = "4";
// $settings->all_pages_header = true;
// $this->quote->client_id = $this->client->id;
// $this->quote->setRelation('client', $this->client);
// $this->quote->save();
// $this->client->settings = $settings;
// $this->client->save();
// CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
// }
// public function testQuoteDesignWithRepeatingFooter()
// {
// $modern = new Modern();
// $designer = new Designer($this->quote, $modern, $this->company->settings->pdf_variables, 'quote');
// $html = $designer->build()->getHtml();
// $this->assertNotNull($html);
// //\Log::error($html);
// $settings = $this->invoice->client->settings;
// $settings->quote_design_id = "4";
// $settings->all_pages_footer = true;
// $this->quote->client_id = $this->client->id;
// $this->quote->setRelation('client', $this->client);
// $this->quote->save();
// $this->client->settings = $settings;
// $this->client->save();
// CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
// }
// public function testQuoteDesignWithRepeatingHeaderAndFooter()
// {
// $modern = new Modern();
// $designer = new Designer($this->quote, $modern, $this->company->settings->pdf_variables, 'quote');
// $html = $designer->build()->getHtml();
// $this->assertNotNull($html);
// //\Log::error($html);
// $settings = $this->invoice->client->settings;
// $settings->quote_design_id = "4";
// $settings->all_pages_header = true;
// $settings->all_pages_footer = true;
// $this->quote->client_id = $this->client->id;
// $this->quote->setRelation('client', $this->client);
// $this->quote->save();
// $this->client->settings = $settings;
// $this->client->save();
// CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
// }
public function testCreditDesignExists()
{
$modern = new Modern();
$designer = new Designer($modern, $this->company->settings->pdf_variables, 'credit');
$designer = new Designer($this->credit, $modern, $this->company->settings->pdf_variables, 'credit');
$html = $designer->build($this->credit)->getHtml();
$html = $designer->build()->getHtml();
$this->assertNotNull($html);
$settings = $this->invoice->client->settings;
$settings->quote_design_id = "6";
$settings->quote_design_id = "4";
$this->credit->client_id = $this->client->id;
$this->credit->setRelation('client', $this->client);
@ -106,32 +188,32 @@ class DesignTest extends TestCase
CreateCreditPdf::dispatchNow($this->credit, $this->credit->company, $this->credit->client->primary_contact()->first());
}
public function testAllDesigns()
{
// public function testAllDesigns()
// {
for($x=1; $x<=10; $x++)
{
// for($x=1; $x<=10; $x++)
// {
$settings = $this->invoice->client->settings;
$settings->quote_design_id = (string)$x;
// $settings = $this->invoice->client->settings;
// $settings->quote_design_id = (string)$x;
$this->quote->client_id = $this->client->id;
$this->quote->setRelation('client', $this->client);
$this->quote->save();
// $this->quote->client_id = $this->client->id;
// $this->quote->setRelation('client', $this->client);
// $this->quote->save();
$this->client->settings = $settings;
$this->client->save();
// $this->client->settings = $settings;
// $this->client->save();
CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
// CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
$this->quote->number = $this->getNextQuoteNumber($this->quote->client);
$this->quote->save();
// $this->quote->number = $this->getNextQuoteNumber($this->quote->client);
// $this->quote->save();
}
// }
$this->assertTrue(true);
// $this->assertTrue(true);
}
// }
}

View File

@ -394,6 +394,51 @@ trait MockAccountData
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost =10;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
$line_items[] = $item;
return $line_items;
}