diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 05a78f07450c..4eb8e06b2e67 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -643,6 +643,17 @@ class CompanySettings extends BaseSettings '$client.phone', '$contact.email', ], + 'vendor_details' => [ + '$vendor.name', + '$vendor.number', + '$vendor.vat_number', + '$vendor.address1', + '$vendor.address2', + '$vendor.city_state_postal', + '$vendor.country', + '$vendor.phone', + '$contact.email', + ], 'company_details' => [ '$company.name', '$company.id_number', diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index bf4664662606..a15e8444f592 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -33,9 +33,10 @@ use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\Traits\NumberFormatter; -use App\Utils\Traits\Pdf\PageNumbering; use App\Utils\Traits\Pdf\PDF; +use App\Utils\Traits\Pdf\PageNumbering; use App\Utils\Traits\Pdf\PdfMaker; +use App\Utils\VendorHtmlEngine; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -120,7 +121,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue if(!$design) $design = Design::find(2); - $html = new HtmlEngine($this->invitation); + $html = new VendorHtmlEngine($this->invitation); if ($design->is_custom) { $options = [ diff --git a/app/Models/PurchaseOrderInvitation.php b/app/Models/PurchaseOrderInvitation.php index c0601653e559..beff77059650 100644 --- a/app/Models/PurchaseOrderInvitation.php +++ b/app/Models/PurchaseOrderInvitation.php @@ -4,6 +4,7 @@ namespace App\Models; +use App\Utils\Ninja; use App\Utils\Traits\Inviteable; use App\Utils\Traits\MakesDates; use Carbon\Carbon; @@ -77,5 +78,30 @@ class PurchaseOrderInvitation extends BaseModel $this->save(); } + public function getPortalLink() :string + { + + if(Ninja::isHosted()) + $domain = $this->company->domain(); + else + $domain = config('ninja.app_url'); + + switch ($this->company->portal_mode) { + case 'subdomain': + return $domain.'/vendor/'; + break; + case 'iframe': + return $domain.'/vendor/'; + break; + case 'domain': + return $domain.'/vendor/'; + break; + + default: + return ''; + break; + } + + } } diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 3a23d5d7b8bf..2f63a7d5644d 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -13,9 +13,11 @@ namespace App\Models; use App\DataMapper\CompanySettings; use App\Models\Presenters\VendorPresenter; +use App\Utils\Traits\AppSetup; use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\Eloquent\SoftDeletes; use Laracasts\Presenter\PresentableTrait; +use Illuminate\Support\Facades\Cache; class Vendor extends BaseModel { @@ -23,7 +25,8 @@ class Vendor extends BaseModel use Filterable; use GeneratesCounter; use PresentableTrait; - + use AppSetup; + protected $fillable = [ 'name', 'assigned_user_id', @@ -96,6 +99,19 @@ class Vendor extends BaseModel return $this->hasMany(Activity::class); } + public function currency() + { + $currencies = Cache::get('currencies'); + + if(!$currencies) + $this->buildCache(true); + + return $currencies->filter(function ($item) { + return $item->id == $this->currency_id; + })->first(); + } + + public function company() { return $this->belongsTo(Company::class); diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 6c349cb07833..d8dc215c7cdd 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -69,6 +69,8 @@ class Design extends BaseDesign const DELIVERY_NOTE = 'delivery_note'; const STATEMENT = 'statement'; + const PURCHASE_ORDER = 'purchase_order'; + public function __construct(string $design = null, array $options = []) { @@ -113,6 +115,10 @@ class Design extends BaseDesign 'id' => 'client-details', 'elements' => $this->clientDetails(), ], + 'vendor-details' => [ + 'id' => 'vendor-details', + 'elements' => $this->vendorDetails(), + ], 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->entityDetails(), @@ -188,6 +194,19 @@ class Design extends BaseDesign return $elements; } + public function vendorDetails(): array + { + $elements = []; + + $variables = $this->context['pdf_variables']['vendor_details']; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + } + + return $elements; + } + public function clientDetails(): array { $elements = []; diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index ce0312b3fed3..8623a452cfce 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -104,15 +104,15 @@ class Helpers * Process reserved keywords on PDF. * * @param string $value - * @param Client $client + * @param Client|Company $entity * @return null|string */ - public static function processReservedKeywords(?string $value, Client $client): ?string + public static function processReservedKeywords(?string $value, $entity): ?string { if(!$value) return ''; - Carbon::setLocale($client->locale()); + Carbon::setLocale($entity->locale()); $replacements = [ 'literal' => [ @@ -121,21 +121,21 @@ class Helpers ':QUARTER' => 'Q' . now()->quarter, ':WEEK_BEFORE' => \sprintf( '%s %s %s', - Carbon::now()->subDays(7)->translatedFormat($client->date_format()), + Carbon::now()->subDays(7)->translatedFormat($entity->date_format()), ctrans('texts.to'), - Carbon::now()->translatedFormat($client->date_format()) + Carbon::now()->translatedFormat($entity->date_format()) ), ':WEEK_AHEAD' => \sprintf( '%s %s %s', - Carbon::now()->addDays(7)->translatedFormat($client->date_format()), + Carbon::now()->addDays(7)->translatedFormat($entity->date_format()), ctrans('texts.to'), - Carbon::now()->addDays(14)->translatedFormat($client->date_format()) + Carbon::now()->addDays(14)->translatedFormat($entity->date_format()) ), ':WEEK' => \sprintf( '%s %s %s', - Carbon::now()->translatedFormat($client->date_format()), + Carbon::now()->translatedFormat($entity->date_format()), ctrans('texts.to'), - Carbon::now()->addDays(7)->translatedFormat($client->date_format()) + Carbon::now()->addDays(7)->translatedFormat($entity->date_format()) ), ], 'raw' => [ diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php new file mode 100644 index 000000000000..955a12ac507c --- /dev/null +++ b/app/Utils/VendorHtmlEngine.php @@ -0,0 +1,781 @@ +invitation = $invitation; + + $this->entity_string = $this->resolveEntityString(); + + $this->entity = $invitation->{$this->entity_string}; + + $this->company = $invitation->company; + + $this->contact = $invitation->contact->load('vendor'); + + $this->vendor = $this->contact->vendor->load('company','country'); + + $this->entity->load('vendor'); + + $this->settings = $this->company->settings; + + $this->entity_calc = $this->entity->calc(); + + $this->helpers = new Helpers(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private function resolveEntityString() + { + switch ($this->invitation) { + case ($this->invitation instanceof InvoiceInvitation): + return 'invoice'; + break; + case ($this->invitation instanceof CreditInvitation): + return 'credit'; + break; + case ($this->invitation instanceof QuoteInvitation): + return 'quote'; + break; + case ($this->invitation instanceof RecurringInvoiceInvitation): + return 'recurring_invoice'; + break; + case ($this->invitation instanceof PurchaseOrderInvitation): + return 'purchase_order'; + break; + default: + # code... + break; + } + } + + public function buildEntityDataArray() :array + { + if (! $this->vendor->currency) { + throw new Exception(debug_backtrace()[1]['function'], 1); + exit; + } + + App::forgetInstance('translator'); + $t = app('translator'); + App::setLocale($this->company->locale()); + $t->replace(Ninja::transformTranslations($this->settings)); + + $data = []; + $data['$global_margin'] = ['value' => '6.35mm', 'label' => '']; + $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->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')]; + + $data['$invoice.date'] = &$data['$date']; + $data['$invoiceDate'] = &$data['$date']; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + + $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + + $data['$dueDate'] = &$data['$due_date']; + + $data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')]; + $data['$invoice.due_date'] = &$data['$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['$poNumber'] = &$data['$invoice.po_number']; + $data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.date')]; + $data['$invoice.datetime'] = &$data['$entity.datetime']; + $data['$quote.datetime'] = &$data['$entity.datetime']; + $data['$credit.datetime'] = &$data['$entity.datetime']; + $data['$payment_button'] = ['value' => ''.ctrans('texts.pay_now').'', 'label' => ctrans('texts.pay_now')]; + $data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')]; + + + if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { + $data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')]; + $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; + $data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')]; + $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')]; + $data['$terms'] = &$data['$entity.terms']; + $data['$view_link'] = ['value' => ''.ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')]; + $data['$viewLink'] = &$data['$view_link']; + $data['$viewButton'] = &$data['$view_link']; + $data['$view_button'] = &$data['$view_link']; + $data['$paymentButton'] = &$data['$payment_button']; + $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')]; + + if($this->entity->project) { + $data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')]; + $data['$invoice.project'] = &$data['$project.name']; + } + + if($this->entity->vendor) { + $data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')]; + } + } + + $data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>'']; + + $data['$entity_number'] = &$data['$number']; + $data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->vendor) ?: ' ', 'label' => ctrans('texts.discount')]; + $data['$discount'] = &$data['$invoice.discount']; + $data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')]; + $data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')]; + + if($this->entity->uses_inclusive_taxes) + $data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; + else + $data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; + + $data['$invoice.subtotal'] = &$data['$subtotal']; + + if ($this->entity->partial > 0) { + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')]; + $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; + $data['$amount_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + + } else { + + if($this->entity->status_id == 1){ + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')]; + $data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.balance_due')]; + $data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')]; + } + else{ + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')]; + $data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')]; + $data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')]; + } + } + + $data['$quote.balance_due'] = &$data['$balance_due']; + $data['$invoice.balance_due'] = &$data['$balance_due']; + + + if ($this->entity_string == 'credit') { + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.credit_balance')]; + $data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')]; + } + + // $data['$balance_due'] = $data['$balance_due']; + $data['$outstanding'] = &$data['$balance_due']; + $data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')]; + $data['$partial'] = &$data['$partial_due']; + + $data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.total')]; + $data['$amount'] = &$data['$total']; + $data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')]; + $data['$quote.total'] = &$data['$total']; + $data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.invoice_total')]; + $data['$invoice_total_raw'] = ['value' => $this->entity_calc->getTotal(), 'label' => ctrans('texts.invoice_total')]; + $data['$invoice.amount'] = &$data['$total']; + $data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.quote_total')]; + $data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.credit_total')]; + $data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')]; + $data['$credit.total'] = &$data['$credit.total']; + $data['$credit.po_number'] = &$data['$invoice.po_number']; + $data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.credit_date')]; + $data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->vendor) ?: ' ', 'label' => ctrans('texts.balance')]; + $data['$credit.balance'] = &$data['$balance']; + + $data['$invoice.balance'] = &$data['$balance']; + $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->vendor) ?: ' ', 'label' => ctrans('texts.taxes')]; + $data['$invoice.taxes'] = &$data['$taxes']; + + $data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')]; + $data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')]; + $data['$user.last_name'] = ['value' => $this->entity->user->last_name, 'label' => ctrans('texts.last_name')]; + $data['$created_by_user'] = &$data['$user.name']; + $data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')]; + + $data['$entity.public_notes'] = &$data['$invoice.public_notes']; + $data['$public_notes'] = &$data['$invoice.public_notes']; + $data['$notes'] = &$data['$public_notes']; + + $data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")]; + $data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")]; + + => ctrans('texts.valid_until')]; + $data['$valid_until'] = &$data['$quote.valid_until']; + $data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.credit_amount')]; + $data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$purchase_order.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order1', $this->entity->custom_value1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order1')]; + $data['$purchase_order.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order2', $this->entity->custom_value2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order2')]; + $data['$purchase_order.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order3', $this->entity->custom_value3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order3')]; + $data['$purchase_order.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order4', $this->entity->custom_value4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order4')]; + + $data['$credit_number'] = &$data['$number']; + $data['$credit_no'] = &$data['$number']; + $data['$credit.credit_no'] = &$data['$number']; + + $data['$invoice_no'] = &$data['$number']; + $data['$invoice.invoice_no'] = &$data['$number']; + $data['$vendor1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor1', $this->vendor->custom_value1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor1')]; + $data['$vendor2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor2', $this->vendor->custom_value2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor2')]; + $data['$vendor3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor3', $this->vendor->custom_value3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor3')]; + $data['$vendor4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor4', $this->vendor->custom_value4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor4')]; + $data['$vendor.custom1'] = &$data['$vendor1']; + $data['$vendor.custom2'] = &$data['$vendor2']; + $data['$vendor.custom3'] = &$data['$vendor3']; + $data['$vendor.custom4'] = &$data['$vendor4']; + $data['$address1'] = ['value' => $this->vendor->address1 ?: ' ', 'label' => ctrans('texts.address1')]; + $data['$address2'] = ['value' => $this->vendor->address2 ?: ' ', 'label' => ctrans('texts.address2')]; + $data['$id_number'] = ['value' => $this->vendor->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; + $data['$vendor.number'] = ['value' => $this->vendor->number ?: ' ', 'label' => ctrans('texts.number')]; + $data['$vat_number'] = ['value' => $this->vendor->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; + $data['$website'] = ['value' => $this->vendor->present()->website() ?: ' ', 'label' => ctrans('texts.website')]; + $data['$phone'] = ['value' => $this->vendor->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')]; + $data['$country'] = ['value' => isset($this->vendor->country->name) ? ctrans('texts.country_' . $this->vendor->country->name) : '', 'label' => ctrans('texts.country')]; + $data['$country_2'] = ['value' => isset($this->vendor->country) ? $this->vendor->country->iso_3166_2 : '', 'label' => ctrans('texts.country')]; + $data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')]; + + if(str_contains($data['$email']['value'], 'example.com')) + $data['$email'] = ['value' => '', 'label' => ctrans('texts.email')]; + + $data['$vendor_name'] = ['value' => $this->vendor->present()->name() ?: ' ', 'label' => ctrans('texts.vendor_name')]; + $data['$vendor.name'] = &$data['$vendor_name']; + $data['$vendor'] = &$data['$vendor_name']; + + $data['$vendor.address1'] = &$data['$address1']; + $data['$vendor.address2'] = &$data['$address2']; + $data['$vendor_address'] = ['value' => $this->vendor->present()->address() ?: ' ', 'label' => ctrans('texts.address')]; + $data['$vendor.address'] = &$data['$vendor_address']; + $data['$vendor.postal_code'] = ['value' => $this->vendor->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')]; + $data['$vendor.public_notes'] = ['value' => $this->vendor->public_notes ?: ' ', 'label' => ctrans('texts.notes')]; + $data['$vendor.city'] = ['value' => $this->vendor->city ?: ' ', 'label' => ctrans('texts.city')]; + $data['$vendor.state'] = ['value' => $this->vendor->state ?: ' ', 'label' => ctrans('texts.state')]; + $data['$vendor.id_number'] = &$data['$id_number']; + $data['$vendor.vat_number'] = &$data['$vat_number']; + $data['$vendor.website'] = &$data['$website']; + $data['$vendor.phone'] = &$data['$phone']; + $data['$city_state_postal'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, $this->vendor->state, $this->vendor->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$vendor.city_state_postal'] = &$data['$city_state_postal']; + $data['$postal_city_state'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, $this->vendor->state, $this->vendor->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$vendor.postal_city_state'] = &$data['$postal_city_state']; + $data['$vendor.country'] = &$data['$country']; + $data['$vendor.email'] = &$data['$email']; + + $data['$vendor.billing_address'] = &$data['$vendor_address']; + $data['$vendor.billing_address1'] = &$data['$vendor.address1']; + $data['$vendor.billing_address2'] = &$data['$vendor.address2']; + $data['$vendor.billing_city'] = &$data['$vendor.city']; + $data['$vendor.billing_state'] = &$data['$vendor.state']; + $data['$vendor.billing_postal_code'] = &$data['$vendor.postal_code']; + $data['$vendor.billing_country'] = &$data['$vendor.country']; + + $data['$vendor.currency'] = ['value' => $this->vendor->currency()->code, 'label' => '']; + + $data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->vendor), 'label' => ctrans('texts.paid_to_date')]; + + $data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')]; + $data['$contact'] = &$data['$contact.full_name']; + + $data['$contact.email'] = &$data['$email']; + $data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')]; + + $data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->vendor->present()->name(), 'label' => ctrans('texts.contact_name')]; + $data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')]; + $data['$firstName'] = &$data['$contact.first_name']; + + $data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')]; + + $data['$contact.custom1'] = ['value' => isset($this->contact) ? $this->contact->custom_value1 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact1')]; + $data['$contact.custom2'] = ['value' => isset($this->contact) ? $this->contact->custom_value2 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact2')]; + $data['$contact.custom3'] = ['value' => isset($this->contact) ? $this->contact->custom_value3 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact3')]; + $data['$contact.custom4'] = ['value' => isset($this->contact) ? $this->contact->custom_value4 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact4')]; + + $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->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')]; + $data['$account'] = &$data['$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.country_2'] = ['value' => $this->getCountryCode(), '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')]; + + $data['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => '']; + $data['$emailSignature'] = &$data['$signature']; + + $logo = $this->company->present()->logo_base64($this->settings); + + $data['$company.logo'] = ['value' => $logo ?: ' ', 'label' => ctrans('texts.logo')]; + $data['$company_logo'] = &$data['$company.logo']; + $data['$company1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')]; + $data['$company2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company2', $this->settings->custom_value2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company2')]; + $data['$company3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company3', $this->settings->custom_value3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company3')]; + $data['$company4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company4', $this->settings->custom_value4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')]; + + $data['$company.custom1'] = &$data['$company1']; + $data['$company.custom2'] = &$data['$company2']; + $data['$company.custom3'] = &$data['$company3']; + $data['$company.custom4'] = &$data['$company4']; + + $data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')]; + $data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')]; + $data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')]; + $data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')]; + + $data['$product.item'] = ['value' => '', 'label' => ctrans('texts.item')]; + $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.description'] = ['value' => '', 'label' => ctrans('texts.description')]; + $data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_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['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')]; + $data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')]; + $data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')]; + $data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')]; + $data['$product.product2'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product2')]; + $data['$product.product3'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product3')]; + $data['$product.product4'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product4')]; + + $data['$task.date'] = ['value' => '', 'label' => ctrans('texts.date')]; + $data['$task.discount'] = ['value' => '', 'label' => ctrans('texts.discount')]; + $data['$task.service'] = ['value' => '', 'label' => ctrans('texts.service')]; + $data['$task.description'] = ['value' => '', 'label' => ctrans('texts.description')]; + $data['$task.rate'] = ['value' => '', 'label' => ctrans('texts.rate')]; + $data['$task.cost'] = ['value' => '', 'label' => ctrans('texts.rate')]; + $data['$task.hours'] = ['value' => '', 'label' => ctrans('texts.hours')]; + $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['$task.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')]; + $data['$task.service'] = ['value' => '', 'label' => ctrans('texts.service')]; + $data['$task.task1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task1')]; + $data['$task.task2'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task2')]; + $data['$task.task3'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task3')]; + $data['$task.task4'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task4')]; + + if ($this->settings->signature_on_pdf) { + $data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')]; + } else { + $data['$contact.signature'] = ['value' => '', 'label' => '']; + } + + $data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')]; + $data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; + $data['$to'] = ['value' => '', 'label' => ctrans('texts.to')]; + $data['$details'] = ['value' => '', 'label' => ctrans('texts.details')]; + + $data['_rate1'] = ['value' => '', 'label' => ctrans('texts.tax')]; + $data['_rate2'] = ['value' => '', 'label' => ctrans('texts.tax')]; + $data['_rate3'] = ['value' => '', 'label' => ctrans('texts.tax')]; + + $data['$font_size'] = ['value' => $this->settings->font_size . 'px', 'label' => '']; + $data['$font_name'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['name'], 'label' => '']; + $data['$font_url'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['url'], 'label' => '']; + + $data['$invoiceninja.whitelabel'] = ['value' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', 'label' => '']; + + $data['$primary_color'] = ['value' => $this->settings->primary_color, 'label' => '']; + $data['$secondary_color'] = ['value' => $this->settings->secondary_color, 'label' => '']; + + $data['$item'] = ['value' => '', 'label' => ctrans('texts.item')]; + $data['$description'] = ['value' => '', 'label' => ctrans('texts.description')]; + + $data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer), $this->company), 'label' => '']; + $data['$footer'] = &$data['$entity_footer']; + + $data['$page_size'] = ['value' => $this->settings->page_size, 'label' => '']; + $data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => '']; + + $data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => '']; + $data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => '']; + $data['$auto_bill'] = &$data['$autoBill']; + + /*Payment Aliases*/ + $data['$paymentLink'] = &$data['$payment_link']; + $data['$payment_url'] = &$data['$payment_link']; + $data['$portalButton'] = &$data['$paymentLink']; + + $data['$dir'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => '']; + $data['$dir_text_align'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'right' : 'left', 'label' => '']; + + $data['$payment.date'] = ['value' => ' ', 'label' => ctrans('texts.payment_date')]; + $data['$method'] = ['value' => ' ', 'label' => ctrans('texts.method')]; + + $data['$statement_amount'] = ['value' => '', 'label' => ctrans('texts.amount')]; + $data['$statement'] = ['value' => '', 'label' => ctrans('texts.statement')]; + + $data['$entity_images'] = ['value' => $this->generateEntityImagesMarkup(), 'label' => '']; + + $data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')]; + + $arrKeysLength = array_map('strlen', array_keys($data)); + array_multisort($arrKeysLength, SORT_DESC, $data); + + return $data; + } + + public function makeValues() :array + { + $data = []; + + $values = $this->buildEntityDataArray(); + + foreach ($values as $key => $value) { + $data[$key] = $value['value']; + } + + return $data; + } + + public 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->vendor).''; + } + + 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); + + if ($country) { + return ctrans('texts.country_' . $country->name); + } + + return ' '; + } + + + private function getCountryCode() :string + { + $country = Country::find($this->settings->country_id); + + if($country) + return $country->iso_3166_2; + // if ($country) { + // return ctrans('texts.country_' . $country->iso_3166_2); + // } + + 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->vendor).''; + } + + return $data; + } + + private function lineTaxValues() :string + { + $tax_map = $this->entity_calc->getTaxMap(); + + $data = ''; + + foreach ($tax_map as $tax) { + $data .= ''.Number::formatMoney($tax['total'], $this->vendor).''; + } + + return $data; + } + + private function makeTotalTaxes() :string + { + $data = ''; + + if (! $this->entity_calc->getTotalTaxMap()) { + return $data; + } + + foreach ($this->entity_calc->getTotalTaxMap() as $tax) { + $data .= ''; + $data .= ''; + $data .= ''.$tax['name'].''; + $data .= ''.Number::formatMoney($tax['total'], $this->vendor).''; + } + + 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'), "/"); + return 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; + } + + /** + * Generate markup for HTML images on entity. + * + * @return string|void + */ + protected function generateEntityImagesMarkup() + { + if ($this->company->getSetting('embed_documents') === false) { + return ''; + } + + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $container = $dom->createElement('div'); + $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);'); + + foreach ($this->entity->documents as $document) { + if (!$document->isImage()) { + continue; + } + + $image = $dom->createElement('img'); + + $image->setAttribute('src', $document->generateUrl()); + $image->setAttribute('style', 'max-height: 100px; margin-top: 20px;'); + + $container->appendChild($image); + } + + $dom->appendChild($container); + + return $dom->saveHTML(); + } +} diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index f0da1c52d4b7..35e9e2e4afe4 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -295,6 +295,7 @@
+
@@ -333,7 +334,8 @@ $entity_images let tables = [ 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', - 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table' + 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', + 'client-details','vendor-details' ]; tables.forEach((tableIdentifier) => {