Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2022-06-09 19:38:32 +10:00
commit bf4e968f5d
406 changed files with 366651 additions and 362237 deletions

View File

@ -1 +1 @@
5.3.96 5.3.97

View File

@ -108,6 +108,7 @@ class CreateSingleAccount extends Command
'default_password_timeout' => 30*60000, 'default_password_timeout' => 30*60000,
'portal_mode' => 'domain', 'portal_mode' => 'domain',
'portal_domain' => 'http://ninja.test:8000', 'portal_domain' => 'http://ninja.test:8000',
'track_inventory' => true
]); ]);
$settings = $company->settings; $settings = $company->settings;

View File

@ -103,7 +103,7 @@ class Kernel extends ConsoleKernel
if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) { if (config('queue.default') == 'database' && Ninja::isSelfHost() && config('ninja.internal_queue_enabled') && !config('ninja.is_docker')) {
$schedule->command('queue:work database --stop-when-empty')->everyMinute()->withoutOverlapping(); $schedule->command('queue:work database --stop-when-empty --memory=256')->everyMinute()->withoutOverlapping();
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping(); $schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();

View File

@ -98,11 +98,11 @@ class CompanySettings extends BaseSettings
public $expense_number_pattern = ''; //@implemented public $expense_number_pattern = ''; //@implemented
public $expense_number_counter = 1; //@implemented public $expense_number_counter = 1; //@implemented
public $recurring_expense_number_pattern = ''; public $recurring_expense_number_pattern = '';
public $recurring_expense_number_counter = 1; public $recurring_expense_number_counter = 1;
public $recurring_quote_number_pattern = ''; public $recurring_quote_number_pattern = '';
public $recurring_quote_number_counter = 1; public $recurring_quote_number_counter = 1;
public $vendor_number_pattern = ''; //@implemented public $vendor_number_pattern = ''; //@implemented
public $vendor_number_counter = 1; //@implemented public $vendor_number_counter = 1; //@implemented
@ -116,6 +116,9 @@ class CompanySettings extends BaseSettings
public $project_number_pattern = ''; //@implemented public $project_number_pattern = ''; //@implemented
public $project_number_counter = 1; //@implemented public $project_number_counter = 1; //@implemented
public $purchase_order_number_pattern = ''; //@implemented
public $purchase_order_number_counter = 1; //@implemented
public $shared_invoice_quote_counter = false; //@implemented public $shared_invoice_quote_counter = false; //@implemented
public $shared_invoice_credit_counter = false; //@implemented public $shared_invoice_credit_counter = false; //@implemented
public $recurring_number_prefix = ''; //@implemented public $recurring_number_prefix = ''; //@implemented
@ -133,6 +136,13 @@ class CompanySettings extends BaseSettings
public $invoice_design_id = 'Wpmbk5ezJn'; //@implemented public $invoice_design_id = 'Wpmbk5ezJn'; //@implemented
public $quote_design_id = 'Wpmbk5ezJn'; //@implemented public $quote_design_id = 'Wpmbk5ezJn'; //@implemented
public $credit_design_id = 'Wpmbk5ezJn'; //@implemented public $credit_design_id = 'Wpmbk5ezJn'; //@implemented
public $purchase_order_design_id = 'Wpmbk5ezJn';
public $purchase_order_footer = ''; //@implemented
public $purchase_order_terms = ''; //@implemented
public $purchase_order_public_notes = ''; //@implemented
public $require_purchase_order_signature = false; //@TODO ben to confirm
public $invoice_footer = ''; //@implemented public $invoice_footer = ''; //@implemented
public $credit_footer = ''; //@implemented public $credit_footer = ''; //@implemented
public $credit_terms = ''; //@implemented public $credit_terms = ''; //@implemented
@ -170,6 +180,8 @@ class CompanySettings extends BaseSettings
public $email_subject_payment = ''; //@implemented public $email_subject_payment = ''; //@implemented
public $email_subject_payment_partial = ''; //@implemented public $email_subject_payment_partial = ''; //@implemented
public $email_subject_statement = ''; //@implemented public $email_subject_statement = ''; //@implemented
public $email_subject_purchase_order = ''; //@implemented
public $email_template_purchase_order = ''; //@implemented
public $email_template_invoice = ''; //@implemented public $email_template_invoice = ''; //@implemented
public $email_template_credit = ''; //@implemented public $email_template_credit = ''; //@implemented
public $email_template_quote = ''; //@implemented public $email_template_quote = ''; //@implemented
@ -277,6 +289,15 @@ class CompanySettings extends BaseSettings
public $auto_archive_invoice_cancelled = false; public $auto_archive_invoice_cancelled = false;
public static $casts = [ public static $casts = [
'email_subject_purchase_order' => 'string',
'email_template_purchase_order' => 'string',
'require_purchase_order_signature' => 'bool',
'purchase_order_public_notes' => 'string',
'purchase_order_terms' => 'string',
'purchase_order_design_id' => 'string',
'purchase_order_footer' => 'string',
'purchase_order_number_pattern' => 'string',
'purchase_order_number_counter' => 'int',
'page_numbering_alignment' => 'string', 'page_numbering_alignment' => 'string',
'page_numbering' => 'bool', 'page_numbering' => 'bool',
'auto_archive_invoice_cancelled' => 'bool', 'auto_archive_invoice_cancelled' => 'bool',
@ -474,6 +495,7 @@ class CompanySettings extends BaseSettings
'portal_custom_footer' => 'string', 'portal_custom_footer' => 'string',
'portal_custom_js' => 'string', 'portal_custom_js' => 'string',
'client_portal_enable_uploads' => 'bool', 'client_portal_enable_uploads' => 'bool',
'purchase_order_number_counter' => 'integer',
]; ];
public static $free_plan_casts = [ public static $free_plan_casts = [
@ -527,6 +549,7 @@ class CompanySettings extends BaseSettings
'invoice_design_id', 'invoice_design_id',
'quote_design_id', 'quote_design_id',
'credit_design_id', 'credit_design_id',
'purchase_order_design_id',
]; ];
/** /**
@ -625,6 +648,25 @@ class CompanySettings extends BaseSettings
'$client.phone', '$client.phone',
'$contact.email', '$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',
],
'purchase_order_details' => [
'$purchase_order.number',
'$purchase_order.po_number',
'$purchase_order.date',
'$purchase_order.due_date',
'$purchase_order.total',
'$purchase_order.balance_due',
],
'company_details' => [ 'company_details' => [
'$company.name', '$company.name',
'$company.id_number', '$company.id_number',

View File

@ -61,10 +61,13 @@ class EmailTemplateDefaults
break; break;
case 'email_template_custom3': case 'email_template_custom3':
return self::emailInvoiceTemplate(); return self::emailInvoiceTemplate();
case 'email_template_purchase_order':
return self::emailPurchaseOrderSubject();
break; break;
/* Subject */ /* Subject */
case 'email_subject_purchase_order':
return self::emailPurchaseOrderSubject();
case 'email_subject_invoice': case 'email_subject_invoice':
return self::emailInvoiceSubject(); return self::emailInvoiceSubject();
break; break;
@ -152,6 +155,20 @@ class EmailTemplateDefaults
return ctrans('texts.payment_subject'); return ctrans('texts.payment_subject');
} }
public static function emailPurchaseOrderSubject()
{
return ctrans('texts.purchase_order_subject', ['number' => '$number']);
}
public static function emailPurchaseOrderTemplate()
{
$purchase_order_message = '<p>$vendor<br><br>'.self::transformText('purchase_order_message').'</p><div class="center">$view_button</div>';
return $purchase_order_message;
}
public static function emailPaymentTemplate() public static function emailPaymentTemplate()
{ {
$payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>'; $payment_message = '<p>$client<br><br>'.self::transformText('payment_message').'<br><br>$invoices</p><div class="center">$view_button</div>';

View File

@ -12,7 +12,6 @@
namespace App\Events\Invoice; namespace App\Events\Invoice;
use App\Models\Company; use App\Models\Company;
use App\Models\InvoiceInvitation;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
/** /**
@ -35,12 +34,12 @@ class InvoiceWasEmailedAndFailed
/** /**
* Create a new event instance. * Create a new event instance.
* *
* @param InvoiceInvitation $invitation * @param $invitation
* @param Company $company * @param Company $company
* @param string $errors * @param string $errors
* @param array $event_vars * @param array $event_vars
*/ */
public function __construct(InvoiceInvitation $invitation, Company $company, string $message, string $template, array $event_vars) public function __construct($invitation, Company $company, string $message, string $template, array $event_vars)
{ {
$this->invitation = $invitation; $this->invitation = $invitation;

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasArchived.
*/
class PurchaseOrderWasArchived
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasCreated.
*/
class PurchaseOrderWasCreated
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasDeleted.
*/
class PurchaseOrderWasDeleted
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasEmailed.
*/
class PurchaseOrderWasEmailed
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasRestored.
*/
class PurchaseOrderWasRestored
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
public $fromDeleted;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, $fromDeleted, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->fromDeleted = $fromDeleted;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasUpdated.
*/
class PurchaseOrderWasUpdated
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use Illuminate\Queue\SerializesModels;
/**
* Class PurchaseOrderWasViewed.
*/
class PurchaseOrderWasViewed
{
use SerializesModels;
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param PurchaseOrder $purchase_order
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Factory;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class PurchaseOrderInvitationFactory
{
public static function create(int $company_id, int $user_id) :PurchaseOrderInvitation
{
$ci = new PurchaseOrderInvitation();
$ci->company_id = $company_id;
$ci->user_id = $user_id;
$ci->vendor_contact_id = null;
$ci->purchase_order_id = null;
$ci->key = Str::random(config('ninja.key_length'));
$ci->transaction_reference = null;
$ci->message_id = null;
$ci->email_error = '';
$ci->signature_base64 = '';
$ci->signature_date = null;
$ci->sent_date = null;
$ci->viewed_date = null;
$ci->opened_date = null;
return $ci;
}
}

View File

@ -52,7 +52,10 @@ class InvoiceItemSum
$this->invoice = $invoice; $this->invoice = $invoice;
$this->currency = $this->invoice->client->currency(); if($this->invoice->client)
$this->currency = $this->invoice->client->currency();
else
$this->currency = $this->invoice->vendor->currency();
$this->line_items = []; $this->line_items = [];
} }

View File

@ -46,7 +46,10 @@ class InvoiceItemSumInclusive
$this->invoice = $invoice; $this->invoice = $invoice;
$this->currency = $this->invoice->client->currency(); if($this->invoice->client)
$this->currency = $this->invoice->client->currency();
else
$this->currency = $this->invoice->vendor->currency();
$this->line_items = []; $this->line_items = [];
} }

View File

@ -44,6 +44,8 @@ class InvoiceSum
private $gross_sub_total; private $gross_sub_total;
private $precision;
/** /**
* Constructs the object with Invoice and Settings object. * Constructs the object with Invoice and Settings object.
* *
@ -53,8 +55,10 @@ class InvoiceSum
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
// if(!$this->invoice->relationLoaded('client')) if($this->invoice->client)
// $this->invoice->load('client'); $this->precision = $this->invoice->client->currency()->precision;
else
$this->precision = $this->invoice->vendor->currency()->precision;
$this->tax_map = new Collection; $this->tax_map = new Collection;
} }
@ -224,11 +228,19 @@ class InvoiceSum
return $this->invoice; return $this->invoice;
} }
public function getPurchaseOrder()
{
$this->setCalculatedAttributes();
$this->invoice->saveQuietly();
return $this->invoice;
}
public function getRecurringInvoice() public function getRecurringInvoice()
{ {
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes(); $this->invoice->total_taxes = $this->getTotalTaxes();
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->saveQuietly(); $this->invoice->saveQuietly();
@ -247,13 +259,13 @@ class InvoiceSum
if ($this->invoice->amount != $this->invoice->balance) { if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance; $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date; $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
} else { } else {
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
} }
} }
/* Set new calculated total */ /* Set new calculated total */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes(); $this->invoice->total_taxes = $this->getTotalTaxes();

View File

@ -41,6 +41,7 @@ class InvoiceSumInclusive
private $sub_total; private $sub_total;
private $precision;
/** /**
* Constructs the object with Invoice and Settings object. * Constructs the object with Invoice and Settings object.
* *
@ -50,6 +51,11 @@ class InvoiceSumInclusive
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
if($this->invoice->client)
$this->precision = $this->invoice->client->currency()->precision;
else
$this->precision = $this->invoice->vendor->currency()->precision;
$this->tax_map = new Collection; $this->tax_map = new Collection;
} }
@ -164,32 +170,15 @@ class InvoiceSumInclusive
private function calculateTotals() private function calculateTotals()
{ {
//$this->total += $this->total_taxes;
// if (is_numeric($this->invoice->custom_value1)) {
// $this->total += $this->invoice->custom_value1;
// }
// if (is_numeric($this->invoice->custom_value2)) {
// $this->total += $this->invoice->custom_value2;
// }
// if (is_numeric($this->invoice->custom_value3)) {
// $this->total += $this->invoice->custom_value3;
// }
// if (is_numeric($this->invoice->custom_value4)) {
// $this->total += $this->invoice->custom_value4;
// }
return $this; return $this;
} }
public function getRecurringInvoice() public function getRecurringInvoice()
{ {
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes(); $this->invoice->total_taxes = $this->getTotalTaxes();
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->saveQuietly(); $this->invoice->saveQuietly();
@ -229,6 +218,15 @@ class InvoiceSumInclusive
return $this->invoice; return $this->invoice;
} }
public function getPurchaseOrder()
{
//Build invoice values here and return Invoice
$this->setCalculatedAttributes();
$this->invoice->saveQuietly();
return $this->invoice;
}
/** /**
* Build $this->invoice variables after * Build $this->invoice variables after
* calculations have been performed. * calculations have been performed.
@ -240,14 +238,14 @@ class InvoiceSumInclusive
if ($this->invoice->amount != $this->invoice->balance) { if ($this->invoice->amount != $this->invoice->balance) {
$paid_to_date = $this->invoice->amount - $this->invoice->balance; $paid_to_date = $this->invoice->amount - $this->invoice->balance;
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date; $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
} else { } else {
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
} }
} }
/* Set new calculated total */ /* Set new calculated total */
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision); $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
$this->invoice->total_taxes = $this->getTotalTaxes(); $this->invoice->total_taxes = $this->getTotalTaxes();

View File

@ -22,6 +22,7 @@ use App\Models\ClientContact;
use App\Models\CreditInvitation; use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Services\ClientPortal\InstantPayment; use App\Services\ClientPortal\InstantPayment;
use App\Utils\CurlUtils; use App\Utils\CurlUtils;
@ -41,7 +42,7 @@ class InvitationController extends Controller
use MakesDates; use MakesDates;
public function router(string $entity, string $invitation_key) public function router(string $entity, string $invitation_key)
{ {
Auth::logout(); Auth::logout();
return $this->genericRouter($entity, $invitation_key); return $this->genericRouter($entity, $invitation_key);
@ -166,7 +167,7 @@ class InvitationController extends Controller
{ {
set_time_limit(45); set_time_limit(45);
if(Ninja::isHosted()) if(Ninja::isHosted())
return $this->returnRawPdf($entity, $invitation_key); return $this->returnRawPdf($entity, $invitation_key);
@ -202,7 +203,7 @@ class InvitationController extends Controller
return response()->streamDownload(function () use($file) { return response()->streamDownload(function () use($file) {
echo $file; echo $file;
}, $file_name, $headers); }, $file_name, $headers);
} }
public function routerForIframe(string $entity, string $client_hash, string $invitation_key) public function routerForIframe(string $entity, string $client_hash, string $invitation_key)
@ -228,14 +229,14 @@ class InvitationController extends Controller
$invitation = InvoiceInvitation::where('key', $invitation_key) $invitation = InvoiceInvitation::where('key', $invitation_key)
->with('contact.client') ->with('contact.client')
->firstOrFail(); ->firstOrFail();
auth()->guard('contact')->loginUsingId($invitation->contact->id, true); auth()->guard('contact')->loginUsingId($invitation->contact->id, true);
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
if($invoice->partial > 0) if($invoice->partial > 0)
$amount = round($invoice->partial, (int)$invoice->client->currency()->precision); $amount = round($invoice->partial, (int)$invoice->client->currency()->precision);
else else
$amount = round($invoice->balance, (int)$invoice->client->currency()->precision); $amount = round($invoice->balance, (int)$invoice->client->currency()->precision);
$gateways = $invitation->contact->client->service()->getPaymentMethods($amount); $gateways = $invitation->contact->client->service()->getPaymentMethods($amount);
@ -279,6 +280,10 @@ class InvitationController extends Controller
$invite = CreditInvitation::withTrashed()->where('key', $invitation_key)->first(); $invite = CreditInvitation::withTrashed()->where('key', $invitation_key)->first();
$invite->contact->send_email = false; $invite->contact->send_email = false;
$invite->contact->save(); $invite->contact->save();
}elseif($entity == 'purchase_order'){
$invite = PurchaseOrderInvitation::withTrashed()->where('key', $invitation_key)->first();
$invite->contact->send_email = false;
$invite->contact->save();
} }
else else
return abort(404); return abort(404);

View File

@ -225,6 +225,7 @@ class InvoiceController extends BaseController
$invoice = $invoice->service() $invoice = $invoice->service()
->fillDefaults() ->fillDefaults()
->triggeredActions($request) ->triggeredActions($request)
->adjustInventory()
->save(); ->save();
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -414,9 +415,14 @@ class InvoiceController extends BaseController
return response()->json(['message' => ctrans('texts.locked_invoice')], 403); return response()->json(['message' => ctrans('texts.locked_invoice')], 403);
} }
$old_invoice = $invoice->line_items;
$invoice = $this->invoice_repo->save($request->all(), $invoice); $invoice = $this->invoice_repo->save($request->all(), $invoice);
$invoice->service()->triggeredActions($request)->touchPdf(); $invoice->service()
->triggeredActions($request)
->touchPdf()
->adjustInventory($old_invoice);
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -709,7 +715,6 @@ class InvoiceController extends BaseController
echo Storage::get($file); echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']); }, basename($file), ['Content-Type' => 'application/pdf']);
break; break;
case 'restore': case 'restore':
$this->invoice_repo->restore($invoice); $this->invoice_repo->restore($invoice);

View File

@ -12,18 +12,23 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Factory\PurchaseOrderFactory; use App\Factory\PurchaseOrderFactory;
use App\Filters\PurchaseOrderFilters; use App\Filters\PurchaseOrderFilters;
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
use App\Jobs\Invoice\ZipInvoices;
use App\Models\Client; use App\Models\Client;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository; use App\Repositories\PurchaseOrderRepository;
use App\Transformers\PurchaseOrderTransformer; use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response; use Illuminate\Http\Response;
@ -174,14 +179,14 @@ class PurchaseOrderController extends BaseController
public function store(StorePurchaseOrderRequest $request) public function store(StorePurchaseOrderRequest $request)
{ {
$client = Client::find($request->get('client_id'));
$purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id)); $purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id));
$purchase_order = $purchase_order->service() $purchase_order = $purchase_order->service()
->fillDefaults() ->fillDefaults()
->save(); ->save();
event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($purchase_order); return $this->itemResponse($purchase_order);
} }
/** /**
@ -352,6 +357,8 @@ class PurchaseOrderController extends BaseController
$purchase_order = $this->purchase_order_repository->save($request->all(), $purchase_order); $purchase_order = $this->purchase_order_repository->save($request->all(), $purchase_order);
event(new PurchaseOrderWasUpdated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($purchase_order); return $this->itemResponse($purchase_order);
} }
/** /**
@ -410,4 +417,221 @@ class PurchaseOrderController extends BaseController
return $this->itemResponse($purchase_order->fresh()); return $this->itemResponse($purchase_order->fresh());
} }
/**
* Perform bulk actions on the list view.
*
* @return Collection
*
* @OA\Post(
* path="/api/v1/purchase_orders/bulk",
* operationId="bulkPurchaseOrderss",
* tags={"purchase_orders"},
* summary="Performs bulk actions on an array of purchase_orders",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="Purchase Order IDS",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The Bulk Action response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function bulk()
{
$action = request()->input('action');
$ids = request()->input('ids');
$purchase_orders = PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (! $purchase_orders) {
return response()->json(['message' => 'No Purchase Orders Found']);
}
/*
* Download Purchase Order/s
*/
if ($action == 'bulk_download' && $purchase_orders->count() > 1) {
$purchase_orders->each(function ($purchase_order) {
if (auth()->user()->cannot('view', $purchase_order)) {
nlog("access denied");
return response()->json(['message' => ctrans('text.access_denied')]);
}
});
ZipInvoices::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
/*
* Send the other actions to the switch
*/
$purchase_orders->each(function ($purchase_order, $key) use ($action) {
if (auth()->user()->can('edit', $purchase_order)) {
$this->performAction($purchase_order, $action, true);
}
});
/* Need to understand which permission are required for the given bulk action ie. view / edit */
return $this->listResponse(PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
}
/**
* @OA\Get(
* path="/api/v1/purchase_orders/{id}/{action}",
* operationId="actionPurchaseOrder",
* tags={"purchase_orders"},
* summary="Performs a custom action on an purchase order",
* description="Performs a custom action on an purchase order.
*
* The current range of actions are as follows
* - 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"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Purchase Order Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Parameter(
* name="action",
* in="path",
* description="The action string to be performed",
* example="clone_to_quote",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the invoice object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Invoice"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param ActionPurchaseOrderRequest $request
* @param PurchaseOrder $purchase_order
* @param $action
* @return \App\Http\Controllers\Response|\Illuminate\Http\JsonResponse|Response|mixed|\Symfony\Component\HttpFoundation\StreamedResponse
*/
public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action)
{
return $this->performAction($invoice, $action);
}
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
{
/*If we are using bulk actions, we don't want to return anything */
switch ($action) {
case 'mark_sent':
$purchase_order->service()->markSent()->save();
if (! $bulk) {
return $this->itemResponse($purchase_order);
}
break;
case 'download':
$file = $purchase_order->service()->getPurchaseOrderPdf();
return response()->streamDownload(function () use($file) {
echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']);
break;
case 'restore':
$this->purchase_order_repository->restore($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'archive':
$this->purchase_order_repository->archive($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'delete':
$this->purchase_order_repository->delete($purchase_order);
if (! $bulk) {
return $this->listResponse($purchase_order);
}
break;
case 'email':
//check query parameter for email_type and set the template else use calculateTemplate
default:
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break;
}
}
} }

View File

@ -362,6 +362,7 @@ class BillingPortalPurchase extends Component
->service() ->service()
->markSent() ->markSent()
->fillDefaults() ->fillDefaults()
->adjustInventory()
->save(); ->save();
Cache::put($this->hash, [ Cache::put($this->hash, [

View File

@ -38,10 +38,13 @@ class StoreProductRequest extends Request
$rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
} }
$rules['cost'] = 'numeric'; $rules['cost'] = 'sometimes|numeric';
$rules['price'] = 'numeric'; $rules['price'] = 'sometimes|numeric';
$rules['quantity'] = 'numeric'; $rules['quantity'] = 'sometimes|numeric';
$rules['in_stock_quantity'] = 'sometimes|numeric';
$rules['stock_notification_threshold'] = 'sometimes|numeric';
$rules['stock_notification'] = 'sometimes|bool';
return $rules; return $rules;
} }

View File

@ -44,6 +44,9 @@ class UpdateProductRequest extends Request
$rules['cost'] = 'numeric'; $rules['cost'] = 'numeric';
$rules['price'] = 'numeric'; $rules['price'] = 'numeric';
$rules['quantity'] = 'numeric'; $rules['quantity'] = 'numeric';
$rules['in_stock_quantity'] = 'sometimes|numeric';
$rules['stock_notification_threshold'] = 'sometimes|numeric';
$rules['stock_notification'] = 'sometimes|bool';
return $rules; return $rules;
} }
@ -60,6 +63,13 @@ class UpdateProductRequest extends Request
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
} }
if(array_key_exists('in_stock_quantity', $input) && request()->has('update_in_stock_quantity') && request()->input('update_in_stock_quantity') == 'true'){
}
elseif(array_key_exists('in_stock_quantity', $input)){
unset($input['in_stock_quantity']);
}
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\PurchaseOrder;
use App\Http\Requests\Request;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
class ActionPurchaseOrderRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
private $error_msg;
// private $invoice;
public function authorize() : bool
{
return auth()->user()->can('edit', $this->purchase_order);
}
public function rules()
{
return [
'action' => 'required'
];
}
protected function prepareForValidation()
{
$input = $this->all();
if($this->action){
$input['action'] = $this->action;
} elseif (!array_key_exists('action', $input) ) {
$this->error_msg = 'Action is a required field';
}
$this->replace($input);
}
public function messages()
{
return [
'action' => $this->error_msg,
];
}
}

View File

@ -38,8 +38,7 @@ class StorePurchaseOrderRequest extends Request
{ {
$rules = []; $rules = [];
$rules['client_id'] = 'required'; $rules['vendor_id'] = 'required';
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)]; $rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
$rules['discount'] = 'sometimes|numeric'; $rules['discount'] = 'sometimes|numeric';

View File

@ -55,7 +55,6 @@ class UpdatePurchaseOrderRequest extends Request
$input = $this->decodePrimaryKeys($input); $input = $this->decodePrimaryKeys($input);
$input['id'] = $this->purchase_order->id; $input['id'] = $this->purchase_order->id;
$this->replace($input); $this->replace($input);

View File

@ -71,6 +71,7 @@ class BaseTransformer
$client_id_search = $this->company $client_id_search = $this->company
->clients() ->clients()
->where('is_deleted', false)
->where('id_number', $client_name); ->where('id_number', $client_name);
if ($client_id_search->count() >= 1) { if ($client_id_search->count() >= 1) {
@ -79,6 +80,7 @@ class BaseTransformer
$client_name_search = $this->company $client_name_search = $this->company
->clients() ->clients()
->where('is_deleted', false)
->where('name', $client_name); ->where('name', $client_name);
if ($client_name_search->count() >= 1) { if ($client_name_search->count() >= 1) {
@ -86,10 +88,11 @@ class BaseTransformer
} }
} }
if (!empty($client_email)) { if (!empty($client_email)) {
$contacts = ClientContact::where( $contacts = ClientContact::whereHas('client', function($query){
'company_id', $query->where('is_deleted', false);
$this->company->id })
)->where('email', $client_email); ->where('company_id', $this->company->id)
->where('email', $client_email);
if ($contacts->count() >= 1) { if ($contacts->count() >= 1) {
return $contacts->first()->client_id; return $contacts->first()->client_id;
@ -109,6 +112,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->clients() ->clients()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -124,6 +128,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->vendors() ->vendors()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -139,6 +144,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->projects() ->projects()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -154,6 +160,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->products() ->products()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)), strtolower(str_replace(' ', '', $key)),
]) ])
@ -186,6 +193,7 @@ class BaseTransformer
{ {
$client = $this->company $client = $this->company
->clients() ->clients()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -203,6 +211,7 @@ class BaseTransformer
{ {
$product = $this->company $product = $this->company
->products() ->products()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`product_key`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $key)), strtolower(str_replace(' ', '', $key)),
]) ])
@ -273,6 +282,7 @@ class BaseTransformer
$tax_rate = $this->company $tax_rate = $this->company
->tax_rates() ->tax_rates()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -292,6 +302,7 @@ class BaseTransformer
$tax_rate = $this->company $tax_rate = $this->company
->tax_rates() ->tax_rates()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -341,6 +352,7 @@ class BaseTransformer
{ {
$invoice = $this->company $invoice = $this->company
->invoices() ->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
]) ])
@ -358,6 +370,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->invoices() ->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
]) ])
@ -371,6 +384,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->expenses() ->expenses()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $expense_number)), strtolower(str_replace(' ', '', $expense_number)),
]) ])
@ -386,6 +400,7 @@ class BaseTransformer
{ {
return $this->company return $this->company
->quotes() ->quotes()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $quote_number)), strtolower(str_replace(' ', '', $quote_number)),
]) ])
@ -401,6 +416,7 @@ class BaseTransformer
{ {
$invoice = $this->company $invoice = $this->company
->invoices() ->invoices()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $invoice_number)), strtolower(str_replace(' ', '', $invoice_number)),
]) ])
@ -418,6 +434,7 @@ class BaseTransformer
{ {
$vendor = $this->company $vendor = $this->company
->vendors() ->vendors()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -452,6 +469,7 @@ class BaseTransformer
{ {
$ec = $this->company $ec = $this->company
->expense_categories() ->expense_categories()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])
@ -486,6 +504,7 @@ class BaseTransformer
{ {
$project = $this->company $project = $this->company
->projects() ->projects()
->where('is_deleted', false)
->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [ ->whereRaw("LOWER(REPLACE(`name`, ' ' ,'')) = ?", [
strtolower(str_replace(' ', '', $name)), strtolower(str_replace(' ', '', $name)),
]) ])

View File

@ -1,11 +1,11 @@
<?php <?php
/** /**
* Entity Ninja (https://entityninja.com). * Invoice Ninja (https://entityninja.com).
* *
* @link https://github.com/entityninja/entityninja source repository * @link https://github.com/invoiceninja/invoiceninja source repository
* *
* @copyright Copyright (c) 2022. Entity Ninja LLC (https://entityninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://www.elastic.co/licensing/elastic-license * @license https://www.elastic.co/licensing/elastic-license
*/ */

View File

@ -11,7 +11,11 @@
namespace App\Jobs\Inventory; namespace App\Jobs\Inventory;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Mail\Admin\InventoryNotificationObject;
use App\Models\Company; use App\Models\Company;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Product; use App\Models\Product;
@ -20,10 +24,9 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
//todo - ensure we are MultiDB Aware in dispatched jobs
class AdjustProductInventory implements ShouldQueue class AdjustProductInventory implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@ -34,9 +37,8 @@ class AdjustProductInventory implements ShouldQueue
public array $old_invoice; public array $old_invoice;
public function __construct(Company $company, Invoice $invoice, array $old_invoice = []) public function __construct(Company $company, Invoice $invoice, ?array $old_invoice = [])
{ {
$this->company = $company; $this->company = $company;
$this->invoice = $invoice; $this->invoice = $invoice;
$this->old_invoice = $old_invoice; $this->old_invoice = $old_invoice;
@ -53,12 +55,18 @@ class AdjustProductInventory implements ShouldQueue
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
if(count($this->old_invoice) > 0) if(count($this->old_invoice) > 0)
return $this->existingInventoryAdjustment(); $this->existingInventoryAdjustment();
return $this->newInventoryAdjustment(); return $this->newInventoryAdjustment();
} }
public function middleware()
{
return [new WithoutOverlapping($this->company->company_key)];
}
private function newInventoryAdjustment() private function newInventoryAdjustment()
{ {
@ -68,16 +76,18 @@ class AdjustProductInventory implements ShouldQueue
{ {
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first(); $p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->where('in_stock_quantity', '>', 0)->first();
$p->in_stock_quantity -= $item->quantity;
$p->save();
//check threshols and notify user if(!$p)
continue;
$p->in_stock_quantity -= $item->quantity;
$p->saveQuietly();
if($p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold) if($p->stock_notification_threshold && $p->in_stock_quantity <= $p->stock_notification_threshold)
$this->notifyStockLevels($p, 'product'); $this->notifyStockLevels($p, 'product');
elseif($this->company->stock_notification_threshold && $p->in_stock_quantity <= $this->company->stock_notification_threshold){ elseif($this->company->stock_notification_threshold && $p->in_stock_quantity <= $this->company->stock_notification_threshold)
$this->notifyStocklevels($p, 'company'); $this->notifyStocklevels($p, 'company');
}
} }
} }
@ -85,11 +95,31 @@ class AdjustProductInventory implements ShouldQueue
private function existingInventoryAdjustment() private function existingInventoryAdjustment()
{ {
foreach($this->old_invoice as $item)
{
$p = Product::where('product_key', $item->product_key)->where('company_id', $this->company->id)->first();
if(!$p)
continue;
$p->in_stock_quantity += $item->quantity;
$p->saveQuietly();
}
} }
private function notifyStocklevels(Product $product, string $notification_level) private function notifyStocklevels(Product $product, string $notification_level)
{ {
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer( (new InventoryNotificationObject($product, $notification_level))->build() );
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();
NinjaMailerJob::dispatch($nmo);
} }
} }

View File

@ -49,8 +49,8 @@ class SystemMaintenance implements ShouldQueue
nlog("Starting System Maintenance"); nlog("Starting System Maintenance");
// if(Ninja::isHosted()) if(Ninja::isHosted())
// return; return;
$delete_pdf_days = config('ninja.maintenance.delete_pdfs'); $delete_pdf_days = config('ninja.maintenance.delete_pdfs');

View File

@ -87,6 +87,7 @@ class SendRecurring implements ShouldQueue
->applyNumber() ->applyNumber()
//->createInvitations() //need to only link invitations to those in the recurring invoice //->createInvitations() //need to only link invitations to those in the recurring invoice
->fillDefaults() ->fillDefaults()
->adjustInventory()
->save(); ->save();
} }

View File

@ -89,6 +89,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
@ -187,6 +188,11 @@ class Import implements ShouldQueue
$this->resources = $resources; $this->resources = $resources;
} }
public function middleware()
{
return [new WithoutOverlapping($this->company->account->key)];
}
/** /**
* Execute the job. * Execute the job.
* *

View File

@ -0,0 +1,221 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Vendor;
use App\Exceptions\FilePermissionsFailure;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
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\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;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Storage;
use setasign\Fpdi\PdfParser\StreamReader;
class CreatePurchaseOrderPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
public $entity;
public $company;
public $contact;
private $disk;
public $invitation;
public $entity_string = '';
public $vendor;
/**
* Create a new job instance.
*
* @param $invitation
*/
public function __construct($invitation, $disk = 'public')
{
$this->invitation = $invitation;
$this->company = $invitation->company;
$this->entity = $invitation->purchase_order;
$this->entity_string = 'purchase_order';
$this->contact = $invitation->contact;
$this->vendor = $invitation->contact->vendor;
$this->vendor->load('company');
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
}
public function handle()
{
MultiDB::setDb($this->company->db);
/* Forget the singleton*/
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->locale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->generate($this->invitation);
}
$entity_design_id = '';
$path = $this->vendor->purchase_order_filepath($this->invitation);
$entity_design_id = 'purchase_order_design_id';
$file_path = $path.$this->entity->numberFormatter().'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
$design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if(!$design)
$design = Design::find(2);
$html = new VendorHtmlEngine($this->invitation);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true)
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$variables = $html->generateLabelsAndValues();
$state = [
'template' => $template->elements([
'client' => null,
'vendor' => $this->vendor,
'entity' => $this->entity,
'pdf_variables' => (array) $this->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $this->entity->company->getSetting('all_pages_header'),
'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'),
],
'process_markdown' => $this->entity->company->markdown_enabled,
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
$pdf = null;
try {
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
else {
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
if($numbered_pdf)
$pdf = $numbered_pdf;
}
} catch (\Exception $e) {
nlog(print_r($e->getMessage(), 1));
}
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
if ($pdf) {
try{
if(!Storage::disk($this->disk)->exists($path))
Storage::disk($this->disk)->makeDirectory($path, 0775);
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
}
catch(\Exception $e)
{
throw new FilePermissionsFailure($e->getMessage());
}
}
return $file_path;
}
public function failed($e)
{
}
}

View File

@ -127,8 +127,7 @@ class PaymentNotification implements ShouldQueue
*/ */
private function sendAnalytics($data) private function sendAnalytics($data)
{ {
nlog($data);
$data = utf8_encode($data); $data = utf8_encode($data);
$curl = curl_init(); $curl = curl_init();

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class CreatePurchaseOrderActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::CREATE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderArchivedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::ARCHIVE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderDeletedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::DELETE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://purchase_orderninja.com).
*
* @link https://github.com/purchase_orderninja/purchase_orderninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://purchase_orderninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderEmailActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->invitation->purchase_order->id;
$fields->company_id = $event->invitation->purchase_order->company_id;
$fields->vendor_contact_id = $event->invitation->purchase_order->vendor_contact_id;
$fields->vendor_id = $event->invitation->purchase_order->vendor_id;
$fields->activity_type_id = Activity::EMAIL_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->invitation->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderRestoredActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->purchase_order_id = $event->purchase_order->id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::RESTORE_PURCHASE_ORDER;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class PurchaseOrderViewedActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->invitation->purchase_order->user_id;
$event->invitation->purchase_order->service()->markSent()->save();
$fields->user_id = $user_id;
$fields->company_id = $event->invitation->company_id;
$fields->activity_type_id = Activity::VIEW_PURCHASE_ORDER;
$fields->vendor_id = $event->invitation->purchase_order->vendor_id;
$fields->vendor_contact_id = $event->invitation->vendor_contact_id;
$fields->invitation_id = $event->invitation->id;
$fields->purchase_order_id = $event->invitation->purchase_order_id;
$this->activity_repo->save($fields, $event->invitation->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\PurchaseOrder;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class UpdatePurchaseOrderActivity implements ShouldQueue
{
protected $activity_repo;
public $delay = 5;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDB($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->purchase_order->user_id;
$fields->user_id = $user_id;
$fields->vendor_id = $event->purchase_order->vendor_id;
$fields->company_id = $event->purchase_order->company_id;
$fields->activity_type_id = Activity::UPDATE_PURCHASE_ORDER;
$fields->purchase_order_id = $event->purchase_order->id;
$this->activity_repo->save($fields, $event->purchase_order, $event->event_vars);
}
}

View File

@ -0,0 +1,99 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\Admin;
use App\Mail\Engine\PaymentEmailEngine;
use App\Models\Company;
use App\Models\Product;
use App\Utils\Ninja;
use App\Utils\Number;
use Illuminate\Support\Facades\App;
use stdClass;
class InventoryNotificationObject
{
public Product $product;
public Company $company;
public $settings;
public string $notification_level;
public function __construct(Product $product, string $notification_level)
{
$this->product = $product;
$this->company = $product->company;
$this->settings = $this->company->settings;
}
public function build()
{
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->getLocale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return $this->product->in_stock_quantity;
}
private function getSubject()
{
return
ctrans(
'texts.inventory_notification_subject',
['product' => $this->product->product_key . ": " . $this->product->notes]
);
}
private function getData()
{
$data = [
'title' => $this->getSubject(),
'content' => ctrans(
'texts.inventory_notification_body',
['amount' => $this->getAmount(),
'product' => $this->product->product_key . ": " . $this->product->notes,
]
),
'url' => config('ninja.app_url'),
'button' => ctrans('texts.login'),
'signature' => $this->settings->email_signature,
'logo' => $this->company->present()->logo(),
'settings' => $this->settings,
'whitelabel' => $this->company->account->isPaid() ? true : false,
];
return $data;
}
}

View File

@ -109,6 +109,14 @@ class Activity extends StaticModel
const DELETE_RECURRING_EXPENSE = 123; const DELETE_RECURRING_EXPENSE = 123;
const RESTORE_RECURRING_EXPENSE = 124; const RESTORE_RECURRING_EXPENSE = 124;
const CREATE_PURCHASE_ORDER = 130;
const UPDATE_PURCHASE_ORDER = 131;
const ARCHIVE_PURCHASE_ORDER = 132;
const DELETE_PURCHASE_ORDER = 133;
const RESTORE_PURCHASE_ORDER = 134;
const EMAIL_PURCHASE_ORDER = 135;
const VIEW_PURCHASE_ORDER = 136;
protected $casts = [ protected $casts = [
'is_system' => 'boolean', 'is_system' => 'boolean',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Models\Presenters;
/**
* Class VendorContactPresenter.
*/
class VendorContactPresenter extends EntityPresenter
{
/**
* @return string
*/
public function name()
{
$contact_name = $this->entity->first_name.' '.$this->entity->last_name;
if (strlen($contact_name) > 1) {
return $contact_name;
}
return $this->entity->vendor->present()->name();
}
public function first_name()
{
return $this->entity->first_name ?: '';
}
public function last_name()
{
return $this->entity->last_name ?: '';
}
}

View File

@ -12,8 +12,15 @@
namespace App\Models; namespace App\Models;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Services\PurchaseOrder\PurchaseOrderService; use App\Services\PurchaseOrder\PurchaseOrderService;
use App\Utils\Ninja;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
class PurchaseOrder extends BaseModel class PurchaseOrder extends BaseModel
{ {
@ -129,11 +136,52 @@ class PurchaseOrder extends BaseModel
{ {
return $this->belongsTo(Client::class)->withTrashed(); return $this->belongsTo(Client::class)->withTrashed();
} }
public function markInvitationsSent()
{
$this->invitations->each(function ($invitation) {
if (! isset($invitation->sent_date)) {
$invitation->sent_date = Carbon::now();
$invitation->save();
}
});
}
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
{
if (! $invitation) {
if($this->invitations()->exists())
$invitation = $this->invitations()->first();
else{
$this->service()->createInvitations();
$invitation = $this->invitations()->first();
}
}
if(!$invitation)
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
$file_path = $this->vendor->purchase_order_filepath($invitation).$this->numberFormatter().'.pdf';
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
}
elseif(Ninja::isHosted() && $portal){
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation,config('filesystems.default'));
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
}
if(Storage::disk('public')->exists($file_path))
return Storage::disk('public')->{$type}($file_path);
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation);
return Storage::disk('public')->{$type}($file_path);
}
public function invitations() public function invitations()
{ {
return $this->hasMany(CreditInvitation::class); return $this->hasMany(PurchaseOrderInvitation::class);
} }
public function project() public function project()
@ -148,7 +196,7 @@ class PurchaseOrder extends BaseModel
public function service() public function service()
{ {
return new PurchaseOrderService($this); return new PurchaseOrderService($this);
} }
public function invoices() public function invoices()
@ -166,4 +214,17 @@ class PurchaseOrder extends BaseModel
return $this->morphMany(Document::class, 'documentable'); return $this->morphMany(Document::class, 'documentable');
} }
public function calc()
{
$purchase_order_calc = null;
if ($this->uses_inclusive_taxes) {
$purchase_order_calc = new InvoiceSumInclusive($this);
} else {
$purchase_order_calc = new InvoiceSum($this);
}
return $purchase_order_calc->build();
}
} }

View File

@ -0,0 +1,107 @@
<?php
namespace App\Models;
use App\Utils\Ninja;
use App\Utils\Traits\Inviteable;
use App\Utils\Traits\MakesDates;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
class PurchaseOrderInvitation extends BaseModel
{
use MakesDates;
use SoftDeletes;
use Inviteable;
protected $fillable = [
'id',
'vendor_contact_id',
];
protected $with = [
'company',
'contact',
];
protected $touches = ['purchase_order'];
public function getEntityType()
{
return self::class;
}
public function entityType()
{
return PurchaseOrder::class;
}
/**
* @return mixed
*/
public function purchase_order()
{
return $this->belongsTo(PurchaseOrder::class)->withTrashed();
}
/**
* @return mixed
*/
public function contact()
{
return $this->belongsTo(VendorContact::class, 'vendor_contact_id', 'id')->withTrashed();
}
/**
* @return mixed
*/
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function getName()
{
return $this->key;
}
public function markViewed()
{
$this->viewed_date = Carbon::now();
$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;
}
}
}

View File

@ -11,9 +11,13 @@
namespace App\Models; namespace App\Models;
use App\DataMapper\CompanySettings;
use App\Models\Presenters\VendorPresenter; use App\Models\Presenters\VendorPresenter;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\NumberFormatter;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
class Vendor extends BaseModel class Vendor extends BaseModel
@ -22,6 +26,7 @@ class Vendor extends BaseModel
use Filterable; use Filterable;
use GeneratesCounter; use GeneratesCounter;
use PresentableTrait; use PresentableTrait;
use AppSetup;
protected $fillable = [ protected $fillable = [
'name', 'name',
@ -95,6 +100,22 @@ class Vendor extends BaseModel
return $this->hasMany(Activity::class); return $this->hasMany(Activity::class);
} }
public function currency()
{
$currencies = Cache::get('currencies');
if(!$currencies)
$this->buildCache(true);
if(!$this->currency_id)
$this->currency_id = 1;
return $currencies->filter(function ($item) {
return $item->id == $this->currency_id;
})->first();
}
public function company() public function company()
{ {
return $this->belongsTo(Company::class); return $this->belongsTo(Company::class);
@ -109,4 +130,54 @@ class Vendor extends BaseModel
{ {
return ctrans('texts.vendor'); return ctrans('texts.vendor');
} }
public function setCompanyDefaults($data, $entity_name) :array
{
$defaults = [];
if (! (array_key_exists('terms', $data) && strlen($data['terms']) > 1)) {
$defaults['terms'] = $this->getSetting($entity_name.'_terms');
} elseif (array_key_exists('terms', $data)) {
$defaults['terms'] = $data['terms'];
}
if (! (array_key_exists('footer', $data) && strlen($data['footer']) > 1)) {
$defaults['footer'] = $this->getSetting($entity_name.'_footer');
} elseif (array_key_exists('footer', $data)) {
$defaults['footer'] = $data['footer'];
}
if (strlen($this->public_notes) >= 1) {
$defaults['public_notes'] = $this->public_notes;
}
return $defaults;
}
public function getSetting($setting)
{
if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) {
return $this->company->settings->{$setting};
}
elseif( property_exists(CompanySettings::defaults(), $setting) ) {
return CompanySettings::defaults()->{$setting};
}
return '';
}
public function purchase_order_filepath($invitation)
{
$contact_key = $invitation->contact->contact_key;
return $this->company->company_key.'/'.$this->vendor_hash.'/'.$contact_key.'/purchase_orders/';
}
public function country()
{
return $this->belongsTo(Country::class);
}
} }

View File

@ -11,6 +11,7 @@
namespace App\Models; namespace App\Models;
use App\Models\Presenters\VendorContactPresenter;
use App\Notifications\ClientContactResetPassword; use App\Notifications\ClientContactResetPassword;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Contracts\Translation\HasLocalePreference;
@ -35,6 +36,8 @@ class VendorContact extends Authenticatable implements HasLocalePreference
protected $touches = ['vendor']; protected $touches = ['vendor'];
protected $presenter = VendorContactPresenter::class;
/* Allow microtime timestamps */ /* Allow microtime timestamps */
protected $dateFormat = 'Y-m-d H:i:s.u'; protected $dateFormat = 'Y-m-d H:i:s.u';
@ -136,4 +139,8 @@ class VendorContact extends Authenticatable implements HasLocalePreference
->withTrashed() ->withTrashed()
->where('id', $this->decodePrimaryKey($value))->firstOrFail(); ->where('id', $this->decodePrimaryKey($value))->firstOrFail();
} }
public function purchase_order_invitations(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(PurchaseOrderInvitation::class);
}
} }

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\GoCardless; namespace App\PaymentDrivers\GoCardless;

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\GoCardless; namespace App\PaymentDrivers\GoCardless;

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\GoCardless; namespace App\PaymentDrivers\GoCardless;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers; namespace App\PaymentDrivers;

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\Mollie; namespace App\PaymentDrivers\Mollie;

View File

@ -8,7 +8,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\Razorpay; namespace App\PaymentDrivers\Razorpay;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers; namespace App\PaymentDrivers;

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace App\PaymentDrivers\Stripe; namespace App\PaymentDrivers\Stripe;

View File

@ -60,6 +60,14 @@ use App\Events\Payment\PaymentWasRefunded;
use App\Events\Payment\PaymentWasRestored; use App\Events\Payment\PaymentWasRestored;
use App\Events\Payment\PaymentWasUpdated; use App\Events\Payment\PaymentWasUpdated;
use App\Events\Payment\PaymentWasVoided; use App\Events\Payment\PaymentWasVoided;
use App\Events\PurchaseOrder\PurchaseOrderWasMarkedSent;
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Events\PurchaseOrder\PurchaseOrderWasDeleted;
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Events\Quote\QuoteWasApproved; use App\Events\Quote\QuoteWasApproved;
use App\Events\Quote\QuoteWasArchived; use App\Events\Quote\QuoteWasArchived;
use App\Events\Quote\QuoteWasCreated; use App\Events\Quote\QuoteWasCreated;
@ -170,6 +178,13 @@ use App\Listeners\Payment\PaymentEmailFailureActivity;
use App\Listeners\Payment\PaymentEmailedActivity; use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentNotification;
use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\Payment\PaymentRestoredActivity;
use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity;
use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity;
use App\Listeners\Quote\QuoteApprovedActivity; use App\Listeners\Quote\QuoteApprovedActivity;
use App\Listeners\Quote\QuoteApprovedNotification; use App\Listeners\Quote\QuoteApprovedNotification;
use App\Listeners\Quote\QuoteApprovedWebhook; use App\Listeners\Quote\QuoteApprovedWebhook;
@ -435,6 +450,27 @@ class EventServiceProvider extends ServiceProvider
PaymentWasEmailedAndFailed::class => [ PaymentWasEmailedAndFailed::class => [
PaymentEmailFailureActivity::class, PaymentEmailFailureActivity::class,
], ],
PurchaseOrderWasArchived::class => [
PurchaseOrderArchivedActivity::class,
],
PurchaseOrderWasCreated::class => [
CreatePurchaseOrderActivity::class,
],
PurchaseOrderWasDeleted::class => [
PurchaseOrderDeletedActivity::class,
],
PurchaseOrderWasEmailed::class => [
PurchaseOrderEmailActivity::class,
],
PurchaseOrderWasRestored::class => [
PurchaseOrderRestoredActivity::class,
],
PurchaseOrderWasUpdated::class => [
UpdatePurchaseOrderActivity::class,
],
PurchaseOrderWasViewed::class => [
PurchaseOrderViewedActivity::class,
],
CompanyDocumentsDeleted::class => [ CompanyDocumentsDeleted::class => [
DeleteCompanyDocuments::class, DeleteCompanyDocuments::class,
], ],
@ -557,7 +593,7 @@ class EventServiceProvider extends ServiceProvider
], ],
VendorWasUpdated::class => [ VendorWasUpdated::class => [
VendorUpdatedActivity::class, VendorUpdatedActivity::class,
], ]
]; ];

View File

@ -11,8 +11,10 @@
namespace App\Repositories; namespace App\Repositories;
use App\Factory\PurchaseOrderInvitationFactory;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
class PurchaseOrderRepository extends BaseRepository class PurchaseOrderRepository extends BaseRepository
@ -26,9 +28,81 @@ class PurchaseOrderRepository extends BaseRepository
public function save(array $data, PurchaseOrder $purchase_order) : ?PurchaseOrder public function save(array $data, PurchaseOrder $purchase_order) : ?PurchaseOrder
{ {
$purchase_order->fill($data); $purchase_order->fill($data);
$purchase_order->save(); $purchase_order->save();
if (isset($data['invitations'])) {
$invitations = collect($data['invitations']);
/* Get array of Keys which have been removed from the invitations array and soft delete each invitation */
$purchase_order->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) {
$invitation = PurchaseOrderInvitation::where('key', $invitation)->first();
if ($invitation)
$invitation->delete();
});
foreach ($data['invitations'] as $invitation) {
//if no invitations are present - create one.
if (! $this->getInvitation($invitation)) {
if (isset($invitation['id']))
unset($invitation['id']);
//make sure we are creating an invite for a contact who belongs to the client only!
$contact = VendorContact::find($invitation['vendor_contact_id']);
if ($contact && $purchase_order->vendor_id == $contact->vendor_id) {
$new_invitation = PurchaseOrderInvitation::withTrashed()
->where('vendor_contact_id', $contact->id)
->where('purchase_order_id', $purchase_order->id)
->first();
if ($new_invitation && $new_invitation->trashed()) {
$new_invitation->restore();
} else {
$new_invitation = PurchaseOrderInvitationFactory::create($purchase_order->company_id, $purchase_order->user_id);
$new_invitation->purchase_order_id = $purchase_order->id;
$new_invitation->vendor_contact_id = $contact->id;
$new_invitation->key = $this->createDbHash($purchase_order->company->db);
$new_invitation->save();
}
}
}
}
}
/* If no invitations have been created, this is our fail safe to maintain state*/
if ($purchase_order->invitations()->count() == 0)
$purchase_order->service()->createInvitations();
/* Recalculate invoice amounts */
$purchase_order = $purchase_order->calc()->getPurchaseOrder();
return $purchase_order; return $purchase_order;
} }
public function getInvitationByKey($key) :?PurchaseOrderInvitation
{
return PurchaseOrderInvitation::where('key', $key)->first();
}
public function getInvitation($invitation, $resource=null)
{
if (is_array($invitation) && ! array_key_exists('key', $invitation))
return false;
$invitation = PurchaseOrderInvitation::where('key', $invitation['key'])->first();
return $invitation;
}
} }

View File

@ -179,7 +179,7 @@ class InvoiceService
$this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run(); $this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run();
$this->setExchangeRate(); $this->setExchangeRate();
return $this; return $this;
} }
@ -565,10 +565,11 @@ class InvoiceService
return $this; return $this;
} }
public function adjustInventory() public function adjustInventory($old_invoice = [])
{ {
if($this->invoice->company->track_inventory) if($this->invoice->company->track_inventory)
AdjustProductInventory::dispatch($this->invoice->company, $this->invoice, null); AdjustProductInventory::dispatchNow($this->invoice->company, $this->invoice, $old_invoice);
return $this; return $this;
} }

View File

@ -50,8 +50,7 @@ class UpdateReminder extends AbstractService
$date_collection = collect(); $date_collection = collect();
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' && $this->settings->schedule_reminder1 == 'after_invoice_date') {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -59,8 +58,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' && $this->settings->schedule_reminder1 == 'before_due_date') {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -68,8 +66,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder1_sent) && if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' && $this->settings->schedule_reminder1 == 'after_due_date') {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -77,8 +74,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' && $this->settings->schedule_reminder2 == 'after_invoice_date') {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -86,8 +82,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' && $this->settings->schedule_reminder2 == 'before_due_date') {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -95,8 +90,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder2_sent) && if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' && $this->settings->schedule_reminder2 == 'after_due_date') {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -104,8 +98,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' && $this->settings->schedule_reminder3 == 'after_invoice_date') {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -113,8 +106,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' && $this->settings->schedule_reminder3 == 'before_due_date') {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
@ -122,8 +114,7 @@ class UpdateReminder extends AbstractService
} }
if (is_null($this->invoice->reminder3_sent) && if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' && $this->settings->schedule_reminder3 == 'after_due_date') {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset); $reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))

View File

@ -35,6 +35,9 @@ class Design extends BaseDesign
/** @var App\Models\Client */ /** @var App\Models\Client */
public $client; public $client;
/** @var App\Models\Vendor */
public $vendor;
/** Global state of the design, @var array */ /** Global state of the design, @var array */
public $context; public $context;
@ -69,6 +72,8 @@ class Design extends BaseDesign
const DELIVERY_NOTE = 'delivery_note'; const DELIVERY_NOTE = 'delivery_note';
const STATEMENT = 'statement'; const STATEMENT = 'statement';
const PURCHASE_ORDER = 'purchase_order';
public function __construct(string $design = null, array $options = []) public function __construct(string $design = null, array $options = [])
{ {
@ -113,6 +118,10 @@ class Design extends BaseDesign
'id' => 'client-details', 'id' => 'client-details',
'elements' => $this->clientDetails(), 'elements' => $this->clientDetails(),
], ],
'vendor-details' => [
'id' => 'vendor-details',
'elements' => $this->vendorDetails(),
],
'entity-details' => [ 'entity-details' => [
'id' => 'entity-details', 'id' => 'entity-details',
'elements' => $this->entityDetails(), 'elements' => $this->entityDetails(),
@ -188,10 +197,29 @@ class Design extends BaseDesign
return $elements; return $elements;
} }
public function vendorDetails(): array
{
$elements = [];
if(!$this->vendor)
return $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 public function clientDetails(): array
{ {
$elements = []; $elements = [];
if(!$this->client)
return $elements;
if ($this->type == self::DELIVERY_NOTE) { if ($this->type == self::DELIVERY_NOTE) {
$elements = [ $elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], ['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
@ -224,6 +252,8 @@ class Design extends BaseDesign
public function entityDetails(): array public function entityDetails(): array
{ {
if ($this->type === 'statement') { if ($this->type === 'statement') {
$s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale()); $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
@ -258,6 +288,12 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['credit_details']; $variables = $this->context['pdf_variables']['credit_details'];
} }
if($this->vendor){
$variables = $this->context['pdf_variables']['purchase_order_details'];
}
$elements = []; $elements = [];
// We don't want to show account balance or invoice total on PDF.. or any amount with currency. // We don't want to show account balance or invoice total on PDF.. or any amount with currency.
@ -763,7 +799,7 @@ class Design extends BaseDesign
} elseif (Str::startsWith($variable, '$custom_surcharge')) { } elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
$visible = $this->entity->{$_variable} > 0 || $this->entity->{$_variable} > '0'; $visible = $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0';
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [ $elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],

View File

@ -30,6 +30,10 @@ trait DesignHelpers
{ {
$this->syncPdfVariables(); $this->syncPdfVariables();
if (isset($this->context['vendor'])) {
$this->vendor = $this->context['vendor'];
}
if (isset($this->context['client'])) { if (isset($this->context['client'])) {
$this->client = $this->context['client']; $this->client = $this->context['client'];
} }

View File

@ -0,0 +1,66 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PurchaseOrder;
use App\Models\Vendor;
use App\Models\PurchaseOrder;
use App\Services\AbstractService;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Database\QueryException;
class ApplyNumber extends AbstractService
{
use GeneratesCounter;
public Vendor $vendor;
private PurchaseOrder $purchase_order;
private bool $completed = true;
public function __construct(Vendor $vendor, PurchaseOrder $purchase_order)
{
$this->vendor = $vendor;
$this->purchase_order = $purchase_order;
}
public function run()
{
if ($this->purchase_order->number != '') {
return $this->purchase_order;
}
$this->trySaving();
return $this->purchase_order;
}
private function trySaving()
{
$x=1;
do{
try{
$this->purchase_order->number = $this->getNextPurchaseOrderNumber($this->purchase_order);
$this->purchase_order->saveQuietly();
$this->completed = false;
}
catch(QueryException $e){
$x++;
if($x>10)
$this->completed = false;
}
}
while($this->completed);
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PurchaseOrder;
use App\Factory\PurchaseOrderInvitationFactory;
use App\Factory\VendorContactFactory;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Services\AbstractService;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Str;
class CreateInvitations extends AbstractService
{
use MakesHash;
public PurchaseOrder $purchase_order;
public function __construct(PurchaseOrder $purchase_order)
{
$this->purchase_order = $purchase_order;
}
private function createBlankContact()
{
$new_contact = VendorContactFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
$new_contact->vendor_id = $this->purchase_order->vendor_id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
public function run()
{
$contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get();
if($contacts->count() == 0){
$this->createBlankContact();
$this->purchase_order->refresh();
$contacts = $this->purchase_order->vendor->contacts;
}
$contacts->each(function ($contact) {
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
->where('vendor_contact_id', $contact->id)
->where('purchase_order_id', $this->purchase_order->id)
->withTrashed()
->first();
if (! $invitation) {
try{
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
$ii->key = $this->createDbHash($this->purchase_order->company->db);
$ii->purchase_order_id = $this->purchase_order->id;
$ii->vendor_contact_id = $contact->id;
$ii->save();
}
catch(\Exception $e){
nlog($e->getMessage());
}
} elseif (! $contact->send_email) {
$invitation->delete();
}
});
if($this->purchase_order->invitations()->count() == 0) {
if($contacts->count() == 0){
$contact = $this->createBlankContact();
}
else{
$contact = $contacts->first();
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
->where('vendor_contact_id', $contact->id)
->where('purchase_order_id', $this->purchase_order->id)
->withTrashed()
->first();
if($invitation){
$invitation->restore();
return $this->purchase_order;
}
}
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
$ii->key = $this->createDbHash($this->purchase_order->company->db);
$ii->purchase_order_id = $this->purchase_order->id;
$ii->vendor_contact_id = $contact->id;
$ii->save();
}
return $this->purchase_order;
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PurchaseOrder;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\PurchaseOrder;
use App\Models\VendorContact;
use App\Services\AbstractService;
use App\Utils\TempFile;
use Illuminate\Support\Facades\Storage;
class GetPurchaseOrderPdf extends AbstractService
{
public function __construct(PurchaseOrder $purchase_order, VendorContact $contact = null)
{
$this->purchase_order = $purchase_order;
$this->contact = $contact;
}
public function run()
{
if (! $this->contact) {
$this->contact = $this->purchase_order->vendor->contacts()->where('send_email', true)->first();
}
$invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first();
if(!$invitation)
$invitation = $this->purchase_order->invitations()->first();
$path = $this->purchase_order->vendor->purchase_order_filepath($invitation);
$file_path = $path.$this->purchase_order->numberFormatter().'.pdf';
// $disk = 'public';
$disk = config('filesystems.default');
$file = Storage::disk($disk)->exists($file_path);
if (! $file) {
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation);
}
return $file_path;
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PurchaseOrder;
use App\Models\PurchaseOrder;
use App\Utils\Ninja;
class MarkSent
{
private $vendor;
private $purchase_order;
public function __construct($vendor, $purchase_order)
{
$this->vendor = $vendor;
$this->purchase_order = $purchase_order;
}
public function run()
{
/* Return immediately if status is not draft */
if ($this->purchase_order->status_id != PurchaseOrder::STATUS_DRAFT) {
return $this->purchase_order;
}
$this->purchase_order->markInvitationsSent();
$this->purchase_order
->service()
->setStatus(PurchaseOrder::STATUS_SENT)
->applyNumber()
// ->adjustBalance($this->purchase_order->amount)
// ->touchPdf()
->save();
return $this->purchase_order;
}
}

View File

@ -13,6 +13,9 @@ namespace App\Services\PurchaseOrder;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Services\PurchaseOrder\ApplyNumber;
use App\Services\PurchaseOrder\CreateInvitations;
use App\Services\PurchaseOrder\GetPurchaseOrderPdf;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
class PurchaseOrderService class PurchaseOrderService
@ -21,11 +24,63 @@ class PurchaseOrderService
public PurchaseOrder $purchase_order; public PurchaseOrder $purchase_order;
public function __construct($purchase_order) public function __construct(PurchaseOrder $purchase_order)
{ {
$this->purchase_order = $purchase_order; $this->purchase_order = $purchase_order;
} }
public function createInvitations()
{
$this->purchase_order = (new CreateInvitations($this->purchase_order))->run();
return $this;
}
public function applyNumber()
{
$this->invoice = (new ApplyNumber($this->purchase_order->vendor, $this->purchase_order))->run();
return $this;
}
public function fillDefaults()
{
// $settings = $this->purchase_order->client->getMergedSettings();
// //TODO implement design, footer, terms
// /* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
// if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
// $this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
// if (!isset($this->purchase_order->public_notes))
// $this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
return $this;
}
public function getPurchaseOrderPdf($contact = null)
{
return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run();
}
public function setStatus($status)
{
$this->purchase_order->status_id = $status;
return $this;
}
public function markSent()
{
$this->purchase_order = (new MarkSent($this->purchase_order->vendor, $this->purchase_order))->run();
return $this;
}
/** /**
* Saves the purchase order. * Saves the purchase order.
* @return \App\Models\PurchaseOrder object * @return \App\Models\PurchaseOrder object
@ -37,21 +92,4 @@ class PurchaseOrderService
return $this->purchase_order; return $this->purchase_order;
} }
public function fillDefaults()
{
$settings = $this->purchase_order->client->getMergedSettings();
//TODO implement design, footer, terms
/* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
$this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
if (!isset($this->purchase_order->public_notes))
$this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
return $this;
}
} }

View File

@ -70,6 +70,7 @@ class ConvertQuote
$invoice->service() $invoice->service()
->fillDefaults() ->fillDefaults()
->adjustInventory()
->save(); ->save();
$quote->invoice_id = $invoice->id; $quote->invoice_id = $invoice->id;

View File

@ -0,0 +1,32 @@
<?php
namespace App\Transformers;
use App\Models\PurchaseOrderInvitation;
use App\Utils\Traits\MakesHash;
class PurchaseOrderInvitationTransformer extends EntityTransformer
{
use MakesHash;
public function transform(PurchaseOrderInvitation $invitation)
{
return [
'id' => $this->encodePrimaryKey($invitation->id),
'vendor_contact_id' => $this->encodePrimaryKey($invitation->vendor_contact_id),
'key' => $invitation->key,
'link' => $invitation->getLink() ?: '',
'sent_date' => $invitation->sent_date ?: '',
'viewed_date' => $invitation->viewed_date ?: '',
'opened_date' => $invitation->opened_date ?: '',
'updated_at' => (int)$invitation->updated_at,
'archived_at' => (int)$invitation->deleted_at,
'created_at' => (int)$invitation->created_at,
'email_status' => $invitation->email_status ?: '',
'email_error' => (string)$invitation->email_error,
];
}
}

View File

@ -13,12 +13,24 @@ namespace App\Transformers;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
class PurchaseOrderTransformer extends EntityTransformer class PurchaseOrderTransformer extends EntityTransformer
{ {
use MakesHash; use MakesHash;
protected $defaultIncludes = [
'invitations',
];
public function includeInvitations(PurchaseOrder $purchase_order)
{
$transformer = new PurchaseOrderInvitationTransformer($this->serializer);
return $this->includeCollection($purchase_order->invitations, $transformer, PurchaseOrderInvitation::class);
}
public function transform(PurchaseOrder $purchase_order) public function transform(PurchaseOrder $purchase_order)
{ {
return [ return [
@ -26,19 +38,18 @@ class PurchaseOrderTransformer extends EntityTransformer
'user_id' => $this->encodePrimaryKey($purchase_order->user_id), 'user_id' => $this->encodePrimaryKey($purchase_order->user_id),
'project_id' => $this->encodePrimaryKey($purchase_order->project_id), 'project_id' => $this->encodePrimaryKey($purchase_order->project_id),
'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id), 'assigned_user_id' => $this->encodePrimaryKey($purchase_order->assigned_user_id),
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id), 'vendor_id' => (string)$this->encodePrimaryKey($purchase_order->vendor_id),
'amount' => (float) $purchase_order->amount, 'amount' => (float)$purchase_order->amount,
'balance' => (float) $purchase_order->balance, 'balance' => (float)$purchase_order->balance,
'client_id' => (string) $this->encodePrimaryKey($purchase_order->client_id), 'client_id' => (string)$this->encodePrimaryKey($purchase_order->client_id),
'vendor_id' => (string) $this->encodePrimaryKey($purchase_order->vendor_id), 'status_id' => (string)($purchase_order->status_id ?: 1),
'status_id' => (string) ($purchase_order->status_id ?: 1), 'design_id' => (string)$this->encodePrimaryKey($purchase_order->design_id),
'design_id' => (string) $this->encodePrimaryKey($purchase_order->design_id), 'created_at' => (int)$purchase_order->created_at,
'created_at' => (int) $purchase_order->created_at, 'updated_at' => (int)$purchase_order->updated_at,
'updated_at' => (int) $purchase_order->updated_at, 'archived_at' => (int)$purchase_order->deleted_at,
'archived_at' => (int) $purchase_order->deleted_at, 'is_deleted' => (bool)$purchase_order->is_deleted,
'is_deleted' => (bool) $purchase_order->is_deleted,
'number' => $purchase_order->number ?: '', 'number' => $purchase_order->number ?: '',
'discount' => (float) $purchase_order->discount, 'discount' => (float)$purchase_order->discount,
'po_number' => $purchase_order->po_number ?: '', 'po_number' => $purchase_order->po_number ?: '',
'date' => $purchase_order->date ?: '', 'date' => $purchase_order->date ?: '',
'last_sent_date' => $purchase_order->last_sent_date ?: '', 'last_sent_date' => $purchase_order->last_sent_date ?: '',
@ -51,36 +62,36 @@ class PurchaseOrderTransformer extends EntityTransformer
'terms' => $purchase_order->terms ?: '', 'terms' => $purchase_order->terms ?: '',
'public_notes' => $purchase_order->public_notes ?: '', 'public_notes' => $purchase_order->public_notes ?: '',
'private_notes' => $purchase_order->private_notes ?: '', 'private_notes' => $purchase_order->private_notes ?: '',
'uses_inclusive_taxes' => (bool) $purchase_order->uses_inclusive_taxes, 'uses_inclusive_taxes' => (bool)$purchase_order->uses_inclusive_taxes,
'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '', 'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '',
'tax_rate1' => (float) $purchase_order->tax_rate1, 'tax_rate1' => (float)$purchase_order->tax_rate1,
'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '', 'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '',
'tax_rate2' => (float) $purchase_order->tax_rate2, 'tax_rate2' => (float)$purchase_order->tax_rate2,
'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '', 'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '',
'tax_rate3' => (float) $purchase_order->tax_rate3, 'tax_rate3' => (float)$purchase_order->tax_rate3,
'total_taxes' => (float) $purchase_order->total_taxes, 'total_taxes' => (float)$purchase_order->total_taxes,
'is_amount_discount' => (bool) ($purchase_order->is_amount_discount ?: false), 'is_amount_discount' => (bool)($purchase_order->is_amount_discount ?: false),
'footer' => $purchase_order->footer ?: '', 'footer' => $purchase_order->footer ?: '',
'partial' => (float) ($purchase_order->partial ?: 0.0), 'partial' => (float)($purchase_order->partial ?: 0.0),
'partial_due_date' => $purchase_order->partial_due_date ?: '', 'partial_due_date' => $purchase_order->partial_due_date ?: '',
'custom_value1' => (string) $purchase_order->custom_value1 ?: '', 'custom_value1' => (string)$purchase_order->custom_value1 ?: '',
'custom_value2' => (string) $purchase_order->custom_value2 ?: '', 'custom_value2' => (string)$purchase_order->custom_value2 ?: '',
'custom_value3' => (string) $purchase_order->custom_value3 ?: '', 'custom_value3' => (string)$purchase_order->custom_value3 ?: '',
'custom_value4' => (string) $purchase_order->custom_value4 ?: '', 'custom_value4' => (string)$purchase_order->custom_value4 ?: '',
'has_tasks' => (bool) $purchase_order->has_tasks, 'has_tasks' => (bool)$purchase_order->has_tasks,
'has_expenses' => (bool) $purchase_order->has_expenses, 'has_expenses' => (bool)$purchase_order->has_expenses,
'custom_surcharge1' => (float) $purchase_order->custom_surcharge1, 'custom_surcharge1' => (float)$purchase_order->custom_surcharge1,
'custom_surcharge2' => (float) $purchase_order->custom_surcharge2, 'custom_surcharge2' => (float)$purchase_order->custom_surcharge2,
'custom_surcharge3' => (float) $purchase_order->custom_surcharge3, 'custom_surcharge3' => (float)$purchase_order->custom_surcharge3,
'custom_surcharge4' => (float) $purchase_order->custom_surcharge4, 'custom_surcharge4' => (float)$purchase_order->custom_surcharge4,
'custom_surcharge_tax1' => (bool) $purchase_order->custom_surcharge_tax1, 'custom_surcharge_tax1' => (bool)$purchase_order->custom_surcharge_tax1,
'custom_surcharge_tax2' => (bool) $purchase_order->custom_surcharge_tax2, 'custom_surcharge_tax2' => (bool)$purchase_order->custom_surcharge_tax2,
'custom_surcharge_tax3' => (bool) $purchase_order->custom_surcharge_tax3, 'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3,
'custom_surcharge_tax4' => (bool) $purchase_order->custom_surcharge_tax4, 'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4,
'line_items' => $purchase_order->line_items ?: (array) [], 'line_items' => $purchase_order->line_items ?: (array)[],
'entity_type' => 'credit', 'entity_type' => 'purchase_order',
'exchange_rate' => (float) $purchase_order->exchange_rate, 'exchange_rate' => (float)$purchase_order->exchange_rate,
'paid_to_date' => (float) $purchase_order->paid_to_date, 'paid_to_date' => (float)$purchase_order->paid_to_date,
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id), 'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),
]; ];
} }

View File

@ -18,6 +18,7 @@ use App\Models\Quote;
use App\Models\QuoteInvitation; use App\Models\QuoteInvitation;
use App\Transformers\ActivityTransformer; use App\Transformers\ActivityTransformer;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use League\Fractal\Resource\Item;
class QuoteTransformer extends EntityTransformer class QuoteTransformer extends EntityTransformer
{ {
@ -30,6 +31,7 @@ class QuoteTransformer extends EntityTransformer
protected $availableIncludes = [ protected $availableIncludes = [
'activities', 'activities',
'client',
]; ];
public function includeActivities(Quote $quote) public function includeActivities(Quote $quote)
@ -61,13 +63,6 @@ class QuoteTransformer extends EntityTransformer
return $this->includeCollection($quote->payments, $transformer, ENTITY_PAYMENT); return $this->includeCollection($quote->payments, $transformer, ENTITY_PAYMENT);
} }
public function includeClient(quote $quote)
{
$transformer = new ClientTransformer($this->account, $this->serializer);
return $this->includeItem($quote->client, $transformer, ENTITY_CLIENT);
}
public function includeExpenses(quote $quote) public function includeExpenses(quote $quote)
{ {
$transformer = new ExpenseTransformer($this->account, $this->serializer); $transformer = new ExpenseTransformer($this->account, $this->serializer);
@ -83,6 +78,13 @@ class QuoteTransformer extends EntityTransformer
return $this->includeCollection($quote->documents, $transformer, Document::class); return $this->includeCollection($quote->documents, $transformer, Document::class);
} }
public function includeClient(Quote $quote): Item
{
$transformer = new ClientTransformer($this->serializer);
return $this->includeItem($quote->client, $transformer, Client::class);
}
public function transform(Quote $quote) public function transform(Quote $quote)
{ {
return [ return [

View File

@ -52,7 +52,7 @@ class Helpers
* *
* @return null|string * @return null|string
*/ */
public function formatCustomFieldValue($custom_fields, $field, $value, Client $client = null): ?string public function formatCustomFieldValue($custom_fields, $field, $value, $entity = null): ?string
{ {
$custom_field = ''; $custom_field = '';
@ -67,7 +67,7 @@ class Helpers
switch ($custom_field) { switch ($custom_field) {
case 'date': case 'date':
return is_null($client) ? $value : $this->translateDate($value, $client->date_format(), $client->locale()); return is_null($entity) ? $value : $this->translateDate($value, $entity->date_format(), $entity->locale());
break; break;
case 'switch': case 'switch':
@ -104,15 +104,15 @@ class Helpers
* Process reserved keywords on PDF. * Process reserved keywords on PDF.
* *
* @param string $value * @param string $value
* @param Client $client * @param Client|Company $entity
* @return null|string * @return null|string
*/ */
public static function processReservedKeywords(?string $value, Client $client): ?string public static function processReservedKeywords(?string $value, $entity): ?string
{ {
if(!$value) if(!$value)
return ''; return '';
Carbon::setLocale($client->locale()); Carbon::setLocale($entity->locale());
$replacements = [ $replacements = [
'literal' => [ 'literal' => [
@ -121,21 +121,21 @@ class Helpers
':QUARTER' => 'Q' . now()->quarter, ':QUARTER' => 'Q' . now()->quarter,
':WEEK_BEFORE' => \sprintf( ':WEEK_BEFORE' => \sprintf(
'%s %s %s', '%s %s %s',
Carbon::now()->subDays(7)->translatedFormat($client->date_format()), Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
ctrans('texts.to'), ctrans('texts.to'),
Carbon::now()->translatedFormat($client->date_format()) Carbon::now()->translatedFormat($entity->date_format())
), ),
':WEEK_AHEAD' => \sprintf( ':WEEK_AHEAD' => \sprintf(
'%s %s %s', '%s %s %s',
Carbon::now()->addDays(7)->translatedFormat($client->date_format()), Carbon::now()->addDays(7)->translatedFormat($entity->date_format()),
ctrans('texts.to'), ctrans('texts.to'),
Carbon::now()->addDays(14)->translatedFormat($client->date_format()) Carbon::now()->addDays(14)->translatedFormat($entity->date_format())
), ),
':WEEK' => \sprintf( ':WEEK' => \sprintf(
'%s %s %s', '%s %s %s',
Carbon::now()->translatedFormat($client->date_format()), Carbon::now()->translatedFormat($entity->date_format()),
ctrans('texts.to'), ctrans('texts.to'),
Carbon::now()->addDays(7)->translatedFormat($client->date_format()) Carbon::now()->addDays(7)->translatedFormat($entity->date_format())
), ),
], ],
'raw' => [ 'raw' => [

View File

@ -18,6 +18,7 @@ use App\Models\Expense;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Models\Project; use App\Models\Project;
use App\Models\PurchaseOrder;
use App\Models\Quote; use App\Models\Quote;
use App\Models\RecurringExpense; use App\Models\RecurringExpense;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
@ -44,8 +45,8 @@ trait GeneratesCounter
$is_client_counter = false; $is_client_counter = false;
$counter_string = $this->getEntityCounter($entity, $client); $counter_string = $this->getEntityCounter($entity, $client);
$pattern = $this->getNumberPattern($entity, $client); $pattern = $this->getNumberPattern($entity, $client);
if ((strpos($pattern, 'clientCounter') !== false) || (strpos($pattern, 'client_counter') !==false) ) { if ((strpos($pattern, 'clientCounter') !== false) || (strpos($pattern, 'client_counter') !==false) ) {
@ -71,9 +72,9 @@ trait GeneratesCounter
$counter_entity = $client->company; $counter_entity = $client->company;
} }
//If it is a quote - we need to //If it is a quote - we need to
$pattern = $this->getNumberPattern($entity, $client); $pattern = $this->getNumberPattern($entity, $client);
if(strlen($pattern) > 1 && (stripos($pattern, 'counter') === false)){ if(strlen($pattern) > 1 && (stripos($pattern, 'counter') === false)){
$pattern = $pattern.'{$counter}'; $pattern = $pattern.'{$counter}';
} }
@ -127,9 +128,9 @@ trait GeneratesCounter
break; break;
case Quote::class: case Quote::class:
if ($this->hasSharedCounter($client, 'quote')) if ($this->hasSharedCounter($client, 'quote'))
return 'invoice_number_counter'; return 'invoice_number_counter';
return 'quote_number_counter'; return 'quote_number_counter';
break; break;
case RecurringInvoice::class: case RecurringInvoice::class:
@ -145,14 +146,21 @@ trait GeneratesCounter
return 'payment_number_counter'; return 'payment_number_counter';
break; break;
case Credit::class: case Credit::class:
if ($this->hasSharedCounter($client, 'credit')) if ($this->hasSharedCounter($client, 'credit'))
return 'invoice_number_counter'; return 'invoice_number_counter';
return 'credit_number_counter'; return 'credit_number_counter';
break; break;
case Project::class: case Project::class:
return 'project_number_counter'; return 'project_number_counter';
break; break;
case PurchaseOrder::class:
return 'purchase_order_number_counter';
break;
case PurchaseOrder::class:
return 'purchase_order_number_counter';
break;
default: default:
return 'default_number_counter'; return 'default_number_counter';
@ -345,6 +353,23 @@ trait GeneratesCounter
} }
public function getNextPurchaseOrderNumber(PurchaseOrder $purchase_order) :string
{
$this->resetCompanyCounters($purchase_order->company);
$counter = $purchase_order->company->settings->purchase_order_number_counter;
$setting_entity = $purchase_order->company->settings->purchase_order_number_counter;
$purchase_order_number = $this->checkEntityNumber(PurchaseOrder::class, $purchase_order, $counter, $purchase_order->company->settings->counter_padding, $purchase_order->company->settings->purchase_order_number_pattern);
$this->incrementCounter($purchase_order->company, 'purchase_order_number_counter');
$entity_number = $purchase_order_number;
return $this->replaceUserVars($purchase_order, $entity_number);
}
/** /**
* Gets the next expense number. * Gets the next expense number.
* *
@ -385,7 +410,7 @@ trait GeneratesCounter
* *
* @return bool True if has shared counter, False otherwise. * @return bool True if has shared counter, False otherwise.
*/ */
public function hasSharedCounter(Client $client, string $type = 'quote') : bool public function hasSharedCounter(Client $client, string $type = 'quote') : bool
{ {
if($type == 'quote') if($type == 'quote')
return (bool) $client->getSetting('shared_invoice_quote_counter'); return (bool) $client->getSetting('shared_invoice_quote_counter');
@ -438,9 +463,9 @@ trait GeneratesCounter
public function checkNumberAvailable($class, $entity, $number) :bool public function checkNumberAvailable($class, $entity, $number) :bool
{ {
if ($entity = $class::whereCompanyId($entity->company_id)->whereNumber($number)->withTrashed()->exists()) if ($entity = $class::whereCompanyId($entity->company_id)->whereNumber($number)->withTrashed()->exists())
return false; return false;
return true; return true;
} }
@ -504,7 +529,7 @@ trait GeneratesCounter
if($reset_counter_frequency == 0) if($reset_counter_frequency == 0)
return; return;
$timezone = Timezone::find($client->getSetting('timezone_id')); $timezone = Timezone::find($client->getSetting('timezone_id'));
$reset_date = Carbon::parse($client->getSetting('reset_counter_date'), $timezone->name); $reset_date = Carbon::parse($client->getSetting('reset_counter_date'), $timezone->name);
@ -558,6 +583,7 @@ trait GeneratesCounter
$settings->invoice_number_counter = 1; $settings->invoice_number_counter = 1;
$settings->quote_number_counter = 1; $settings->quote_number_counter = 1;
$settings->credit_number_counter = 1; $settings->credit_number_counter = 1;
$settings->purchase_order_number_counter = 1;
$client->company->settings = $settings; $client->company->settings = $settings;
$client->company->save(); $client->company->save();
@ -622,6 +648,7 @@ trait GeneratesCounter
$settings->task_number_counter = 1; $settings->task_number_counter = 1;
$settings->expense_number_counter = 1; $settings->expense_number_counter = 1;
$settings->recurring_expense_number_counter =1; $settings->recurring_expense_number_counter =1;
$settings->purchase_order_number_counter = 1;
$company->settings = $settings; $company->settings = $settings;
$company->save(); $company->save();
@ -644,7 +671,7 @@ trait GeneratesCounter
$search = []; $search = [];
$replace = []; $replace = [];
$search[] = '{$counter}'; $search[] = '{$counter}';
$replace[] = $counter; $replace[] = $counter;
@ -659,7 +686,7 @@ trait GeneratesCounter
$search[] = '{$year}'; $search[] = '{$year}';
$replace[] = Carbon::now($entity->company->timezone()->name)->format('Y'); $replace[] = Carbon::now($entity->company->timezone()->name)->format('Y');
if (strstr($pattern, '{$user_id}') || strstr($pattern, '{$userId}')) { if (strstr($pattern, '{$user_id}') || strstr($pattern, '{$userId}')) {
$user_id = $entity->user_id ? $entity->user_id : 0; $user_id = $entity->user_id ? $entity->user_id : 0;
$search[] = '{$user_id}'; $search[] = '{$user_id}';
@ -683,7 +710,7 @@ trait GeneratesCounter
$search[] = '{$vendor_id_number}'; $search[] = '{$vendor_id_number}';
$replace[] = $entity->id_number; $replace[] = $entity->id_number;
} }
if ($entity instanceof Expense) { if ($entity instanceof Expense) {
if ($entity->vendor) { if ($entity->vendor) {
$search[] = '{$vendor_id_number}'; $search[] = '{$vendor_id_number}';
@ -708,7 +735,7 @@ trait GeneratesCounter
$search[] = '{$expense_id_number}'; $search[] = '{$expense_id_number}';
$replace[] = $entity->id_number; $replace[] = $entity->id_number;
} }
if ($entity->client || ($entity instanceof Client)) { if ($entity->client || ($entity instanceof Client)) {
$client = $entity->client ?: $entity; $client = $entity->client ?: $entity;

View File

@ -0,0 +1,714 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Utils;
use App\Models\Country;
use App\Models\CreditInvitation;
use App\Models\GatewayType;
use App\Models\InvoiceInvitation;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use App\Utils\transformTranslations;
use Exception;
use Illuminate\Support\Facades\App;
class VendorHtmlEngine
{
use MakesDates;
public $entity;
public $invitation;
public $vendor;
public $contact;
public $company;
public $settings;
public $entity_calc;
public $entity_string;
private $helpers;
public function __construct($invitation)
{
$this->invitation = $invitation;
$this->entity_string = $this->resolveEntityString();
$this->entity = $invitation->purchase_order;
$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()) ?: '&nbsp;', 'label' => ctrans('texts.date')];
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: '&nbsp;', 'label' => ctrans('texts.due_date')];
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: '&nbsp;', '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()) ?: '&nbsp;', 'label' => ctrans('texts.payment_due')];
$data['$poNumber'] = ['value' => $this->entity->po_number, 'label' => ctrans('texts.po_number')];
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.date')];
$data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')];
$data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')];
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_number')];
$data['$number_short'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_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' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', '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()) ?: '&nbsp;', 'label' => ctrans('texts.date')];
$data['$purchase_order.number'] = &$data['$number'];
$data['$purchase_order.date'] = &$data['$date'];
$data['$purchase_order.po_number'] = &$data['$poNumber'];
$data['$purchase_order.due_date'] = &$data['$due_date'];
$data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>''];
$data['$entity_number'] = &$data['$number'];
$data['$discount'] = ['value' => $this->entity->discount, 'label' => ctrans('texts.discount')];
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->vendor) ?: '&nbsp;', '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) ?: '&nbsp;', 'label' => ctrans('texts.net_subtotal')];
else
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.net_subtotal')];
if ($this->entity->partial > 0) {
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: '&nbsp;', '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()) ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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['$balance_due'] = $data['$balance_due'];
$data['$outstanding'] = &$data['$balance_due'];
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.partial_due')];
$data['$partial'] = &$data['$partial_due'];
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.total')];
$data['$purchase_order.total'] = &$data['$total'];
$data['$amount'] = &$data['$total'];
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.balance')];
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.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['$public_notes'] = ['value' => $this->entity->public_notes, 'label' => ctrans("texts.public_notes")];
$data['$entity.public_notes'] = &$data['$public_notes'];
$data['$notes'] = &$data['$public_notes'];
$data['$purchase_order.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order1', $this->entity->custom_value1, $this->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order4')];
$data['$vendor1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor1', $this->vendor->custom_value1, $this->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', '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->company) ?: '&nbsp;', '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 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$address2'] = ['value' => $this->vendor->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$id_number'] = ['value' => $this->vendor->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')];
$data['$vendor.number'] = ['value' => $this->vendor->number ?: '&nbsp;', 'label' => ctrans('texts.number')];
$data['$vat_number'] = ['value' => $this->vendor->vat_number ?: '&nbsp;', 'label' => ctrans('texts.vat_number')];
$data['$website'] = ['value' => $this->vendor->present()->website() ?: '&nbsp;', 'label' => ctrans('texts.website')];
$data['$phone'] = ['value' => $this->vendor->present()->phone() ?: '&nbsp;', '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() ?: '&nbsp;', '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() ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$vendor.address'] = &$data['$vendor_address'];
$data['$vendor.postal_code'] = ['value' => $this->vendor->postal_code ?: '&nbsp;', 'label' => ctrans('texts.postal_code')];
$data['$vendor.public_notes'] = ['value' => $this->vendor->public_notes ?: '&nbsp;', 'label' => ctrans('texts.notes')];
$data['$vendor.city'] = ['value' => $this->vendor->city ?: '&nbsp;', 'label' => ctrans('texts.city')];
$data['$vendor.state'] = ['value' => $this->vendor->state ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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 : '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact1')];
$data['$contact.custom2'] = ['value' => isset($this->contact) ? $this->contact->custom_value2 : '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact2')];
$data['$contact.custom3'] = ['value' => isset($this->contact) ? $this->contact->custom_value3 : '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact3')];
$data['$contact.custom4'] = ['value' => isset($this->contact) ? $this->contact->custom_value4 : '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$company.address2'] = ['value' => $this->settings->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$company.city'] = ['value' => $this->settings->city ?: '&nbsp;', 'label' => ctrans('texts.city')];
$data['$company.state'] = ['value' => $this->settings->state ?: '&nbsp;', 'label' => ctrans('texts.state')];
$data['$company.postal_code'] = ['value' => $this->settings->postal_code ?: '&nbsp;', '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 ?: '&nbsp;', 'label' => ctrans('texts.phone')];
$data['$company.email'] = ['value' => $this->settings->email ?: '&nbsp;', 'label' => ctrans('texts.email')];
$data['$company.vat_number'] = ['value' => $this->settings->vat_number ?: '&nbsp;', 'label' => ctrans('texts.vat_number')];
$data['$company.id_number'] = ['value' => $this->settings->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')];
$data['$company.website'] = ['value' => $this->settings->website ?: '&nbsp;', 'label' => ctrans('texts.website')];
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$signature'] = ['value' => $this->settings->email_signature ?: '&nbsp;', 'label' => ''];
$data['$emailSignature'] = &$data['$signature'];
$logo = $this->company->present()->logo_base64($this->settings);
$data['$company.logo'] = ['value' => $logo ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', '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) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->vendor) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->vendor) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
$data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->vendor) ?: '&nbsp;', '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')];
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' => '&nbsp;', 'label' => ctrans('texts.payment_date')];
$data['$method'] = ['value' => '&nbsp;', '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 .= '<span>'.$tax['name'].'</span>';
}
return $data;
}
private function totalTaxValues() :string
{
$data = '';
if (! $this->entity_calc->getTotalTaxMap()) {
return $data;
}
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
$data .= '<span>'.Number::formatMoney($tax['total'], $this->vendor).'</span>';
}
return $data;
}
private function lineTaxLabels() :string
{
$tax_map = $this->entity_calc->getTaxMap();
$data = '';
foreach ($tax_map as $tax) {
$data .= '<span>'.$tax['name'].'</span>';
}
return $data;
}
private function getCountryName() :string
{
$country = Country::find($this->settings->country_id);
if ($country) {
return ctrans('texts.country_' . $country->name);
}
return '&nbsp;';
}
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 '&nbsp;';
}
/**
* 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 <tr> rows with line item
* aggregate data
*/
private function makeLineTaxes() :string
{
$tax_map = $this->entity_calc->getTaxMap();
$data = '';
foreach ($tax_map as $tax) {
$data .= '<tr class="line_taxes">';
$data .= '<td>'.$tax['name'].'</td>';
$data .= '<td>'.Number::formatMoney($tax['total'], $this->vendor).'</td></tr>';
}
return $data;
}
private function lineTaxValues() :string
{
$tax_map = $this->entity_calc->getTaxMap();
$data = '';
foreach ($tax_map as $tax) {
$data .= '<span>'.Number::formatMoney($tax['total'], $this->vendor).'</span>';
}
return $data;
}
private function makeTotalTaxes() :string
{
$data = '';
if (! $this->entity_calc->getTotalTaxMap()) {
return $data;
}
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
$data .= '<tr>';
$data .= '<td colspan="{ count($this->entity->company->settings->pdf_variables->total_columns) - 2 }"></td>';
$data .= '<td>'.$tax['name'].'</td>';
$data .= '<td>'.Number::formatMoney($tax['total'], $this->vendor).'</td></tr>';
}
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();
}
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.96', 'app_version' => '5.3.97',
'app_tag' => '5.3.96', 'app_tag' => '5.3.97',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,16 +6,18 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Models\Company; use App\Models\Company;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
class CompanyFactory extends Factory class CompanyFactory extends Factory
{ {
use MakesHash;
/** /**
* The name of the factory's corresponding model. * The name of the factory's corresponding model.
* *
@ -41,6 +43,7 @@ class CompanyFactory extends Factory
'enabled_modules' => config('ninja.enabled_modules'), 'enabled_modules' => config('ninja.enabled_modules'),
'custom_fields' => (object) [ 'custom_fields' => (object) [
], ],
'company_key' => $this->createHash(),
]; ];
} }
} }

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Factories;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class PurchaseOrderInvitationFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = PurchaseOrderInvitation::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'key' => Str::random(40),
];
}
}

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -7,7 +7,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

View File

@ -6,7 +6,7 @@
* *
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com) * @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
* *
* @license https://opensource.org/licenses/AAL * @license https://www.elastic.co/licensing/elastic-license
*/ */
namespace Database\Factories; namespace Database\Factories;

Some files were not shown because too many files have changed in this diff Show More