diff --git a/.env.dusk.example b/.env.dusk.example index 1e9637c88a31..5529bb44f721 100644 --- a/.env.dusk.example +++ b/.env.dusk.example @@ -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 diff --git a/.env.travis b/.env.travis index 2ad583a420ab..f672e2a58d55 100644 --- a/.env.travis +++ b/.env.travis @@ -21,3 +21,4 @@ TEST_USERNAME=user@example.com TEST_PERMISSIONS_USERNAME=permissions@example.com MULTI_DB_ENABLED=true +NINJA_ENVIRONMENT=development diff --git a/app/Console/Commands/SendTestEmails.php b/app/Console/Commands/SendTestEmails.php index a55b1df51c05..7e25dba03f17 100644 --- a/app/Console/Commands/SendTestEmails.php +++ b/app/Console/Commands/SendTestEmails.php @@ -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')]; diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index df727032a55b..47ff97554cf8 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -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); diff --git a/app/Helpers/Email/InvoiceEmail.php b/app/Helpers/Email/InvoiceEmail.php index 6af1550f4091..a946baeeebca 100644 --- a/app/Helpers/Email/InvoiceEmail.php +++ b/app/Helpers/Email/InvoiceEmail.php @@ -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; } diff --git a/app/Helpers/Email/QuoteEmail.php b/app/Helpers/Email/QuoteEmail.php index fc3302228eec..3696eb6ea3dd 100644 --- a/app/Helpers/Email/QuoteEmail.php +++ b/app/Helpers/Email/QuoteEmail.php @@ -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; diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index dec3c6ccf433..6bd3bc062493 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -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 diff --git a/app/Http/Controllers/ClientPortal/QuoteController.php b/app/Http/Controllers/ClientPortal/QuoteController.php index c98080caaac3..d2cf33597e40 100644 --- a/app/Http/Controllers/ClientPortal/QuoteController.php +++ b/app/Http/Controllers/ClientPortal/QuoteController.php @@ -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 diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 008ecff23321..2341ffca289c 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -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); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 8c06901e9eab..ed8266fb5758 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -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); diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 5e11973a1119..2cca17684b05 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -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); diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php index 3ad0a11d19f1..9d86390553b8 100644 --- a/app/Http/Middleware/QueryLogging.php +++ b/app/Http/Middleware/QueryLogging.php @@ -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); } } diff --git a/app/Http/Requests/Account/CreateAccountRequest.php b/app/Http/Requests/Account/CreateAccountRequest.php index 298e28c3ebbc..3b52a1c069b4 100644 --- a/app/Http/Requests/Account/CreateAccountRequest.php +++ b/app/Http/Requests/Account/CreateAccountRequest.php @@ -25,7 +25,6 @@ class CreateAccountRequest extends Request public function authorize() { return true; - //return ! auth()->user(); } /** diff --git a/app/Http/Requests/ClientPortal/ShowInvoiceRequest.php b/app/Http/Requests/ClientPortal/ShowInvoiceRequest.php index 15013892869b..e3371b40dc9d 100644 --- a/app/Http/Requests/ClientPortal/ShowInvoiceRequest.php +++ b/app/Http/Requests/ClientPortal/ShowInvoiceRequest.php @@ -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; } } diff --git a/app/Jobs/Credit/CreateCreditPdf.php b/app/Jobs/Credit/CreateCreditPdf.php index 8a0fef63e84f..496f71715dae 100644 --- a/app/Jobs/Credit/CreateCreditPdf.php +++ b/app/Jobs/Credit/CreateCreditPdf.php @@ -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); diff --git a/app/Jobs/Invoice/CreateInvoicePdf.php b/app/Jobs/Invoice/CreateInvoicePdf.php index 44f13602802a..155f27a51b5e 100644 --- a/app/Jobs/Invoice/CreateInvoicePdf.php +++ b/app/Jobs/Invoice/CreateInvoicePdf.php @@ -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); diff --git a/app/Jobs/Invoice/ZipInvoices.php b/app/Jobs/Invoice/ZipInvoices.php index 4004ce79cd77..c427ea0adf59 100644 --- a/app/Jobs/Invoice/ZipInvoices.php +++ b/app/Jobs/Invoice/ZipInvoices.php @@ -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(); diff --git a/app/Jobs/Quote/CreateQuotePdf.php b/app/Jobs/Quote/CreateQuotePdf.php index 87f0f7b9ab53..99f423c053ab 100644 --- a/app/Jobs/Quote/CreateQuotePdf.php +++ b/app/Jobs/Quote/CreateQuotePdf.php @@ -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); diff --git a/app/Jobs/Util/UploadFile.php b/app/Jobs/Util/UploadFile.php index 536dd94cab3a..6fe1fe728ad7 100644 --- a/app/Jobs/Util/UploadFile.php +++ b/app/Jobs/Util/UploadFile.php @@ -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; diff --git a/app/Listeners/Document/DeleteCompanyDocuments.php b/app/Listeners/Document/DeleteCompanyDocuments.php index 4dedb47e4c3f..e8c4d701bace 100644 --- a/app/Listeners/Document/DeleteCompanyDocuments.php +++ b/app/Listeners/Document/DeleteCompanyDocuments.php @@ -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(); } diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 78aee6af8415..1fc6d528af6c 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -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 ]); diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 9ba26acd7337..609a03b7aa8a 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -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); } diff --git a/app/Models/CreditInvitation.php b/app/Models/CreditInvitation.php index cc76d7e673dd..139a5b9a4000 100644 --- a/app/Models/CreditInvitation.php +++ b/app/Models/CreditInvitation.php @@ -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; + } } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index e0df9f8fc964..40a2a200062e 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -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; diff --git a/app/Models/InvoiceInvitation.php b/app/Models/InvoiceInvitation.php index 5906d588c00f..2fc0e1a99283 100644 --- a/app/Models/InvoiceInvitation.php +++ b/app/Models/InvoiceInvitation.php @@ -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; + } + } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 86fb9a6c422a..49d4ae481971 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -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; } diff --git a/app/Models/QuoteInvitation.php b/app/Models/QuoteInvitation.php index 5180524a92c9..b456297d7250 100644 --- a/app/Models/QuoteInvitation.php +++ b/app/Models/QuoteInvitation.php @@ -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; + } } diff --git a/app/Notifications/BaseNotification.php b/app/Notifications/BaseNotification.php index a66bb03b05ef..1a7f2f30c624 100644 --- a/app/Notifications/BaseNotification.php +++ b/app/Notifications/BaseNotification.php @@ -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; } -} +} \ No newline at end of file diff --git a/app/Notifications/SendGenericNotification.php b/app/Notifications/SendGenericNotification.php index e2e4cc353f1e..0deab68d3199 100644 --- a/app/Notifications/SendGenericNotification.php +++ b/app/Notifications/SendGenericNotification.php @@ -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; } /** diff --git a/app/Services/Invoice/GetInvoicePdf.php b/app/Services/Invoice/GetInvoicePdf.php index 3e8a7a790fe1..c6c4354636aa 100644 --- a/app/Services/Invoice/GetInvoicePdf.php +++ b/app/Services/Invoice/GetInvoicePdf.php @@ -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); } } diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index e7e49754f990..054a93b317c9 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -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) diff --git a/app/Services/Quote/MarkSent.php b/app/Services/Quote/MarkSent.php index 5010cdd3a988..09976a019cb3 100644 --- a/app/Services/Quote/MarkSent.php +++ b/app/Services/Quote/MarkSent.php @@ -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(); diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php new file mode 100644 index 000000000000..7dee190e9e89 --- /dev/null +++ b/app/Utils/HtmlEngine.php @@ -0,0 +1,603 @@ +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' => "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 .= ''. $tax['name'] .''; + } + + return $data; + } + + private function totalTaxValues() :string + { + $data = ''; + + if (!$this->entity_calc->getTotalTaxMap()) { + return $data; + } + + foreach ($this->entity_calc->getTotalTaxMap() as $tax) { + $data .= ''. Number::formatMoney($tax['total'], $this->client) .''; + } + + return $data; + } + + private function lineTaxLabels() :string + { + $tax_map = $this->entity_calc->getTaxMap(); + + $data = ''; + + foreach ($tax_map as $tax) { + $data .= ''. $tax['name'] .''; + } + + 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 rows with line item + * aggregate data + */ + private function makeLineTaxes() :string + { + $tax_map = $this->entity_calc->getTaxMap(); + + $data = ''; + + foreach ($tax_map as $tax) { + $data .= ''; + $data .= ''. $tax['name'] .''; + $data .= ''. Number::formatMoney($tax['total'], $this->client) .''; + } + + return $data; + } + + private function lineTaxValues() :string + { + $tax_map = $this->entity_calc->getTaxMap(); + + $data = ''; + + foreach ($tax_map as $tax) { + $data .= ''. Number::formatMoney($tax['total'], $this->client) .''; + } + + 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 .= ''; + $data .= ''. $tax['name'] .''; + $data .= ''. Number::formatMoney($tax['total'], $this->client) .''; + } + + 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; + } + +} \ No newline at end of file diff --git a/app/Utils/TempFile.php b/app/Utils/TempFile.php new file mode 100644 index 000000000000..8c58b4152b07 --- /dev/null +++ b/app/Utils/TempFile.php @@ -0,0 +1,26 @@ +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, diff --git a/app/Utils/Traits/MakesInvoiceHtml.php b/app/Utils/Traits/MakesInvoiceHtml.php index 5466bedadb52..c40dab153791 100644 --- a/app/Utils/Traits/MakesInvoiceHtml.php +++ b/app/Utils/Traits/MakesInvoiceHtml.php @@ -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); diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index 985c9fb2a21d..c824707954c3 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -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 .= ''; - $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; } diff --git a/composer.json b/composer.json index 37e2f47db417..af11a319dae8 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/config/filesystems.php b/config/filesystems.php index 77225298c38a..1d44642d3e11 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -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', diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 0fc69762e05a..139f2170d2c3 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -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', ]; diff --git a/resources/views/email/admin/generic_email.blade.php b/resources/views/email/admin/generic_email.blade.php index ae8d87338f7a..f9ed5418ae7e 100644 --- a/resources/views/email/admin/generic_email.blade.php +++ b/resources/views/email/admin/generic_email.blade.php @@ -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']) diff --git a/resources/views/email/template/dark.blade.php b/resources/views/email/template/dark.blade.php index 4f409cf4fa88..10cc558ade89 100644 --- a/resources/views/email/template/dark.blade.php +++ b/resources/views/email/template/dark.blade.php @@ -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]) diff --git a/resources/views/portal/default/invoices/show.blade.php b/resources/views/portal/default/invoices/show.blade.php index 2032f366f10a..607e3956326f 100644 --- a/resources/views/portal/default/invoices/show.blade.php +++ b/resources/views/portal/default/invoices/show.blade.php @@ -32,7 +32,7 @@
- +