Download Invoice by Invitation (#3312)

* style cs

* Style CS

* Throw Record not found exception if invalid primary key hash is provided

* Improve error handling

* Create abstract implementation for designs

* working on custom designs

* Add Design Model

* invoice services

* Download Invoice by Invitation
This commit is contained in:
David Bomba 2020-02-12 11:41:17 +11:00 committed by GitHub
parent dee99b1a62
commit be3ade65f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 498 additions and 201 deletions

View File

@ -0,0 +1,27 @@
<?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\Designs;
abstract class AbstractDesign
{
abstract public function header();
abstract public function body();
abstract public function table();
abstract public function footer();
abstract public function table_styles();
}

77
app/Designs/Custom.php Normal file
View File

@ -0,0 +1,77 @@
<?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\Designs;
class Custom extends AbstractDesign
{
private $header;
private $body;
private $table;
private $footer;
private $table_styles;
public function __construct(array $data)
{
$this->header = $data['header'];
$this->body = $data['body'];
$this->table = $data['table'];
$this->footer = $data['footer'];
$this->table_styles = $data['table_styles'];
}
public function header()
{
return $this->header;
}
public function body()
{
return $this->body;
}
public function table_styles()
{
return $this->table_styles;
}
public function table()
{
return $this->table;
}
public function footer()
{
return $this->footer;
}
}

View File

@ -14,8 +14,7 @@ namespace App\Designs;
use App\Models\Company;
use App\Models\Invoice;
class Designer
{
class Designer {
protected $design;
@ -26,33 +25,32 @@ class Designer
protected $html;
private static $custom_fields = [
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
'company1',
'company2',
'company3',
'company4',
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
'company1',
'company2',
'company3',
'company4',
];
public function __construct($design, $input_variables)
{
public function __construct($design, $input_variables) {
$this->design = $design;
$this->input_variables = (array)$input_variables;
$this->input_variables = (array) $input_variables;
}
/**
@ -60,23 +58,21 @@ class Designer
* formatted HTML
* @return string The HTML design built
*/
public function build(Invoice $invoice) :Designer
{
public function build(Invoice $invoice):Designer {
$this->exportVariables($invoice)
->setDesign($this->getSection('header'))
->setDesign($this->getSection('body'))
->setDesign($this->getTable($invoice))
->setDesign($this->getSection('footer'));
->setDesign($this->getSection('header'))
->setDesign($this->getSection('body'))
->setDesign($this->getTable($invoice))
->setDesign($this->getSection('footer'));
return $this;
}
public function getTable(Invoice $invoice) :string
{
public function getTable(Invoice $invoice):string {
$table_header = $invoice->table_header($this->input_variables['table_columns'], $this->design->table_styles());
$table_body = $invoice->table_body($this->input_variables['table_columns'], $this->design->table_styles());
$table_body = $invoice->table_body($this->input_variables['table_columns'], $this->design->table_styles());
$data = str_replace('$table_header', $table_header, $this->getSection('table'));
$data = str_replace('$table_body', $table_body, $data);
@ -85,13 +81,11 @@ class Designer
}
public function getHtml() :string
{
public function getHtml():string {
return $this->html;
}
private function setDesign($section)
{
private function setDesign($section) {
$this->html .= $section;
@ -99,48 +93,44 @@ class Designer
}
/**
* Returns the template section on with the
* Returns the template section on with the
* stacked variables replaced with single variables.
*
*
* @param string $section the method name to be executed ie header/body/table/footer
* @return string The HTML of the template section
*/
public function getSection($section) :string
{
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{$section}());
public function getSection($section):string {
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{ $section}());
}
private function exportVariables($invoice)
{
private function exportVariables($invoice) {
$company = $invoice->company;
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
$this->exported_variables['$company_address'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_address']), $this->companyAddress($company));
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
$this->exported_variables['$company_address'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_address']), $this->companyAddress($company));
$this->exported_variables['$invoice_details_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
$this->exported_variables['$invoice_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
$this->exported_variables['$invoice_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
return $this;
}
private function processVariables($input_variables, $variables) :string
{
private function processVariables($input_variables, $variables):string {
$output = '';
foreach($input_variables as $value)
$output .= $variables[$value];
foreach ($input_variables as $value)
$output .= $variables[$value];
return $output;
}
private function processLabels($input_variables, $variables) :string
{
private function processLabels($input_variables, $variables):string {
$output = '';
foreach($input_variables as $value) {
foreach ($input_variables as $value) {
$tmp = str_replace("</span>", "_label</span>", $variables[$value]);
$output .= $tmp;
@ -156,19 +146,19 @@ class Designer
// * $invoice_details
// */
// $header = $this->design->header();
// /*
// * $company_logo - full URL
// * $client_details
// */
// $body = $this->design->body();
// /*
// /*
// * $table_header
// * $table_body
// * $total_labels
// * $total_values
// */
// */
// $table = $this->design->table();
// /*
@ -178,132 +168,124 @@ class Designer
// $footer = $this->design->footer();
// }
private function clientDetails(Company $company)
{
private function clientDetails(Company $company) {
$data = [
'name' => '<p>$client.name</p>',
'id_number' => '<p>$client.id_number</p>',
'vat_number' => '<p>$client.vat_number</p>',
'address1' => '<p>$client.address1</p>',
'address2' => '<p>$client.address2</p>',
'name' => '<p>$client.name</p>',
'id_number' => '<p>$client.id_number</p>',
'vat_number' => '<p>$client.vat_number</p>',
'address1' => '<p>$client.address1</p>',
'address2' => '<p>$client.address2</p>',
'city_state_postal' => '<p>$client.city_state_postal</p>',
'postal_city_state' => '<p>$client.postal_city_state</p>',
'country' => '<p>$client.country</p>',
'email' => '<p>$client.email</p>',
'client1' => '<p>$client1</p>',
'client2' => '<p>$client2</p>',
'client3' => '<p>$client3</p>',
'client4' => '<p>$client4</p>',
'contact1' => '<p>$contact1</p>',
'contact2' => '<p>$contact2</p>',
'contact3' => '<p>$contact3</p>',
'contact4' => '<p>$contact4</p>',
'country' => '<p>$client.country</p>',
'email' => '<p>$client.email</p>',
'client1' => '<p>$client1</p>',
'client2' => '<p>$client2</p>',
'client3' => '<p>$client3</p>',
'client4' => '<p>$client4</p>',
'contact1' => '<p>$contact1</p>',
'contact2' => '<p>$contact2</p>',
'contact3' => '<p>$contact3</p>',
'contact4' => '<p>$contact4</p>',
];
return $this->processCustomFields($company, $data);
}
private function companyDetails(Company $company)
{
private function companyDetails(Company $company) {
$data = [
'company_name' => '<span>$company.company_name</span>',
'id_number' => '<span>$company.id_number</span>',
'vat_number' => '<span>$company.vat_number</span>',
'website' => '<span>$company.website</span>',
'email' => '<span>$company.email</span>',
'phone' => '<span>$company.phone</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
'id_number' => '<span>$company.id_number</span>',
'vat_number' => '<span>$company.vat_number</span>',
'website' => '<span>$company.website</span>',
'email' => '<span>$company.email</span>',
'phone' => '<span>$company.phone</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
];
return $this->processCustomFields($company, $data);
}
private function companyAddress(Company $company)
{
private function companyAddress(Company $company) {
$data = [
'address1' => '<span>$company.address1</span>',
'address2' => '<span>$company.address1</span>',
'address1' => '<span>$company.address1</span>',
'address2' => '<span>$company.address1</span>',
'city_state_postal' => '<span>$company.city_state_postal</span>',
'postal_city_state' => '<span>$company.postal_city_state</span>',
'country' => '<span>$company.country</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
'country' => '<span>$company.country</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
];
return $this->processCustomFields($company, $data);
}
private function invoiceDetails(Company $company)
{
private function invoiceDetails(Company $company) {
$data = [
'invoice_number' => '<span>$invoice_number</span>',
'po_number' => '<span>$po_number</span>',
'date' => '<span>$date</span>',
'due_date' => '<span>$due_date</span>',
'balance_due' => '<span>$balance_due</span>',
'invoice_total' => '<span>$invoice_total</span>',
'partial_due' => '<span>$partial_due</span>',
'invoice1' => '<span>$invoice1</span>',
'invoice2' => '<span>$invoice2</span>',
'invoice3' => '<span>$invoice3</span>',
'invoice4' => '<span>$invoice4</span>',
'surcharge1' =>'<span>$surcharge1</span>',
'surcharge2' =>'<span>$surcharge2</span>',
'surcharge3' =>'<span>$surcharge3</span>',
'surcharge4' =>'<span>$surcharge4</span>',
'po_number' => '<span>$po_number</span>',
'date' => '<span>$date</span>',
'due_date' => '<span>$due_date</span>',
'balance_due' => '<span>$balance_due</span>',
'invoice_total' => '<span>$invoice_total</span>',
'partial_due' => '<span>$partial_due</span>',
'invoice1' => '<span>$invoice1</span>',
'invoice2' => '<span>$invoice2</span>',
'invoice3' => '<span>$invoice3</span>',
'invoice4' => '<span>$invoice4</span>',
'surcharge1' => '<span>$surcharge1</span>',
'surcharge2' => '<span>$surcharge2</span>',
'surcharge3' => '<span>$surcharge3</span>',
'surcharge4' => '<span>$surcharge4</span>',
];
return $this->processCustomFields($company, $data);
}
private function processCustomFields(Company $company, $data)
{
private function processCustomFields(Company $company, $data) {
$custom_fields = $company->custom_fields;
foreach(self::$custom_fields as $cf)
{
if (!$custom_fields) {
return [];
}
if(!property_exists($custom_fields, $cf) || (strlen($custom_fields->{$cf}) == 0))
foreach (self::$custom_fields as $cf) {
if (!property_exists($custom_fields, $cf) || (strlen($custom_fields->{ $cf}) == 0)) {
unset($data[$cf]);
}
}
return $data;
}
private function processInputVariables($company, $variables)
{
private function processInputVariables($company, $variables) {
$custom_fields = $company->custom_fields;
$matches = array_intersect(self::$custom_fields, $variables);
foreach($matches as $match)
{
foreach ($matches as $match) {
if(!property_exists($custom_fields, $match) || (strlen($custom_fields->{$match}) == 0))
{
foreach($variables as $key => $value)
{
if($value == $match)
{
if (!property_exists($custom_fields, $match) || (strlen($custom_fields->{ $match}) == 0)) {
foreach ($variables as $key => $value) {
if ($value == $match) {
unset($variables[$key]);
}
}
}
}
return $variables;

View File

@ -11,15 +11,13 @@
namespace App\Designs;
class Modern
class Modern extends AbstractDesign
{
public function __construct()
{
}
public function __construct() {
}
public function header()
{
public function header() {
return '
<!DOCTYPE html>
@ -35,7 +33,7 @@ class Modern
</style>
</head>
<body>
<div class="bg-orange-600 flex justify-between py-12 px-12">
<div class="w-1/2">
<h1 class="text-white font-bold text-5xl">$company.name</h1>
@ -53,8 +51,7 @@ class Modern
}
public function body()
{
public function body() {
return '
<div class="flex justify-between px-12 pt-12">
@ -74,17 +71,15 @@ class Modern
}
public function table_styles()
{
public function table_styles() {
return [
'table_header_thead_class' => "text-left text-white bg-gray-900",
'table_header_td_class' => "px-4 py-2",
'table_body_td_class' => "border-t border-b border-gray-900 px-4 py-4",
'table_header_td_class' => "px-4 py-2",
'table_body_td_class' => "border-t border-b border-gray-900 px-4 py-4",
];
}
public function table()
{
public function table() {
return '
<div class="px-12 pt-5 pb-20">
@ -115,14 +110,14 @@ class Modern
</div>
</div>
<div class="flex px-4 mt-4 w-full items-end mt-5">
<div class="flex px-4 mt-4 w-full items-end mt-5" style="page-break-inside: avoid;">
<div class="w-1/2">
<p class="font-semibold">$terms_label</p>
$terms
</div>
</div>
<div class="mt-8 px-4 py-2 bg-gray-900 text-white">
<div class="mt-8 px-4 py-2 bg-gray-900 text-white" style="page-break-inside: avoid;">
<div class="w-1/2"></div>
<div class="w-auto flex justify-end">
<div class="w-56">
@ -136,8 +131,7 @@ class Modern
';
}
public function footer()
{
public function footer() {
return '
<div class="bg-orange-600 flex justify-between py-8 px-12" style="page-break-inside: avoid;">

View File

@ -28,10 +28,13 @@ use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Invoice\EmailInvoice;
use App\Jobs\Invoice\StoreInvoice;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Repositories\InvoiceRepository;
use App\Transformers\InvoiceTransformer;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
/**
* Class InvoiceController
@ -57,9 +60,11 @@ class InvoiceController extends BaseController {
* @param \App\Repositories\InvoiceRepository $invoice_repo The invoice repo
*/
public function __construct(InvoiceRepository $invoice_repo) {
parent::__construct();
$this->invoice_repo = $invoice_repo;
}
/**
@ -75,8 +80,8 @@ class InvoiceController extends BaseController {
* tags={"invoices"},
* summary="Gets a list of invoices",
* description="Lists invoices, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the invoices, these are handled by the InvoiceFilters class which defines the methods available",
*
* Query parameters can be added to performed more fine grained filtering of the invoices, these are handled by the InvoiceFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
@ -519,17 +524,17 @@ class InvoiceController extends BaseController {
* tags={"invoices"},
* summary="Performs a custom action on an invoice",
* description="Performs a custom action on an invoice.
The current range of actions are as follows
- clone_to_invoice
- clone_to_quote
- history
- delivery_note
- mark_paid
- download
- archive
- delete
- email",
*
* The current range of actions are as follows
* - clone_to_invoice
* - clone_to_quote
* - history
* - delivery_note
* - mark_paid
* - download
* - archive
* - delete
* - email",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
@ -649,11 +654,11 @@ class InvoiceController extends BaseController {
public function downloadPdf($invitation_key) {
$invitation = InvoiceInvitation::whereKey($invitation_key)->company()->first();
$invitation = $this->invoice_repo->getInvitationByKey($invitation_key);
$contact = $invitation->contact;
$invoice = $invitation->invoice;
$file_path = CreateInvoicePdf::dispatchNow($invoice, $invoice->company, $contact);
$file_path = $invoice->service()->getInvoicePdf(); //CreateInvoicePdf::dispatchNow($invoice, $invoice->company, $contact);
return response()->download($file_path);

View File

@ -39,27 +39,35 @@ class CreateInvoicePdf implements ShouldQueue {
public $company;
public $contact;
private $disk;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice, Company $company, ClientContact $contact) {
public function __construct(Invoice $invoice, Company $company, ClientContact $contact, $disk = 'public')
{
$this->invoice = $invoice;
$this->company = $company;
$this->contact = $contact;
$this->disk = $disk ?? config('filesystems.default');
}
public function handle() {
MultiDB::setDB($this->company->db);
App::setLocale($this->contact->preferredLocale());
$this->invoice->load('client');
$path = 'public/'.$this->invoice->client->client_hash.'/invoices/';
$file_path = $path.$this->invoice->number.'.pdf';
$path = $this->invoice->client->client_hash . '/invoices/';
$file_path = $path . $this->invoice->number . '.pdf';
$modern = new Modern();
$designer = new Designer($modern, $this->invoice->client->getSetting('invoice_variables'));
@ -74,9 +82,14 @@ class CreateInvoicePdf implements ShouldQueue {
//create pdf
$pdf = $this->makePdf(null, null, $html);
$path = Storage::put($file_path, $pdf);
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
\Log::error($instance);
return $path;
//$instance = Storage::disk($this->disk)->putFileAs($path, $pdf, $this->invoice->number . '.pdf');
//$instance = Storage::putFileAs($path, $pdf, $this->invoice->number . '.pdf');
return $instance;
}
/**

View File

@ -12,40 +12,37 @@
namespace App\Jobs\Invoice;
use App\Models\Invoice;
use App\Repositories\InvoiceRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InvoiceNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
class InvoiceNotification implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $invoice;
public $invoice;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice)
{
$this->invoice = $invoice;
}
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice) {
$this->invoice = $invoice;
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
/**
* Execute the job.
*
*
* @return void
*/
public function handle() {
//notification for the invoice.
//
//could mean a email, sms, slack, push
}
//notification for the invoice.
//
//could mean a email, sms, slack, push
}
}

View File

@ -47,7 +47,7 @@ class UploadFile implements ShouldQueue
public $entity;
public function __construct($file, $type, $user, $company, $entity, $disk = 'local')
public function __construct($file, $type, $user, $company, $entity, $disk = 'public')
{
$this->file = $file;
$this->type = $type;

25
app/Models/Design.php Normal file
View File

@ -0,0 +1,25 @@
<?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\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Design extends BaseModel
{
public function company()
{
return $this->belongsTo(Company::class);
}
}

View File

@ -342,9 +342,14 @@ class Invoice extends BaseModel
/** TODO// DOCUMENT THIS FUNCTIONALITY */
public function pdf_url()
{
$public_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
// $public_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
// $storage_path = 'public/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$public_path = $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$storage_path = $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
$storage_path = 'public/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
if (!Storage::exists($storage_path)) {
event(new InvoiceWasUpdated($this, $this->company));

View File

@ -131,4 +131,9 @@ class InvoiceRepository extends BaseRepository {
public function markSent(Invoice $invoice):?Invoice {
return $invoice->service()->markSent()->save();
}
public function getInvitationByKey($key)
{
return InvoiceInvitation::whereRaw("BINARY `key`= ?", [$key])->first();
}
}

View File

@ -0,0 +1,52 @@
<?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\Services\Invoice;
use App\Jobs\Invoice\CreateInvoicePdf;
use Illuminate\Support\Facades\Storage;
class GetInvoicePdf
{
public function __construct()
{
}
public function __invoke($invoice, $contact = null)
{
$path = $invoice->client->client_hash . '/invoices/';
$file_path = $path . $invoice->number . '.pdf';
$disk = config('filesystems.default');
$file = Storage::disk($disk)->exists($file_path);
if(!$contact)
$contact = $invoice->client->primary_contact()->first();
if(!$file)
{
$file_path = CreateInvoicePdf::dispatchNow($invoice, $invoice->company, $contact);
}
//return $file_path;
return Storage::disk($disk)->path($file_path);
}
}

View File

@ -17,6 +17,7 @@ use App\Services\Client\ClientService;
use App\Services\Invoice\ApplyNumber;
use App\Services\Invoice\ApplyPayment;
use App\Services\Invoice\CreateInvitations;
use App\Services\Invoice\GetInvoicePdf;
use App\Services\Invoice\MarkInvoicePaid;
use App\Services\Invoice\MarkSent;
use App\Services\Invoice\UpdateBalance;
@ -112,6 +113,22 @@ class InvoiceService
return $this;
}
public function getInvoicePdf()
{
$get_invoice_pdf = new GetInvoicePdf();
return $get_invoice_pdf($this->invoice);
}
public function markViewed()
{
$this->invoice->last_viewed = Carbon::now()->format('Y-m-d H:i');
@ -159,7 +176,6 @@ class InvoiceService
/**
* Saves the invoice
* @return Invoice object

View File

@ -11,6 +11,7 @@
namespace App\Utils\Traits;
use App\Exceptions\ModelNotFoundException;
use App\Libraries\MultiDB;
use Hashids\Hashids;
@ -69,6 +70,9 @@ trait MakesHash
// \Log::error($decoded_array);
if(!is_array($decoded_array))
throw new ModelNotFoundException("Resource not found", 1);
return $decoded_array[0];
} catch (\Exception $e) {
return response()->json(['error'=>'Invalid primary key'], 400);

View File

@ -13,7 +13,7 @@ return [
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
'default' => env('FILESYSTEM_DRIVER', 'public'),
/*
|--------------------------------------------------------------------------

View File

@ -1353,6 +1353,20 @@ class CreateUsersTable extends Migration
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade')->onUpdate('cascade');
});
Schema::create('designs', function ($table) {
$table->increments('id');
$table->unsignedInteger('user_id')->nullable();
$table->unsignedInteger('company_id')->nullable()->index();
$table->string('name');
$table->boolean('is_custom')->default(true);
$table->boolean('is_active')->default(true);
$table->mediumText('design')->nullable();
$table->timestamps(6);
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
});
}
/**

View File

@ -31,6 +31,7 @@ class DatabaseSeeder extends Seeder
$this->call('PaymentTypesSeeder');
$this->call('GatewayTypesSeeder');
$this->call('DateFormatsSeeder');
$this->call('DesignSeeder');
}
}

View File

@ -0,0 +1,40 @@
<?php
use App\Models\Bank;
use App\Models\Design;
use Illuminate\Database\Seeder;
class DesignSeeder extends Seeder
{
public function run()
{
Eloquent::unguard();
$this->createDesigns();
}
private function createDesigns()
{
$designs = [
['id' => 1, 'name' => 'Plain', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 2, 'name' => 'Clean', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 3, 'name' => 'Bold', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 4, 'name' => 'Modern', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 5, 'name' => 'Business', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 6, 'name' => 'Creative', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 7, 'name' => 'Elegant', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 8, 'name' => 'Hipster', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 9, 'name' => 'Playful', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
['id' => 10, 'name' => 'Photo', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
];
foreach($designs as $design) {
$d = Design::find($design['id']);
if(!$d)
Design::create($design);
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Tests\Integration;
use App\Jobs\Invoice\CreateInvoicePdf;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Services\Invoice\GetInvoicePdf
*/
class InvoiceUploadTest extends TestCase
{
use MockAccountData;
use DatabaseTransactions;
public function setUp() :void
{
parent::setUp();
$this->makeTestData();
}
public function testInvoiceUploadWorks()
{
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
$this->assertNotNull($this->invoice->service()->getInvoicePdf());
}
}

View File

@ -19,6 +19,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Tests\MockAccountData;
use Tests\TestCase;
@ -50,7 +51,7 @@ class UploadFileTest extends TestCase
$document = UploadFile::dispatchNow(
$image, UploadFile::IMAGE, $this->invoice->user, $this->invoice->company, $this->invoice
);
$this->assertNotNull($document);
}