mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
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:
parent
dee99b1a62
commit
be3ade65f1
27
app/Designs/AbstractDesign.php
Normal file
27
app/Designs/AbstractDesign.php
Normal 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
77
app/Designs/Custom.php
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;">
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
25
app/Models/Design.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
52
app/Services/Invoice/GetInvoicePdf.php
Normal file
52
app/Services/Invoice/GetInvoicePdf.php
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -13,7 +13,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DRIVER', 'local'),
|
||||
'default' => env('FILESYSTEM_DRIVER', 'public'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -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');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,6 +31,7 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call('PaymentTypesSeeder');
|
||||
$this->call('GatewayTypesSeeder');
|
||||
$this->call('DateFormatsSeeder');
|
||||
$this->call('DesignSeeder');
|
||||
|
||||
}
|
||||
}
|
||||
|
40
database/seeds/DesignSeeder.php
Normal file
40
database/seeds/DesignSeeder.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
39
tests/Integration/InvoiceUploadTest.php
Normal file
39
tests/Integration/InvoiceUploadTest.php
Normal 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());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user