2020-09-04 10:18:41 +02:00
< ? php
/**
2020-09-06 11:38:10 +02:00
* Invoice Ninja ( https :// invoiceninja . com ) .
2020-09-04 10:18:41 +02:00
*
* @ 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\PdfMaker ;
2020-10-28 11:10:49 +01:00
use App\Models\Quote ;
2020-09-04 10:18:41 +02:00
use App\Services\PdfMaker\Designs\Utilities\BaseDesign ;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers ;
2020-09-11 10:07:59 +02:00
use App\Utils\Number ;
2020-09-06 11:38:10 +02:00
use App\Utils\Traits\MakesInvoiceValues ;
2020-10-06 12:49:00 +02:00
use DOMDocument ;
2020-09-06 11:38:10 +02:00
use Illuminate\Support\Str ;
2020-09-04 10:18:41 +02:00
class Design extends BaseDesign
{
use MakesInvoiceValues , DesignHelpers ;
/** @var App\Models\Invoice || @var App\Models\Quote */
public $entity ;
2020-09-11 10:07:59 +02:00
/** @var App\Models\Client */
public $client ;
2020-09-04 10:18:41 +02:00
/** Global state of the design, @var array */
public $context ;
/** Type of entity => product||task */
public $type ;
/** Design string */
public $design ;
/** Construct options */
public $options ;
2020-09-04 13:17:30 +02:00
const BOLD = 'bold' ;
const BUSINESS = 'business' ;
const CLEAN = 'clean' ;
const CREATIVE = 'creative' ;
const ELEGANT = 'elegant' ;
const HIPSTER = 'hipster' ;
const MODERN = 'modern' ;
const PLAIN = 'plain' ;
const PLAYFUL = 'playful' ;
2020-09-08 13:21:07 +02:00
const CUSTOM = 'custom' ;
2020-09-04 10:18:41 +02:00
public function __construct ( string $design = null , array $options = [])
{
2020-09-04 13:17:30 +02:00
Str :: endsWith ( '.html' , $design ) ? $this -> design = $design : $this -> design = " { $design } .html " ;
2020-09-04 10:18:41 +02:00
$this -> options = $options ;
}
public function html () : ? string
{
2020-09-08 13:21:07 +02:00
if ( $this -> design == 'custom.html' ) {
return $this -> composeFromPartials (
$this -> options [ 'custom_partials' ]
);
}
2020-09-04 10:18:41 +02:00
$path = isset ( $this -> options [ 'custom_path' ])
? $this -> options [ 'custom_path' ]
: config ( 'ninja.designs.base_path' );
return file_get_contents (
2020-09-09 14:47:26 +02:00
$path . $this -> design
2020-09-04 10:18:41 +02:00
);
}
public function elements ( array $context , string $type = 'product' ) : array
{
$this -> context = $context ;
$this -> type = $type ;
$this -> setup ();
return [
'company-details' => [
'id' => 'company-details' ,
'elements' => $this -> companyDetails (),
],
'company-address' => [
'id' => 'company-address' ,
'elements' => $this -> companyAddress (),
],
'client-details' => [
'id' => 'client-details' ,
'elements' => $this -> clientDetails (),
],
'entity-details' => [
'id' => 'entity-details' ,
'elements' => $this -> entityDetails (),
],
2020-11-09 14:30:50 +01:00
'delivery-note-table' => [
'id' => 'delivery-note-table' ,
'elements' => $this -> deliveryNoteTable (),
],
2020-09-04 10:18:41 +02:00
'product-table' => [
'id' => 'product-table' ,
'elements' => $this -> productTable (),
],
2020-11-04 14:56:08 +01:00
'task-table' => [
'id' => 'task-table' ,
'elements' => $this -> taskTable (),
],
2020-11-04 11:23:06 +01:00
'table-totals' => [
'id' => 'table-totals' ,
'elements' => $this -> tableTotals (),
2020-09-08 14:30:20 +02:00
],
2020-09-04 10:18:41 +02:00
'footer-elements' => [
'id' => 'footer' ,
'elements' => [
$this -> sharedFooterElements (),
],
],
];
}
public function companyDetails ()
{
$variables = $this -> context [ 'pdf_variables' ][ 'company_details' ];
$elements = [];
foreach ( $variables as $variable ) {
2020-10-27 23:51:39 +01:00
$elements [] = [ 'element' => 'p' , 'content' => $variable , 'show_empty' => false ];
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
public function companyAddress () : array
{
$variables = $this -> context [ 'pdf_variables' ][ 'company_address' ];
$elements = [];
foreach ( $variables as $variable ) {
2020-10-27 23:51:39 +01:00
$elements [] = [ 'element' => 'p' , 'content' => $variable , 'show_empty' => false ];
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
public function clientDetails () : array
{
$elements = [];
2020-11-09 14:30:50 +01:00
if ( $this -> type == 'delivery_note' ) {
$elements = [
[ 'element' => 'p' , 'content' => $this -> entity -> client -> name , 'show_empty' => false ],
[ 'element' => 'p' , 'content' => $this -> entity -> client -> shipping_address1 , 'show_empty' => false ],
[ 'element' => 'p' , 'content' => $this -> entity -> client -> shipping_address2 , 'show_empty' => false ],
[ 'element' => 'p' , 'content' => " { $this -> entity -> client -> shipping_city } { $this -> entity -> client -> shipping_state } { $this -> entity -> client -> shipping_postal_code } " , 'show_empty' => false ],
[ 'element' => 'p' , 'content' => optional ( $this -> entity -> client -> shipping_country ) -> name , 'show_empty' => false ],
];
if ( ! is_null ( $this -> context [ 'contact' ])) {
$elements [] = [ 'element' => 'p' , 'content' => $this -> context [ 'contact' ] -> email , 'show_empty' => false ];
}
return $elements ;
}
$variables = $this -> context [ 'pdf_variables' ][ 'client_details' ];
2020-09-04 10:18:41 +02:00
foreach ( $variables as $variable ) {
2020-10-27 23:51:39 +01:00
$elements [] = [ 'element' => 'p' , 'content' => $variable , 'show_empty' => false ];
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
public function entityDetails () : array
{
$variables = $this -> context [ 'pdf_variables' ][ 'invoice_details' ];
2020-10-28 11:10:49 +01:00
if ( $this -> entity instanceof Quote ) {
2020-09-04 10:18:41 +02:00
$variables = $this -> context [ 'pdf_variables' ][ 'quote_details' ];
}
$elements = [];
foreach ( $variables as $variable ) {
2020-10-20 12:46:08 +02:00
$_variable = explode ( '.' , $variable )[ 1 ];
2020-10-20 13:01:07 +02:00
$_customs = [ 'custom1' , 'custom2' , 'custom3' , 'custom4' ];
2020-10-27 23:51:39 +01:00
2020-10-20 13:01:07 +02:00
if ( in_array ( $_variable , $_customs )) {
2020-10-20 12:46:08 +02:00
$elements [] = [ 'element' => 'tr' , 'elements' => [
[ 'element' => 'th' , 'content' => $variable . '_label' ],
[ 'element' => 'th' , 'content' => $variable ],
]];
} else {
$elements [] = [ 'element' => 'tr' , 'properties' => [ 'hidden' => $this -> entityVariableCheck ( $variable )], 'elements' => [
[ 'element' => 'th' , 'content' => $variable . '_label' ],
[ 'element' => 'th' , 'content' => $variable ],
]];
}
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
2020-11-09 14:30:50 +01:00
public function deliveryNoteTable () : array
{
2020-11-25 15:19:52 +01:00
if ( $this -> type !== 'delivery_note' ) {
return [];
}
2020-11-09 16:10:47 +01:00
2020-11-09 14:30:50 +01:00
$elements = [
[ 'element' => 'thead' , 'elements' => [
[ 'element' => 'th' , 'content' => '$item_label' ],
[ 'element' => 'th' , 'content' => '$description_label' ],
[ 'element' => 'th' , 'content' => '$product.quantity_label' ],
]],
[ 'element' => 'tbody' , 'elements' => $this -> buildTableBody ( 'delivery_note' )],
];
return $elements ;
}
2020-11-04 14:56:08 +01:00
/**
* Parent method for building products table .
2020-11-25 15:19:52 +01:00
*
* @ return array
2020-11-04 14:56:08 +01:00
*/
2020-09-04 10:18:41 +02:00
public function productTable () : array
{
2020-11-04 14:56:08 +01:00
$product_items = collect ( $this -> entity -> line_items ) -> filter ( function ( $item ) {
return $item -> type_id == 1 ;
});
2020-11-06 13:12:51 +01:00
if ( count ( $product_items ) == 0 ) {
2020-11-04 14:56:08 +01:00
return [];
}
2020-11-09 14:30:50 +01:00
if ( $this -> type == 'delivery_note' ) {
return [];
}
2020-09-04 10:18:41 +02:00
return [
2020-11-04 14:56:08 +01:00
[ 'element' => 'thead' , 'elements' => $this -> buildTableHeader ( 'product' )],
[ 'element' => 'tbody' , 'elements' => $this -> buildTableBody ( '$product' )],
];
}
/**
* Parent method for building tasks table .
2020-11-25 15:19:52 +01:00
*
* @ return array
2020-11-04 14:56:08 +01:00
*/
public function taskTable () : array
{
$task_items = collect ( $this -> entity -> line_items ) -> filter ( function ( $item ) {
2020-11-09 16:10:47 +01:00
return $item -> type_id == 2 ;
2020-11-04 14:56:08 +01:00
});
2020-11-06 13:12:51 +01:00
if ( count ( $task_items ) == 0 ) {
2020-11-04 14:56:08 +01:00
return [];
}
2020-11-09 14:30:50 +01:00
if ( $this -> type == 'delivery_note' ) {
return [];
}
2020-11-04 14:56:08 +01:00
return [
[ 'element' => 'thead' , 'elements' => $this -> buildTableHeader ( 'task' )],
[ 'element' => 'tbody' , 'elements' => $this -> buildTableBody ( '$task' )],
2020-09-04 10:18:41 +02:00
];
}
2020-11-04 14:56:08 +01:00
/**
* Generate the structure of table headers . ( < thead /> )
2020-11-25 15:19:52 +01:00
*
2020-11-04 14:56:08 +01:00
* @ param string $type " product " or " task "
2020-11-25 15:19:52 +01:00
* @ return array
2020-11-04 14:56:08 +01:00
*/
public function buildTableHeader ( string $type ) : array
2020-09-04 10:18:41 +02:00
{
2020-11-04 14:56:08 +01:00
$this -> processTaxColumns ( $type );
2020-09-04 10:18:41 +02:00
$elements = [];
2020-12-01 15:18:48 +01:00
// Some of column can be aliased. This is simple workaround for these.
$aliases = [
'$task.product_key' => '$task.service' ,
];
2020-11-04 14:56:08 +01:00
foreach ( $this -> context [ 'pdf_variables' ][ " { $type } _columns " ] as $column ) {
2020-12-01 15:18:48 +01:00
if ( array_key_exists ( $column , $aliases )) {
$elements [] = [ 'element' => 'th' , 'content' => $aliases [ $column ] . '_label' ];
} else {
$elements [] = [ 'element' => 'th' , 'content' => $column . '_label' ];
}
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
2020-11-04 14:56:08 +01:00
/**
* Generate the structure of table body . ( < tbody /> )
2020-11-25 15:19:52 +01:00
*
2020-11-04 14:56:08 +01:00
* @ param string $type " $product " or " $task "
2020-11-25 15:19:52 +01:00
* @ return array
2020-11-04 14:56:08 +01:00
*/
public function buildTableBody ( string $type ) : array
2020-09-04 10:18:41 +02:00
{
$elements = [];
2020-11-04 14:56:08 +01:00
$items = $this -> transformLineItems ( $this -> entity -> line_items , $type );
2020-09-04 10:18:41 +02:00
if ( count ( $items ) == 0 ) {
return [];
}
2020-11-25 15:19:52 +01:00
if ( $type == 'delivery_note' ) {
2020-11-09 14:30:50 +01:00
foreach ( $items as $row ) {
$element = [ 'element' => 'tr' , 'elements' => []];
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ 'delivery_note.product_key' ]];
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ 'delivery_note.notes' ]];
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ 'delivery_note.quantity' ]];
2020-12-01 15:18:48 +01:00
2020-11-09 14:30:50 +01:00
$elements [] = $element ;
}
return $elements ;
}
2020-09-04 10:18:41 +02:00
foreach ( $items as $row ) {
$element = [ 'element' => 'tr' , 'elements' => []];
2020-10-06 12:49:00 +02:00
if (
2020-11-04 14:56:08 +01:00
array_key_exists ( $type , $this -> context ) &&
! empty ( $this -> context [ $type ]) &&
! is_null ( $this -> context [ $type ])
2020-10-06 12:49:00 +02:00
) {
$document = new DOMDocument ();
2020-11-04 14:56:08 +01:00
$document -> loadHTML ( $this -> context [ $type ], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
2020-10-06 12:49:00 +02:00
$td = $document -> getElementsByTagName ( 'tr' ) -> item ( 0 );
if ( $td ) {
foreach ( $td -> childNodes as $child ) {
if ( $child -> nodeType !== 1 ) {
continue ;
}
if ( $child -> tagName !== 'td' ) {
continue ;
}
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => strtr ( $child -> nodeValue , $row )];
}
}
} else {
2020-11-04 14:56:08 +01:00
$_type = Str :: startsWith ( $type , '$' ) ? ltrim ( $type , '$' ) : $type ;
foreach ( $this -> context [ 'pdf_variables' ][ " { $_type } _columns " ] as $key => $cell ) {
// We want to keep aliases like these:
// $task.cost => $task.rate
// $task.quantity => $task.hours
2020-11-06 13:12:51 +01:00
2020-11-04 14:56:08 +01:00
if ( $cell == '$task.rate' ) {
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ '$task.cost' ]];
2020-11-25 15:19:52 +01:00
} elseif ( $cell == '$task.hours' ) {
2020-11-04 14:56:08 +01:00
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ '$task.quantity' ]];
2020-11-25 15:19:52 +01:00
} elseif ( $cell == '$task.description' ) {
2020-11-06 13:12:51 +01:00
$_element = [ 'element' => 'td' , 'content' => '' , 'elements' => [
[ 'element' => 'span' , 'content' => $row [ $cell ]],
]];
foreach ( $this -> getTaskTimeLogs ( $row ) as $log ) {
$_element [ 'elements' ][] = [ 'element' => 'span' , 'content' => $log , 'properties' => [ 'class' => 'task-duration' ]];
}
$element [ 'elements' ][] = $_element ;
2020-11-04 14:56:08 +01:00
} else {
$element [ 'elements' ][] = [ 'element' => 'td' , 'content' => $row [ $cell ]];
}
2020-10-06 12:49:00 +02:00
}
2020-09-04 10:18:41 +02:00
}
$elements [] = $element ;
}
return $elements ;
}
2020-11-04 11:23:06 +01:00
public function tableTotals () : array
2020-09-04 10:18:41 +02:00
{
2020-11-09 14:30:50 +01:00
if ( $this -> type == 'delivery_note' ) {
return [];
}
2020-09-04 10:18:41 +02:00
$variables = $this -> context [ 'pdf_variables' ][ 'total_columns' ];
$elements = [
2020-09-08 14:30:20 +02:00
[ 'element' => 'div' , 'elements' => [
2020-11-04 11:23:06 +01:00
[ 'element' => 'span' , 'content' => '$entity.public_notes' , 'properties' => [ 'data-element' => 'total-table-public-notes-label' ]],
2020-09-04 10:18:41 +02:00
]],
];
2020-09-09 14:47:26 +02:00
foreach ([ 'discount' , 'custom_surcharge1' , 'custom_surcharge2' , 'custom_surcharge3' , 'custom_surcharge4' ] as $property ) {
$variable = sprintf ( '%s%s' , '$' , $property );
if (
2020-09-11 16:46:49 +02:00
! is_null ( $this -> entity -> { $property }) &&
! empty ( $this -> entity -> { $property }) &&
$this -> entity -> { $property } != 0
2020-09-09 14:47:26 +02:00
) {
continue ;
}
$variables = array_filter ( $variables , function ( $m ) use ( $variable ) {
return $m != $variable ;
});
}
2020-09-04 10:18:41 +02:00
foreach ( $variables as $variable ) {
2020-09-11 10:07:59 +02:00
if ( $variable == '$total_taxes' ) {
$taxes = $this -> entity -> calc () -> getTotalTaxMap ();
if ( ! $taxes ) {
continue ;
}
foreach ( $taxes as $tax ) {
$elements [] = [ 'element' => 'div' , 'elements' => [
[ 'element' => 'span' , 'content' => 'This is placeholder for the 3rd fraction of element.' , 'properties' => [ 'style' => 'opacity: 0%' ]], // Placeholder for fraction of element (3fr)
[ 'element' => 'span' , 'content' , 'content' => $tax [ 'name' ]],
[ 'element' => 'span' , 'content' , 'content' => Number :: formatMoney ( $tax [ 'total' ], $this -> context [ 'client' ])],
]];
}
} elseif ( $variable == '$line_taxes' ) {
$taxes = $this -> entity -> calc () -> getTaxMap ();
if ( ! $taxes ) {
continue ;
}
foreach ( $taxes as $tax ) {
$elements [] = [ 'element' => 'div' , 'elements' => [
[ 'element' => 'span' , 'content' => 'This is placeholder for the 3rd fraction of element.' , 'properties' => [ 'style' => 'opacity: 0%' ]], // Placeholder for fraction of element (3fr)
[ 'element' => 'span' , 'content' , 'content' => $tax [ 'name' ]],
[ 'element' => 'span' , 'content' , 'content' => Number :: formatMoney ( $tax [ 'total' ], $this -> context [ 'client' ])],
]];
}
} else {
$elements [] = [ 'element' => 'div' , 'elements' => [
[ 'element' => 'span' , 'content' => 'This is placeholder for the 3rd fraction of element.' , 'properties' => [ 'style' => 'opacity: 0%' ]], // Placeholder for fraction of element (3fr)
[ 'element' => 'span' , 'content' => $variable . '_label' ],
[ 'element' => 'span' , 'content' => $variable ],
]];
2020-09-04 13:17:30 +02:00
}
2020-09-04 10:18:41 +02:00
}
return $elements ;
}
}