mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Performance improvements (#3634)
* Adjustments for tests * Implement handling of temp downloading resources * Refactor paths * Refactors for file paths * Refactor paths * Add in S3 adapter * Refactor company Documment URL * Refactor for entity pdf performance * Refactors for invoice generation * Enhancements for emails invoices * Emails * Fixes for client portal queries
This commit is contained in:
parent
74a6c4f2ee
commit
e5a230e0c7
@ -5,7 +5,7 @@ APP_URL=http://127.0.0.1:8000
|
||||
APP_KEY=s7epnjtomsdond5zgfqgaqmwhhcjct02
|
||||
APP_CIPHER=AES-256-CBC
|
||||
REQUIRE_HTTPS=false
|
||||
NINJA_ENVIRONMENT=hosted
|
||||
NINJA_ENVIRONMENT=development
|
||||
|
||||
DB_TYPE=mysql
|
||||
DB_STRICT=false
|
||||
|
@ -21,3 +21,4 @@ TEST_USERNAME=user@example.com
|
||||
TEST_PERMISSIONS_USERNAME=permissions@example.com
|
||||
|
||||
MULTI_DB_ENABLED=true
|
||||
NINJA_ENVIRONMENT=development
|
||||
|
@ -143,7 +143,7 @@ class SendTestEmails extends Command
|
||||
$invoice->setRelation('invitations', $ii);
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
CreateInvoicePdf::dispatch($invoice, $company, $client->primary_contact()->first());
|
||||
CreateInvoicePdf::dispatch($invoice->invitations()->first());
|
||||
|
||||
$cc_emails = [config('ninja.testvars.test_email')];
|
||||
$bcc_emails = [config('ninja.testvars.test_email')];
|
||||
|
@ -173,7 +173,7 @@ class InvoiceFilters extends QueryFilters
|
||||
if (auth('contact')->user()) {
|
||||
return $this->contactViewFilter();
|
||||
} else {
|
||||
return $this->builder->company();
|
||||
return $this->builder->company()->with(['invitations.company'],['documents.company']);
|
||||
}
|
||||
|
||||
// return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
|
@ -77,7 +77,7 @@ class InvoiceEmail extends EmailBuilder
|
||||
->setViewText(ctrans('texts.view_invoice'));
|
||||
|
||||
if ($client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments($invoice->pdf_file_path());
|
||||
$this->setAttachments($invitation->pdf_file_path());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class QuoteEmail extends EmailBuilder
|
||||
->setBody($body_template);
|
||||
|
||||
if ($client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->attachments = $quote->pdf_file_path();
|
||||
$this->attachments = $invitation->pdf_file_path();
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -19,6 +19,7 @@ use App\Http\Requests\Request;
|
||||
use App\Jobs\Entity\ActionEntity;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use ZipStream\Option\Archive;
|
||||
@ -45,8 +46,11 @@ class InvoiceController extends Controller
|
||||
*/
|
||||
public function index(InvoiceFilters $filters)
|
||||
{
|
||||
$invoices = auth()->user()->client->company->invoices()->paginate(10);
|
||||
$client_id = auth('contact')->user()->client->id;
|
||||
|
||||
$invoices = Invoice::where('client_id', $client_id)->paginate(10);
|
||||
|
||||
// $invoices = Invoice::filter($filters);
|
||||
return $this->render('invoices.index', ['invoices' => $invoices]);
|
||||
}
|
||||
|
||||
@ -154,10 +158,9 @@ class InvoiceController extends Controller
|
||||
|
||||
//if only 1 pdf, output to buffer for download
|
||||
if ($invoices->count() == 1) {
|
||||
return response()->download(public_path($invoices->first()->pdf_file_path()));
|
||||
return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($invoices->first()->pdf_file_path()));
|
||||
}
|
||||
|
||||
|
||||
# enable output of HTTP headers
|
||||
$options = new Archive();
|
||||
$options->setSendHttpHeaders(true);
|
||||
@ -166,7 +169,7 @@ class InvoiceController extends Controller
|
||||
$zip = new ZipStream(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.invoices')).".zip", $options);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), public_path($invoice->pdf_file_path()));
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
|
||||
}
|
||||
|
||||
# finish the zip stream
|
||||
|
@ -7,6 +7,7 @@ use App\Http\Requests\ClientPortal\ProcessQuotesInBulkRequest;
|
||||
use App\Http\Requests\ClientPortal\ShowQuoteRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
@ -70,7 +71,7 @@ class QuoteController extends Controller
|
||||
}
|
||||
|
||||
if ($quotes->count() == 1) {
|
||||
return response()->download(public_path($quotes->first()->pdf_file_path()));
|
||||
return response()->download(TempFile::path($invoices->first()->pdf_file_path()), basename($quotes->first()->pdf_file_path()));
|
||||
}
|
||||
|
||||
# enable output of HTTP headers
|
||||
@ -81,7 +82,7 @@ class QuoteController extends Controller
|
||||
$zip = new ZipStream(date('Y-m-d') . '_' . str_replace(' ', '_', trans('texts.invoices')) . ".zip", $options);
|
||||
|
||||
foreach ($quotes as $quote) {
|
||||
$zip->addFileFromPath(basename($quote->pdf_file_path()), public_path($quote->pdf_file_path()));
|
||||
$zip->addFileFromPath(basename($quote->pdf_file_path()), TempFile::path($quote->pdf_file_path()));
|
||||
}
|
||||
|
||||
# finish the zip stream
|
||||
|
@ -22,6 +22,7 @@ use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Transformers\CreditTransformer;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
/**
|
||||
@ -526,8 +527,8 @@ class CreditController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
return response()->download(public_path($credit->pdf_file_path()));
|
||||
break;
|
||||
return response()->download(TempFile::path($credit->pdf_file_path()), basename($credit->pdf_file_path()));
|
||||
break;
|
||||
case 'archive':
|
||||
$this->credit_repository->archive($credit);
|
||||
|
||||
|
@ -36,6 +36,7 @@ use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
@ -659,7 +660,7 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
return response()->download(public_path($invoice->pdf_file_path()));
|
||||
return response()->download(TempFile::path($invoice->pdf_file_path()), basename($invoice->pdf_file_path()));
|
||||
break;
|
||||
case 'archive':
|
||||
$this->invoice_repo->archive($invoice);
|
||||
|
@ -31,6 +31,7 @@ use App\Models\Quote;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Transformers\QuoteTransformer;
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@ -657,7 +658,7 @@ class QuoteController extends BaseController
|
||||
# code...
|
||||
break;
|
||||
case 'download':
|
||||
return response()->download(public_path($quote->pdf_file_path()));
|
||||
return response()->download(TempFile::path($quote->pdf_file_path()), basename($quote->pdf_file_path()));
|
||||
break;
|
||||
case 'archive':
|
||||
$this->invoice_repo->archive($quote);
|
||||
|
@ -47,10 +47,11 @@ class QueryLogging
|
||||
$count = count($queries);
|
||||
$timeEnd = microtime(true);
|
||||
$time = $timeEnd - $timeStart;
|
||||
// Log::info($request->method() . ' - ' . $request->url() . ": $count queries - " . $time);
|
||||
|
||||
Log::info($request->method() . ' - ' . $request->url() . ": $count queries - " . $time);
|
||||
|
||||
// if($count > 50)
|
||||
// Log::info($queries);
|
||||
if($count > 50)
|
||||
Log::info($queries);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,6 @@ class CreateAccountRequest extends Request
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
//return ! auth()->user();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,8 @@ class ShowInvoiceRequest extends Request
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->client->id === $this->invoice->client_id;
|
||||
{info(auth('contact')->user()->client->id);
|
||||
info($this->invoice->client_id);
|
||||
return auth('contact')->user()->client->id === $this->invoice->client_id;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
@ -44,32 +45,30 @@ class CreateCreditPdf implements ShouldQueue
|
||||
|
||||
private $disk;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($credit, Company $company, ClientContact $contact = null)
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $company;
|
||||
$this->credit = $invitation->credit;
|
||||
|
||||
$this->contact = $contact;
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$this->credit->load('client');
|
||||
|
||||
if (!$this->contact) {
|
||||
$this->contact = $this->credit->client->primary_contact()->first();
|
||||
}
|
||||
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
$path = $this->credit->client->credit_filepath();
|
||||
@ -80,11 +79,8 @@ class CreateCreditPdf implements ShouldQueue
|
||||
|
||||
$designer = new Designer($this->credit, $design, $this->credit->client->getSetting('pdf_variables'), 'credit');
|
||||
|
||||
//get invoice design
|
||||
// $html = $this->generateInvoiceHtml($designer->build()->getHtml(), $this->credit, $this->contact);
|
||||
$html = $this->generateEntityHtml($designer, $this->credit, $this->contact);
|
||||
$html = (new HtmlEngine($designer, $invitation, 'credit'))->build();
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
|
@ -19,6 +19,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
@ -44,30 +45,28 @@ class CreateInvoicePdf implements ShouldQueue
|
||||
|
||||
private $disk;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invoice, Company $company, ClientContact $contact = null)
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $company;
|
||||
$this->invoice = $invitation->invoice;
|
||||
|
||||
$this->contact = $contact;
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->invoice->load('client');
|
||||
|
||||
if (!$this->contact) {
|
||||
$this->contact = $this->invoice->client->primary_contact()->first();
|
||||
}
|
||||
|
||||
{info(print_r($this->invitation->contact,1));
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
$path = $this->invoice->client->invoice_filepath();
|
||||
@ -78,8 +77,7 @@ class CreateInvoicePdf implements ShouldQueue
|
||||
|
||||
$designer = new Designer($this->invoice, $design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
|
||||
|
||||
//get invoice design
|
||||
$html = $this->generateEntityHtml($designer, $this->invoice, $this->contact);
|
||||
$html = (new HtmlEngine($designer, $this->invitation, 'invoice'))->build();
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
@ -16,6 +16,7 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\DownloadInvoices;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\TempFile;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@ -72,7 +73,7 @@ class ZipInvoices implements ShouldQueue
|
||||
$zip = new ZipStream($file_name, $options);
|
||||
|
||||
foreach ($this->invoices as $invoice) {
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), public_path($invoice->pdf_file_path()));
|
||||
$zip->addFileFromPath(basename($invoice->pdf_file_path()), TempFile::path($invoice->pdf_file_path()));
|
||||
}
|
||||
|
||||
$zip->finish();
|
||||
|
@ -19,6 +19,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
@ -44,34 +45,30 @@ class CreateQuotePdf implements ShouldQueue
|
||||
|
||||
private $disk;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote, Company $company, ClientContact $contact = null)
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->company = $company;
|
||||
$this->quote = $invitation->quote;
|
||||
|
||||
$this->contact = $contact;
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$settings = $this->quote->client->getMergedSettings();
|
||||
|
||||
$this->quote->load('client');
|
||||
|
||||
if (!$this->contact) {
|
||||
$this->contact = $this->quote->client->primary_contact()->first();
|
||||
}
|
||||
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
$path = $this->quote->client->quote_filepath();
|
||||
@ -82,20 +79,12 @@ class CreateQuotePdf implements ShouldQueue
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
||||
$quote_number = $this->quote->number;
|
||||
|
||||
//$start = microtime(true);
|
||||
|
||||
$design_body = $designer->build()->getHtml();
|
||||
|
||||
$html = $this->generateEntityHtml($designer, $this->quote, $this->contact);
|
||||
$html = (new HtmlEngine($designer, $this->invitation, 'quote'))->build();
|
||||
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
|
||||
//\Log::error("PDF Build time = ". (microtime(true) - $start));
|
||||
|
||||
$file_path = $path . $quote_number . '.pdf';
|
||||
$file_path = $path . $this->quote->number . '.pdf';
|
||||
|
||||
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
|
@ -47,7 +47,7 @@ class UploadFile implements ShouldQueue
|
||||
|
||||
public $entity;
|
||||
|
||||
public function __construct($file, $type, $user, $company, $entity, $disk = 'public')
|
||||
public function __construct($file, $type, $user, $company, $entity, $disk = null)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->type = $type;
|
||||
|
@ -34,8 +34,8 @@ class DeleteCompanyDocuments
|
||||
// Remove all files & folders, under company's path.
|
||||
// This will delete directory itself, as well.
|
||||
// In case we want to remove the content of folder, we should use $fs->cleanDirectory();
|
||||
$filesystem = new Filesystem();
|
||||
$filesystem->deleteDirectory($path);
|
||||
//$filesystem = new Filesystem();
|
||||
Storage::deleteDirectory($event->company->company_key);
|
||||
|
||||
Document::whereCompanyId($event->company->id)->delete();
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ class TemplateEmail extends Mailable
|
||||
'view_link' => $this->build_email->getViewLink(),
|
||||
'view_text' => $this->build_email->getViewText(),
|
||||
'title' => $this->build_email->getSubject(),
|
||||
'signature' => $settings->email_signature,
|
||||
'settings' => $settings,
|
||||
'company' => $company
|
||||
]);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Credit\CreditWasUpdated;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Credit\CreateCreditPdf;
|
||||
@ -218,15 +219,18 @@ class Credit extends BaseModel
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
{
|
||||
$storage_path = 'storage/' . $this->client->credit_filepath() . $this->number . '.pdf';
|
||||
|
||||
if (Storage::exists($storage_path)) {
|
||||
$storage_path = Storage::url($this->client->credit_filepath() . $this->number . '.pdf');
|
||||
|
||||
if (Storage::exists($this->client->credit_filepath() . $this->number . '.pdf')) {
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
if (!$invitation) {
|
||||
event(new CreditWasUpdated($this, $this->company));
|
||||
CreateCreditPdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
|
||||
} else {
|
||||
event(new CreditWasUpdated($this, $this->company));
|
||||
CreateCreditPdf::dispatchNow($invitation->credit, $invitation->company, $invitation->contact);
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,15 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Credit\CreditWasUpdated;
|
||||
use App\Jobs\Credit\CreateCreditPdf;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CreditInvitation extends BaseModel
|
||||
{
|
||||
@ -112,4 +115,16 @@ class CreditInvitation extends BaseModel
|
||||
$this->viewed_date = Carbon::now();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->credit->client->quote_filepath() . $this->credit->number . '.pdf');
|
||||
|
||||
if (!Storage::exists($this->credit->client->credit_filepath() . $this->credit->number . '.pdf')) {
|
||||
event(new CreditWasUpdated($this, $this->company));
|
||||
CreateCreditPdf::dispatchNow($this);
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +195,16 @@ class Invoice extends BaseModel
|
||||
return $this->morphMany(CompanyLedger::class, 'company_ledgerable');
|
||||
}
|
||||
|
||||
public function activities()
|
||||
{
|
||||
return $this->hasMany(Activity::class);
|
||||
}
|
||||
|
||||
public function history()
|
||||
{
|
||||
$this->activities->with('backup');
|
||||
}
|
||||
|
||||
// public function credits()
|
||||
// {
|
||||
// return $this->belongsToMany(Credit::class)->using(Paymentable::class)->withPivot(
|
||||
@ -356,21 +366,6 @@ class Invoice extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the template for the invoice
|
||||
*
|
||||
* @return string Either the template view, OR the template HTML string
|
||||
* @todo this needs attention, invoice->settings needs clarification
|
||||
*/
|
||||
public function design(): string
|
||||
{
|
||||
if ($this->client->getSetting('design')) {
|
||||
return File::exists(resource_path($this->client->getSetting('design'))) ? File::get(resource_path($this->client->getSetting('design'))) : File::get(resource_path('views/pdf/design1.blade.php'));
|
||||
} else {
|
||||
return File::get(resource_path('views/pdf/design1.blade.php'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the invoice calculator object
|
||||
*
|
||||
@ -389,34 +384,16 @@ class Invoice extends BaseModel
|
||||
return $invoice_calc->build();
|
||||
}
|
||||
|
||||
/** TODO// DOCUMENT THIS FUNCTIONALITY */
|
||||
public function pdf_url()
|
||||
public function pdf_file_path($invitation = null)
|
||||
{
|
||||
// $public_path = 'storage/' . $this->client->invoice_filepath() . $this->number . '.pdf';
|
||||
if(!$invitation)
|
||||
$invitation = $this->invitations->first();
|
||||
|
||||
// $storage_path = 'public/' . $this->client->invoice_filepath() . $this->number . '.pdf';
|
||||
$storage_path = Storage::url($this->client->invoice_filepath() . $this->number . '.pdf');
|
||||
|
||||
$public_path = $this->client->invoice_filepath() . $this->number . '.pdf';
|
||||
|
||||
$storage_path = 'storage/' . $this->client->invoice_filepath() . $this->number . '.pdf';
|
||||
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
if (!Storage::disk($disk)->exists($public_path)) {
|
||||
if (!Storage::exists($this->client->invoice_filepath() . $this->number . '.pdf')) {
|
||||
event(new InvoiceWasUpdated($this, $this->company));
|
||||
CreateInvoicePdf::dispatch($this, $this->company, $this->client->primary_contact()->first());
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = 'storage/' . $this->client->invoice_filepath() . $this->number . '.pdf';
|
||||
|
||||
|
||||
if (!Storage::exists($storage_path)) {
|
||||
CreateInvoicePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
|
||||
CreateInvoicePdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class InvoiceInvitation extends BaseModel
|
||||
{
|
||||
@ -122,4 +124,17 @@ class InvoiceInvitation extends BaseModel
|
||||
$this->viewed_date = Carbon::now();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->invoice->client->invoice_filepath() . $this->invoice->number . '.pdf');
|
||||
|
||||
if (!Storage::exists($this->invoice->client->invoice_filepath() . $this->invoice->number . '.pdf')) {
|
||||
event(new InvoiceWasUpdated($this->invoice, $this->company));
|
||||
CreateInvoicePdf::dispatchNow($this);
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Quote\QuoteWasUpdated;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
@ -177,17 +178,18 @@ class Quote extends BaseModel
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
{
|
||||
$storage_path = 'storage/' . $this->client->quote_filepath() . $this->number . '.pdf';
|
||||
if(!$invitation)
|
||||
$invitation = $this->invitations->where('client_contact_id', $this->client->primary_contact()->first()->id)->first();
|
||||
|
||||
if (Storage::exists($storage_path)) {
|
||||
$storage_path = Storage::url($this->client->quote_filepath() . $this->number . '.pdf');
|
||||
|
||||
if (Storage::exists($this->client->quote_filepath() . $this->number . '.pdf')) {
|
||||
return $storage_path;
|
||||
}
|
||||
|
||||
if (!$invitation) {
|
||||
CreateQuotePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
|
||||
} else {
|
||||
CreateQuotePdf::dispatchNow($invitation->quote, $invitation->company, $invitation->contact);
|
||||
}
|
||||
event(new QuoteWasUpdated($this, $this->company));
|
||||
|
||||
CreateQuotePdf::dispatchNow($invitation);
|
||||
|
||||
return $storage_path;
|
||||
}
|
||||
|
@ -11,11 +11,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Quote\QuoteWasUpdated;
|
||||
use App\Jobs\Quote\CreateQuotePdf;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class QuoteInvitation extends BaseModel
|
||||
{
|
||||
@ -110,4 +113,16 @@ class QuoteInvitation extends BaseModel
|
||||
$this->viewed_date = Carbon::now();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function pdf_file_path()
|
||||
{
|
||||
$storage_path = Storage::url($this->quote->client->quote_filepath() . $this->quote->number . '.pdf');
|
||||
|
||||
if (!Storage::exists($this->quote->client->quote_filepath() . $this->quote->number . '.pdf')) {
|
||||
event(new QuoteWasUpdated($this, $this->company));
|
||||
CreateQuotePdf::dispatchNow($this);
|
||||
}
|
||||
|
||||
return $storage_path;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Utils\TempFile;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -74,11 +75,11 @@ class BaseNotification extends Notification implements ShouldQueue
|
||||
}
|
||||
|
||||
if ($this->settings->pdf_email_attachment) {
|
||||
$mail_message->attach(public_path($this->entity->pdf_file_path()));
|
||||
$mail_message->attach(TempFile::path($this->invitation->pdf_file_path()), ['as' => basename($this->invitation->pdf_file_path())]);
|
||||
}
|
||||
|
||||
foreach ($this->entity->documents as $document) {
|
||||
$mail_message->attach($document->generateUrl(), ['as' => $document->name]);
|
||||
$mail_message->attach(TempFile::path($document->generateUrl()), ['as' => $document->name]);
|
||||
}
|
||||
|
||||
if ($this->entity instanceof Invoice && $this->settings->ubl_email_attachment) {
|
||||
@ -98,7 +99,6 @@ class BaseNotification extends Notification implements ShouldQueue
|
||||
|
||||
if ($design_style == 'custom') {
|
||||
$email_style_custom = $this->settings->email_style_custom;
|
||||
//$body = str_replace("$body", $body, $email_style_custom);
|
||||
$body = strtr($email_style_custom, "$body", $body);
|
||||
}
|
||||
|
||||
@ -109,11 +109,13 @@ class BaseNotification extends Notification implements ShouldQueue
|
||||
'title' => '',
|
||||
'settings' => '',
|
||||
'company' => '',
|
||||
'view_link' => $this->invitation->getLink(),
|
||||
'view_text' => ctrans('texts.view_'.$this->entity_string),
|
||||
'logo' => $this->entity->company->present()->logo(),
|
||||
'signature' => '',
|
||||
'signature' => $this->settings->email_signature,
|
||||
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,8 @@ class SendGenericNotification extends BaseNotification implements ShouldQueue
|
||||
$this->settings = $this->entity->client->getMergedSettings();
|
||||
$this->subject = $subject;
|
||||
$this->body = $body;
|
||||
$this->invitation = $invitation;
|
||||
$this->entity_string = $entity_string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,8 @@ class GetInvoicePdf extends AbstractService
|
||||
$this->contact = $this->invoice->client->primary_contact()->first();
|
||||
}
|
||||
|
||||
$invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first();
|
||||
|
||||
$path = $this->invoice->client->invoice_filepath();
|
||||
|
||||
$file_path = $path . $this->invoice->number . '.pdf';
|
||||
@ -41,11 +43,9 @@ class GetInvoicePdf extends AbstractService
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
if (!$file) {
|
||||
$file_path = CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->contact);
|
||||
$file_path = CreateInvoicePdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
//return $file_path;
|
||||
|
||||
return Storage::disk($disk)->path($file_path);
|
||||
}
|
||||
}
|
||||
|
@ -105,9 +105,7 @@ class InvoiceService
|
||||
|
||||
public function getInvoicePdf($contact)
|
||||
{
|
||||
$get_invoice_pdf = new GetInvoicePdf($this->invoice, $contact);
|
||||
|
||||
return $get_invoice_pdf->run();
|
||||
return (new GetInvoicePdf($this->invoice, $contact))->run();
|
||||
}
|
||||
|
||||
public function sendEmail($contact)
|
||||
|
@ -22,7 +22,7 @@ class MarkSent
|
||||
|
||||
/* Return immediately if status is not draft */
|
||||
if ($this->quote->status_id != Quote::STATUS_DRAFT) {
|
||||
return $quote;
|
||||
return $this->quote;
|
||||
}
|
||||
|
||||
$this->quote->markInvitationsSent();
|
||||
|
603
app/Utils/HtmlEngine.php
Normal file
603
app/Utils/HtmlEngine.php
Normal file
@ -0,0 +1,603 @@
|
||||
<?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\Utils;
|
||||
|
||||
use App\Designs\Designer;
|
||||
use App\Models\Country;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class HtmlEngine
|
||||
{
|
||||
|
||||
public $entity;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public $client;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $company;
|
||||
|
||||
public $designer;
|
||||
|
||||
public $settings;
|
||||
|
||||
public $entity_calc;
|
||||
|
||||
public $entity_string;
|
||||
|
||||
public function __construct(Designer $designer, $invitation, $entity_string)
|
||||
{
|
||||
$this->designer = $designer;
|
||||
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->entity = $invitation->{$entity_string};
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->client = $this->entity->client;
|
||||
|
||||
$this->settings = $this->client->getMergedSettings();
|
||||
|
||||
$this->entity_calc = $this->entity->calc();
|
||||
|
||||
$this->entity_string = $entity_string;
|
||||
}
|
||||
|
||||
public function build() :string
|
||||
{
|
||||
|
||||
App::setLocale($this->client->preferredLocale());
|
||||
|
||||
$values_and_labels = $this->generateLabelsAndValues();
|
||||
|
||||
$this->designer->build();
|
||||
|
||||
$data = [];
|
||||
$data['entity'] = $this->entity;
|
||||
$data['lang'] = $this->client->preferredLocale();
|
||||
$data['includes'] = $this->designer->getIncludes();
|
||||
$data['header'] = $this->designer->getHeader();
|
||||
$data['body'] = $this->designer->getBody();
|
||||
$data['footer'] = $this->designer->getFooter();
|
||||
|
||||
$html = view('pdf.stub', $data)->render();
|
||||
|
||||
$html = $this->parseLabelsAndValues($values_and_labels['labels'], $values_and_labels['values'], $html);
|
||||
|
||||
return $html;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
private function buildEntityDataArray() :array
|
||||
{
|
||||
if (!$this->client->currency()) {
|
||||
throw new \Exception(debug_backtrace()[1]['function'], 1);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => ''];
|
||||
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
|
||||
$data['$to'] = ['value' => '', 'label' => ctrans('texts.to')];
|
||||
$data['$total_tax_labels'] = ['value' => $this->totalTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$total_tax_values'] = ['value' => $this->totalTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$date'] = ['value' => $this->entity->date ?: ' ', 'label' => ctrans('texts.date')];
|
||||
//$data['$invoice_date'] = ['value' => $this->date ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
$data['$invoice.date'] = &$data['$date'];
|
||||
$data['$invoice.due_date'] = ['value' => $this->entity->due_date ?: ' ', 'label' => ctrans('texts.due_date')];
|
||||
$data['$due_date'] = &$data['$invoice.due_date'];
|
||||
$data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')];
|
||||
$data['$line_taxes'] = ['value' => $this->makeLineTaxes() ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
$data['$invoice.line_taxes'] = &$data['$line_taxes'];
|
||||
$data['$total_taxes'] = ['value' => $this->makeTotalTaxes() ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
$data['$invoice.total_taxes'] = &$data['$total_taxes'];
|
||||
|
||||
if ($this->entity_string == 'invoice') {
|
||||
$data['$entity_label'] = ['value' => '', 'label' => ctrans('texts.invoice')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'quote') {
|
||||
$data['$entity_label'] = ['value' => '', 'label' => ctrans('texts.quote')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.quote_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'credit') {
|
||||
$data['$entity_label'] = ['value' => '', 'label' => ctrans('texts.credit')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: ' ', 'label' => ctrans('texts.credit_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
}
|
||||
|
||||
$data['$entity_number'] = &$data['$number'];
|
||||
|
||||
//$data['$paid_to_date'] = ;
|
||||
$data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ctrans('texts.discount')];
|
||||
$data['$discount'] = &$data['$invoice.discount'];
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$invoice.subtotal'] = &$data['$subtotal'];
|
||||
$data['$invoice.balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$quote.balance_due'] = &$data['$invoice.balance_due'];
|
||||
$data['$balance_due'] = &$data['$invoice.balance_due'];
|
||||
$data['$invoice.partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.total')];
|
||||
$data['$amount'] = &$data['$total'];
|
||||
$data['$quote.total'] = &$data['$total'];
|
||||
$data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.invoice_total')];
|
||||
$data['$invoice.amount'] = &$data['$total'];
|
||||
$data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.quote_total')];
|
||||
$data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_total')];
|
||||
$data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$credit.amount'] = &$data['$credit.total'];
|
||||
$data['$credit.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$credit.date'] = ['value' => $this->entity->date, 'label' => ctrans('texts.credit_date')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
$data['$credit.balance'] = &$data['$balance'];
|
||||
|
||||
$data['$invoice.balance'] = &$data['$balance'];
|
||||
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
$data['$invoice.taxes'] = &$data['$taxes'];
|
||||
|
||||
$data['$invoice.custom1'] = ['value' => $this->entity->custom_value1 ?: ' ', 'label' => $this->makeCustomField('invoice1')];
|
||||
$data['$invoice.custom2'] = ['value' => $this->entity->custom_value2 ?: ' ', 'label' => $this->makeCustomField('invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->entity->custom_value3 ?: ' ', 'label' => $this->makeCustomField('invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->entity->custom_value4 ?: ' ', 'label' => $this->makeCustomField('invoice4')];
|
||||
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: ' ', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
|
||||
|
||||
// $data['$your_invoice'] = ;
|
||||
// $data['$quote'] = ;
|
||||
// $data['$your_quote'] = ;
|
||||
//
|
||||
$data['$quote.date'] = ['value' => $this->entity->date ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$quote.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$quote.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$quote.quote_number'] = &$data['$quote.number'];
|
||||
$data['$quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.valid_until'] = ['value' => $this->entity->due_date, 'label' => ctrans('texts.valid_until')];
|
||||
$data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')];
|
||||
$data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')];
|
||||
;
|
||||
$data['$credit_number'] = &$data['$number'];
|
||||
$data['$credit_no'] = &$data['$number'];
|
||||
$data['$credit.credit_no'] = &$data['$number'];
|
||||
|
||||
// $data['$invoice_issued_to'] = ;
|
||||
// $data['$quote_issued_to'] = ;
|
||||
// $data['$rate'] = ;
|
||||
// $data['$hours'] = ;
|
||||
// $data['$from'] = ;
|
||||
// $data['$to'] = ;
|
||||
// $data['$invoice_to'] = ;
|
||||
// $data['$quote_to'] = ;
|
||||
// $data['$details'] = ;
|
||||
$data['$invoice_no'] = &$data['$number'];
|
||||
$data['$invoice.invoice_no'] = &$data['$number'];
|
||||
$data['$client1'] = ['value' => $this->client->custom_value1 ?: ' ', 'label' => $this->makeCustomField('client1')];
|
||||
$data['$client2'] = ['value' => $this->client->custom_value2 ?: ' ', 'label' => $this->makeCustomField('client2')];
|
||||
$data['$client3'] = ['value' => $this->client->custom_value3 ?: ' ', 'label' => $this->makeCustomField('client3')];
|
||||
$data['$client4'] = ['value' => $this->client->custom_value4 ?: ' ', 'label' => $this->makeCustomField('client4')];
|
||||
$data['$address1'] = ['value' => $this->client->address1 ?: ' ', 'label' => ctrans('texts.address1')];
|
||||
$data['$address2'] = ['value' => $this->client->address2 ?: ' ', 'label' => ctrans('texts.address2')];
|
||||
$data['$id_number'] = ['value' => $this->client->id_number ?: ' ', 'label' => ctrans('texts.id_number')];
|
||||
$data['$vat_number'] = ['value' => $this->client->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')];
|
||||
$data['$website'] = ['value' => $this->client->present()->website() ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$phone'] = ['value' => $this->client->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')];
|
||||
$data['$country'] = ['value' => isset($this->client->country->name) ? $this->client->country->name : 'No Country Set', 'label' => ctrans('texts.country')];
|
||||
$data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')];
|
||||
$data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: ' ', 'label' => ctrans('texts.client_name')];
|
||||
$data['$client.name'] = &$data['$client_name'];
|
||||
$data['$client.address1'] = &$data['$address1'];
|
||||
$data['$client.address2'] = &$data['$address2'];
|
||||
$data['$client_address'] = ['value' => $this->entity->present()->address() ?: ' ', 'label' => ctrans('texts.address')];
|
||||
$data['$client.address'] = &$data['$client_address'];
|
||||
$data['$client.id_number'] = &$data['$id_number'];
|
||||
$data['$client.vat_number'] = &$data['$vat_number'];
|
||||
$data['$client.website'] = &$data['$website'];
|
||||
$data['$client.phone'] = &$data['$phone'];
|
||||
$data['$city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')];
|
||||
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
|
||||
$data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')];
|
||||
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
|
||||
$data['$client.country'] = &$data['$country'];
|
||||
$data['$client.email'] = &$data['$email'];
|
||||
|
||||
|
||||
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')];
|
||||
$data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')];
|
||||
|
||||
$data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : 'no contact name on record', 'label' => ctrans('texts.contact_name')];
|
||||
$data['$contact.custom1'] = ['value' => isset($this->contact) ? $this->contact->custom_value1 : ' ', 'label' => $this->makeCustomField('contact1')];
|
||||
$data['$contact.custom2'] = ['value' => isset($this->contact) ? $this->contact->custom_value2 : ' ', 'label' => $this->makeCustomField('contact1')];
|
||||
$data['$contact.custom3'] = ['value' => isset($this->contact) ? $this->contact->custom_value3 : ' ', 'label' => $this->makeCustomField('contact1')];
|
||||
$data['$contact.custom4'] = ['value' => isset($this->contact) ? $this->contact->custom_value4 : ' ', 'label' => $this->makeCustomField('contact1')];
|
||||
|
||||
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')];
|
||||
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')];
|
||||
$data['$company.name'] = ['value' => $this->company->present()->name() ?: ' ', 'label' => ctrans('texts.company_name')];
|
||||
$data['$company.address1'] = ['value' => $this->settings->address1 ?: ' ', 'label' => ctrans('texts.address1')];
|
||||
$data['$company.address2'] = ['value' => $this->settings->address2 ?: ' ', 'label' => ctrans('texts.address2')];
|
||||
$data['$company.city'] = ['value' => $this->settings->city ?: ' ', 'label' => ctrans('texts.city')];
|
||||
$data['$company.state'] = ['value' => $this->settings->state ?: ' ', 'label' => ctrans('texts.state')];
|
||||
$data['$company.postal_code'] = ['value' => $this->settings->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')];
|
||||
$data['$company.country'] = ['value' => $this->getCountryName(), 'label' => ctrans('texts.country')];
|
||||
$data['$company.phone'] = ['value' => $this->settings->phone ?: ' ', 'label' => ctrans('texts.phone')];
|
||||
$data['$company.email'] = ['value' => $this->settings->email ?: ' ', 'label' => ctrans('texts.email')];
|
||||
$data['$company.vat_number'] = ['value' => $this->settings->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')];
|
||||
$data['$company.id_number'] = ['value' => $this->settings->id_number ?: ' ', 'label' => ctrans('texts.id_number')];
|
||||
$data['$company.website'] = ['value' => $this->settings->website ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: ' ', 'label' => ctrans('texts.address')];
|
||||
|
||||
$logo = $this->company->present()->logo($this->settings);
|
||||
|
||||
$data['$company.logo'] = ['value' => "<img src='{$logo}' class='h-32' alt='logo'>" ?: ' ', 'label' => ctrans('texts.logo')];
|
||||
$data['$company_logo'] = &$data['$company.logo'];
|
||||
$data['$company1'] = ['value' => $this->settings->custom_value1 ?: ' ', 'label' => $this->makeCustomField('company1')];
|
||||
$data['$company2'] = ['value' => $this->settings->custom_value2 ?: ' ', 'label' => $this->makeCustomField('company2')];
|
||||
$data['$company3'] = ['value' => $this->settings->custom_value3 ?: ' ', 'label' => $this->makeCustomField('company3')];
|
||||
$data['$company4'] = ['value' => $this->settings->custom_value4 ?: ' ', 'label' => $this->makeCustomField('company4')];
|
||||
|
||||
$data['$product.date'] = ['value' => '', 'label' => ctrans('texts.date')];
|
||||
$data['$product.discount'] = ['value' => '', 'label' => ctrans('texts.discount')];
|
||||
$data['$product.product_key'] = ['value' => '', 'label' => ctrans('texts.product_key')];
|
||||
$data['$product.notes'] = ['value' => '', 'label' => ctrans('texts.notes')];
|
||||
$data['$product.cost'] = ['value' => '', 'label' => ctrans('texts.cost')];
|
||||
$data['$product.quantity'] = ['value' => '', 'label' => ctrans('texts.quantity')];
|
||||
$data['$product.tax_name1'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
|
||||
|
||||
$data['$task.date'] = ['value' => '', 'label' => ctrans('texts.date')];
|
||||
$data['$task.discount'] = ['value' => '', 'label' => ctrans('texts.discount')];
|
||||
$data['$task.product_key'] = ['value' => '', 'label' => ctrans('texts.product_key')];
|
||||
$data['$task.notes'] = ['value' => '', 'label' => ctrans('texts.notes')];
|
||||
$data['$task.cost'] = ['value' => '', 'label' => ctrans('texts.cost')];
|
||||
$data['$task.quantity'] = ['value' => '', 'label' => ctrans('texts.quantity')];
|
||||
$data['$task.tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$task.tax_name1'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$task.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$task.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
|
||||
//$data['$contact.signature']
|
||||
|
||||
// $data['custom_label1'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label2'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label3'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
// $data['custom_label4'] = ['value' => '', 'label' => ctrans('texts.')];
|
||||
//$data['$blank'] = ;
|
||||
//$data['$surcharge'] = ;
|
||||
/*
|
||||
$data['$tax_invoice'] =
|
||||
$data['$tax_quote'] =
|
||||
$data['$statement'] = ;
|
||||
$data['$statement_date'] = ;
|
||||
$data['$your_statement'] = ;
|
||||
$data['$statement_issued_to'] = ;
|
||||
$data['$statement_to'] = ;
|
||||
$data['$credit_note'] = ;
|
||||
$data['$credit_date'] = ;
|
||||
$data['$credit_issued_to'] = ;
|
||||
$data['$credit_to'] = ;
|
||||
$data['$your_credit'] = ;
|
||||
$data['$phone'] = ;
|
||||
|
||||
$data['$outstanding'] = ;
|
||||
$data['$invoice_due_date'] = ;
|
||||
$data['$quote_due_date'] = ;
|
||||
$data['$service'] = ;
|
||||
$data['$product_key'] = ;
|
||||
$data['$unit_cost'] = ;
|
||||
$data['$custom_value1'] = ;
|
||||
$data['$custom_value2'] = ;
|
||||
$data['$delivery_note'] = ;
|
||||
$data['$date'] = ;
|
||||
$data['$method'] = ;
|
||||
$data['$payment_date'] = ;
|
||||
$data['$reference'] = ;
|
||||
$data['$amount'] = ;
|
||||
$data['$amount_paid'] =;
|
||||
*/
|
||||
|
||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function generateLabelsAndValues()
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$values = $this->buildEntityDataArray();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$data['values'][$key] = $value['value'];
|
||||
$data['labels'][$key.'_label'] = $value['label'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
private function totalTaxLabels() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (!$this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<span>'. $tax['name'] .'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function totalTaxValues() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (!$this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<span>'. Number::formatMoney($tax['total'], $this->client) .'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function lineTaxLabels() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<span>'. $tax['name'] .'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getCountryName() :string
|
||||
{
|
||||
$country = Country::find($this->settings->country_id)->first();
|
||||
|
||||
if($country)
|
||||
return $country->name;
|
||||
|
||||
|
||||
return ' ';
|
||||
}
|
||||
/**
|
||||
* Due to the way we are compiling the blade template we
|
||||
* have no ability to iterate, so in the case
|
||||
* of line taxes where there are multiple rows,
|
||||
* we use this function to format a section of rows
|
||||
*
|
||||
* @return string a collection of <tr> rows with line item
|
||||
* aggregate data
|
||||
*/
|
||||
private function makeLineTaxes() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<tr class="line_taxes">';
|
||||
$data .= '<td>'. $tax['name'] .'</td>';
|
||||
$data .= '<td>'. Number::formatMoney($tax['total'], $this->client) .'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function lineTaxValues() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<span>'. Number::formatMoney($tax['total'], $this->client) .'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function makeCustomField($field) :string
|
||||
{
|
||||
$custom_fields = $this->company->custom_fields;
|
||||
|
||||
if ($custom_fields && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
$custom_field_parts = explode("|", $custom_field);
|
||||
|
||||
return $custom_field_parts[0];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function makeTotalTaxes() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (!$this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<tr class="total_taxes">';
|
||||
$data .= '<td>'. $tax['name'] .'</td>';
|
||||
$data .= '<td>'. Number::formatMoney($tax['total'], $this->client) .'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function parseLabelsAndValues($labels, $values, $section) :string
|
||||
{
|
||||
|
||||
$section = strtr($section, $labels);
|
||||
return strtr($section, $values);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
| Ensures the URL doesn't have duplicated trailing slash
|
||||
*/
|
||||
public function generateAppUrl()
|
||||
{
|
||||
return rtrim(config('ninja.app_url'), "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds CSS to assist with the generation
|
||||
* of Repeating headers and footers on the PDF
|
||||
* @return string The css string
|
||||
*/
|
||||
private function generateCustomCSS() :string
|
||||
{
|
||||
|
||||
$header_and_footer = '
|
||||
.header, .header-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer, .footer-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
thead {display: table-header-group;}
|
||||
tfoot {display: table-footer-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
|
||||
$header = '
|
||||
.header, .header-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
thead {display: table-header-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
|
||||
$footer = '
|
||||
|
||||
.footer, .footer-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
tfoot {display: table-footer-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
$css = '';
|
||||
|
||||
if ($this->settings->all_pages_header && $this->settings->all_pages_footer) {
|
||||
$css .= $header_and_footer;
|
||||
} elseif ($this->settings->all_pages_header && !$this->settings->all_pages_footer) {
|
||||
$css .= $header;
|
||||
} elseif (!$this->settings->all_pages_header && $this->settings->all_pages_footer) {
|
||||
$css .= $footer;
|
||||
}
|
||||
|
||||
$css .= '
|
||||
.page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0mm
|
||||
}
|
||||
|
||||
html {
|
||||
';
|
||||
|
||||
$css .= 'font-size:' . $this->settings->font_size . 'px;';
|
||||
// $css .= 'font-size:14px;';
|
||||
|
||||
$css .= '}';
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
}
|
26
app/Utils/TempFile.php
Normal file
26
app/Utils/TempFile.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?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\Utils;
|
||||
|
||||
class TempFile
|
||||
{
|
||||
|
||||
public static function path($url) :string
|
||||
{
|
||||
|
||||
$temp_path = tempnam(sys_get_temp_dir(), basename($url));
|
||||
copy($url, $temp_path);
|
||||
|
||||
return $temp_path;
|
||||
}
|
||||
|
||||
}
|
@ -102,7 +102,7 @@ class TemplateEngine
|
||||
private function replaceValues()
|
||||
{
|
||||
if ($this->entity_obj) {
|
||||
$this->entityValues();
|
||||
$this->entityValues($this->entity_obj->client->primary_contact()->first());
|
||||
} else {
|
||||
$this->fakerValues();
|
||||
}
|
||||
@ -115,11 +115,6 @@ class TemplateEngine
|
||||
$labels = $this->makeFakerLabels();
|
||||
$values = $this->makeFakerValues();
|
||||
|
||||
// $this->body = str_replace(array_keys($labels), array_values($labels), $this->body);
|
||||
// $this->body = str_replace(array_keys($values), array_values($values), $this->body);
|
||||
// $this->subject = str_replace(array_keys($labels), array_values($labels), $this->subject);
|
||||
// $this->subject = str_replace(array_keys($values), array_values($values), $this->subject);
|
||||
|
||||
$this->body = strtr($this->body, $labels);
|
||||
$this->body = strtr($this->body, $values);
|
||||
|
||||
@ -133,16 +128,16 @@ class TemplateEngine
|
||||
$this->body = $converter->convertToHtml($this->body);
|
||||
}
|
||||
|
||||
private function entityValues()
|
||||
private function entityValues($contact)
|
||||
{
|
||||
$labels = $this->entity_obj->makeLabels();
|
||||
$values = $this->entity_obj->makeValues();
|
||||
|
||||
$this->body = strtr($this->body, $labels);
|
||||
$this->body = strtr($this->body, $values);
|
||||
$data = $this->entity_obj->buildLabelsAndValues($contact);
|
||||
|
||||
$this->subject = strtr($this->subject, $labels);
|
||||
$this->subject = strtr($this->subject, $values);
|
||||
$this->body = strtr($this->body, $data['labels']);
|
||||
$this->body = strtr($this->body, $data['values']);
|
||||
|
||||
$this->subject = strtr($this->subject, $data['labels']);
|
||||
$this->subject = strtr($this->subject, $data['values']);
|
||||
|
||||
$converter = new CommonMarkConverter([
|
||||
'allow_unsafe_links' => false,
|
||||
|
@ -42,8 +42,7 @@ trait MakesInvoiceHtml
|
||||
|
||||
App::setLocale($client->preferredLocale());
|
||||
|
||||
$labels = $entity->makeLabels();
|
||||
$values = $entity->makeValues($contact);
|
||||
$values_and_labels = $entity->buildLabelsAndValues($contact);
|
||||
|
||||
$designer->build();
|
||||
|
||||
@ -57,10 +56,8 @@ trait MakesInvoiceHtml
|
||||
|
||||
$html = view('pdf.stub', $data)->render();
|
||||
|
||||
$html = $this->parseLabelsAndValues($labels, $values, $html);
|
||||
|
||||
// info($html);
|
||||
|
||||
$html = $this->parseLabelsAndValues($values_and_labels['labels'], $values_and_labels['values'], $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
@ -72,17 +69,13 @@ trait MakesInvoiceHtml
|
||||
|
||||
App::setLocale($client->preferredLocale());
|
||||
|
||||
$labels = $entity->makeLabels();
|
||||
$values = $entity->makeValues($contact);
|
||||
$data = $entity->buildLabelsAndValues($contact);
|
||||
|
||||
return $this->parseLabelsAndValues($labels, $values, $content);
|
||||
return $this->parseLabelsAndValues($data['labels'], $data['values'], $content);
|
||||
}
|
||||
|
||||
private function parseLabelsAndValues($labels, $values, $section) :string
|
||||
{
|
||||
|
||||
// $section = str_replace(array_keys($labels), array_values($labels), $section);
|
||||
// $section = str_replace(array_keys($values), array_values($values), $section);
|
||||
|
||||
$section = strtr($section, $labels);
|
||||
$section = strtr($section, $values);
|
||||
|
@ -159,6 +159,20 @@ trait MakesInvoiceValues
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function buildLabelsAndValues($contact)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$values = $this->makeLabelsAndValues($contact);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$data['values'][$key] = $value['value'];
|
||||
$data['labels'][$key.'_label'] = $value['label'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function makeLabelsAndValues($contact = null) :array
|
||||
{
|
||||
if (!$this->client->currency() || !$this->client) {
|
||||
@ -432,7 +446,7 @@ trait MakesInvoiceValues
|
||||
|
||||
$table_header .= '</tr>';
|
||||
|
||||
$table_header = str_replace(array_keys($data), array_values($data), $table_header);
|
||||
$table_header = strtr($table_header, $data);// str_replace(array_keys($data), array_values($data), $table_header);
|
||||
|
||||
return $table_header;
|
||||
}
|
||||
|
@ -37,6 +37,8 @@
|
||||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/socialite": "^4.0",
|
||||
"laravel/tinker": "^1.0",
|
||||
"league/flysystem-aws-s3-v3": "~1.0",
|
||||
"league/flysystem-cached-adapter": "~1.0",
|
||||
"league/fractal": "^0.17.0",
|
||||
"league/omnipay": "^3.0",
|
||||
"maennchen/zipstream-php": "^1.2",
|
||||
|
@ -61,7 +61,9 @@ return [
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'url' => env('AWS_URL'),
|
||||
'visibility' => 'public',
|
||||
],
|
||||
'gcs' => [
|
||||
'driver' => 'gcs',
|
||||
|
@ -3197,4 +3197,5 @@ return [
|
||||
'open_in_new_tab' => 'Open in new tab',
|
||||
'complete_your_payment' => 'Complete payment',
|
||||
'authorize_for_future_use' => 'Authorize payment method for future use',
|
||||
'view_credit' => 'View Credit',
|
||||
];
|
||||
|
@ -9,9 +9,15 @@
|
||||
@lang($body)
|
||||
@endslot
|
||||
|
||||
@slot('signature')
|
||||
@if(isset($view_link))
|
||||
@component('email.components.button', ['url' => $view_link])
|
||||
{{ $view_text }}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@if(isset($signature))
|
||||
{{ $signature }}
|
||||
@endslot
|
||||
@endif
|
||||
|
||||
@slot('footer')
|
||||
@component('email.components.footer', ['url' => 'https://invoiceninja.com', 'url_text' => '© InvoiceNinja'])
|
||||
|
@ -9,6 +9,11 @@
|
||||
|
||||
@endcomponent
|
||||
|
||||
@if(isset($view_link))
|
||||
@component('email.components.button', ['url' => $view_link])
|
||||
{{$view_text}}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
@if($footer)
|
||||
@component('email.components.button', ['url' => $view_link])
|
||||
|
@ -32,7 +32,7 @@
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
|
||||
<embed src="{{ asset($invoice->pdf_url()) }}#toolbar=1&navpanes=1&scrollbar=1" type="application/pdf" width="100%" height="1180px" />
|
||||
<embed src="{{ $invoice->pdf_file_path() }}#toolbar=1&navpanes=1&scrollbar=1" type="application/pdf" width="100%" height="1180px" />
|
||||
|
||||
<div id="pdfCanvas" style="display:none;width:100%;background-color:#525659;border:solid 2px #9a9a9a;padding-top:40px;text-align:center">
|
||||
<canvas id="theCanvas" style="max-width:100%;border:solid 1px #CCCCCC;"></canvas>
|
||||
|
@ -2,7 +2,7 @@
|
||||
@section('meta_title', ctrans('texts.view_invoice'))
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ asset($invoice->pdf_url()) }}">
|
||||
<meta name="pdf-url" content="{{ $invoice->pdf_file_path() }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
@endpush
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="{{ asset($invoice->pdf_url()) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
<a target="_blank" href="{{ $invoice->pdf_file_path() }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,7 +75,7 @@
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="{{ asset($invoice->pdf_url()) }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
<a target="_blank" href="{{ $invoice->pdf_file_path() }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,9 +51,7 @@ Route::group(['middleware' => ['auth:contact','locale'], 'prefix' => 'client', '
|
||||
|
||||
Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
|
||||
/*Invitation catches*/
|
||||
Route::get('invoice/{invitation_key}', 'ClientPortal\InvitationController@router')->name('invoice.invitation_key');
|
||||
Route::get('quote/{invitation_key}', 'ClientPortal\InvitationController@router')->name('quote.invitation_key');
|
||||
Route::get('credit/{invitation_key}', 'ClientPortal\InvitationController@router')->name('credit.invitation_key');
|
||||
Route::get('{entity}/{invitation_key}', 'ClientPortal\InvitationController@router');
|
||||
Route::get('invoice/{invitation_key}/download_pdf', 'InvoiceController@downloadPdf')->name('invoice.download_invitation_key');
|
||||
Route::get('quote/{invitation_key}/download_pdf', 'QuoteController@downloadPdf')->name('quote.download_invitation_key');
|
||||
Route::get('credit/{invitation_key}/download_pdf', 'CreditController@downloadPdf')->name('credit.download_invitation_key');
|
||||
|
@ -65,6 +65,10 @@ class DesignTest extends TestCase
|
||||
|
||||
$this->invoice->uses_inclusive_taxes = false;
|
||||
|
||||
$this->invoice->service()->createInvitations()->markSent()->save();
|
||||
$this->invoice->fresh();
|
||||
$this->invoice->load('invitations');
|
||||
|
||||
$settings = $this->invoice->client->settings;
|
||||
$settings->invoice_design_id = "VolejRejNm";
|
||||
$settings->all_pages_header = true;
|
||||
@ -73,7 +77,7 @@ class DesignTest extends TestCase
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
|
||||
CreateInvoicePdf::dispatchNow($this->invoice->invitations->first());
|
||||
}
|
||||
|
||||
public function testQuoteDesignExists()
|
||||
@ -97,6 +101,10 @@ class DesignTest extends TestCase
|
||||
|
||||
$this->quote->uses_inclusive_taxes = false;
|
||||
|
||||
$this->quote->service()->createInvitations()->markSent()->save();
|
||||
|
||||
$this->quote->fresh();
|
||||
$this->quote->load('invitations');
|
||||
$settings = $this->quote->client->settings;
|
||||
$settings->invoice_design_id = "VolejRejNm";
|
||||
$settings->all_pages_header = true;
|
||||
@ -105,7 +113,12 @@ class DesignTest extends TestCase
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
|
||||
$this->quote->setRelation('client', $this->client);
|
||||
|
||||
$invitation = $this->quote->invitations->first();
|
||||
$invitation->setRelation('quote', $this->quote);
|
||||
|
||||
CreateQuotePdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
|
||||
@ -126,12 +139,17 @@ class DesignTest extends TestCase
|
||||
|
||||
$this->credit->client_id = $this->client->id;
|
||||
$this->credit->setRelation('client', $this->client);
|
||||
$this->credit->save();
|
||||
|
||||
$this->credit->service()->createInvitations()->markSent()->save();
|
||||
$this->credit->fresh();
|
||||
$this->credit->load('invitations');
|
||||
$invitation = $this->credit->invitations->first();
|
||||
$invitation->setRelation('credit', $this->credit);
|
||||
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateCreditPdf::dispatchNow($this->credit, $this->credit->company, $this->credit->client->primary_contact()->first());
|
||||
CreateCreditPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
public function testAllDesigns()
|
||||
@ -149,7 +167,14 @@ class DesignTest extends TestCase
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
|
||||
$this->quote->service()->createInvitations()->markSent()->save();
|
||||
$this->quote->fresh();
|
||||
$this->quote->load('invitations');
|
||||
|
||||
$invitation = $this->quote->invitations->first();
|
||||
$invitation->setRelation('quote', $this->quote);
|
||||
|
||||
CreateQuotePdf::dispatchNow($invitation);
|
||||
|
||||
$this->quote->number = $this->getNextQuoteNumber($this->quote->client);
|
||||
$this->quote->save();
|
||||
@ -158,117 +183,4 @@ class DesignTest extends TestCase
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 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());
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class InvoiceUploadTest extends TestCase
|
||||
|
||||
public function testInvoiceUploadWorks()
|
||||
{
|
||||
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
|
||||
CreateInvoicePdf::dispatchNow($this->invoice->invitations->first());
|
||||
|
||||
$this->assertNotNull($this->invoice->service()->getInvoicePdf($this->invoice->client->primary_contact()->first()));
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\ViewComposers\TranslationComposer
|
||||
*/
|
||||
class CollectionMergingTest extends TestCase
|
||||
{
|
||||
|
@ -30,6 +30,9 @@ class CompanyDocumentsTest extends TestCase
|
||||
|
||||
public function testCompanyDocumentExists()
|
||||
{
|
||||
$company_key = $this->company->company_key;
|
||||
|
||||
|
||||
$original_count = Document::whereCompanyId($this->company->id)->count();
|
||||
|
||||
$image = UploadedFile::fake()->image('avatar.jpg');
|
||||
@ -44,16 +47,14 @@ class CompanyDocumentsTest extends TestCase
|
||||
|
||||
$this->assertNotNull($document);
|
||||
|
||||
$this->assertGreaterThan($original_count, Document::whereCompanyId($this->company->id)->count());
|
||||
$this->assertTrue(Storage::exists($document->path));
|
||||
|
||||
$company_key = $this->company->company_key;
|
||||
$this->assertGreaterThan($original_count, Document::whereCompanyId($this->company->id)->count());
|
||||
|
||||
$this->company->delete();
|
||||
|
||||
$this->assertEquals(0, Document::whereCompanyId($this->company->id)->count());
|
||||
|
||||
$path = sprintf('%s/%s', storage_path('app/public'), $company_key);
|
||||
|
||||
$this->assertFalse(file_exists($path));
|
||||
$this->assertFalse(Storage::exists($document->path));
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Utils\Traits\Invoice\ActionInvoice
|
||||
* @covers App\Utils\Traits\Invoice\ActionsInvoice
|
||||
*/
|
||||
class InvoiceActionsTest extends TestCase
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user