1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 13:12:50 +01:00

Merge pull request #7541 from turbo124/v5-develop

v5.3.99
This commit is contained in:
David Bomba 2022-06-11 15:17:55 +10:00 committed by GitHub
commit db33d3cab3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 920 additions and 77 deletions

View File

@ -1 +1 @@
5.3.98
5.3.99

View File

@ -62,7 +62,7 @@ class EmailTemplateDefaults
case 'email_template_custom3':
return self::emailInvoiceTemplate();
case 'email_template_purchase_order':
return self::emailPurchaseOrderSubject();
return self::emailPurchaseOrderTemplate();
break;
/* Subject */
@ -157,7 +157,7 @@ class EmailTemplateDefaults
public static function emailPurchaseOrderSubject()
{
return ctrans('texts.purchase_order_subject', ['number' => '$number']);
return ctrans('texts.purchase_order_subject', ['number' => '$number', 'account' => '$account']);
}
public static function emailPurchaseOrderTemplate()

View File

@ -13,6 +13,7 @@ namespace App\Events\PurchaseOrder;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Queue\SerializesModels;
/**
@ -25,7 +26,7 @@ class PurchaseOrderWasEmailed
/**
* @var PurchaseOrder
*/
public $purchase_order;
public $invitation;
public $company;
@ -38,9 +39,9 @@ class PurchaseOrderWasEmailed
* @param Company $company
* @param array $event_vars
*/
public function __construct(PurchaseOrder $purchase_order, Company $company, array $event_vars)
public function __construct(PurchaseOrderInvitation $invitation, Company $company, array $event_vars)
{
$this->purchase_order = $purchase_order;
$this->invitation = $invitation;
$this->company = $company;
$this->event_vars = $event_vars;
}

View File

@ -154,9 +154,11 @@ class EntityViewController extends Controller
if (! $invitation->viewed_date) {
$invitation->markViewed();
event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars()));
if(!session()->get('is_silent'))
event(new InvitationWasViewed($invitation->{$request->entity_type}, $invitation, $invitation->{$request->entity_type}->company, Ninja::eventVars()));
$this->fireEntityViewedEvent($invitation, $request->entity_type);
if(!session()->get('is_silent'))
$this->fireEntityViewedEvent($invitation, $request->entity_type);
}
return redirect()->route('client.'.$request->entity_type.'.show', [$request->entity_type => $this->encodePrimaryKey($invitation->{$key})]);

View File

@ -129,9 +129,11 @@ class InvitationController extends Controller
if (auth()->guard('contact')->user() && ! request()->has('silent') && ! $invitation->viewed_date) {
$invitation->markViewed();
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
if(!session()->get('is_silent'))
event(new InvitationWasViewed($invitation->{$entity}, $invitation, $invitation->{$entity}->company, Ninja::eventVars()));
$this->fireEntityViewedEvent($invitation, $entity);
if(!session()->get('is_silent'))
$this->fireEntityViewedEvent($invitation, $entity);
}
else{
$is_silent = 'true';

View File

@ -61,7 +61,7 @@ class InvoiceController extends Controller
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();
if ($invitation && auth()->guard('contact') && ! request()->has('silent') && ! $invitation->viewed_date) {
if ($invitation && auth()->guard('contact') && !session()->get('is_silent') && ! $invitation->viewed_date) {
$invitation->markViewed();

View File

@ -24,6 +24,8 @@ use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
use App\Jobs\Invoice\ZipInvoices;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
use App\Models\Client;
use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository;
@ -31,6 +33,7 @@ use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class PurchaseOrderController extends BaseController
{
@ -183,6 +186,7 @@ class PurchaseOrderController extends BaseController
$purchase_order = $purchase_order->service()
->fillDefaults()
->triggeredActions($request)
->save();
event(new PurchaseOrderWasCreated($purchase_order, $purchase_order->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -485,7 +489,7 @@ class PurchaseOrderController extends BaseController
* Download Purchase Order/s
*/
if ($action == 'bulk_download' && $purchase_orders->count() > 1) {
if ($action == 'bulk_download' && $purchase_orders->count() >= 1) {
$purchase_orders->each(function ($purchase_order) {
if (auth()->user()->cannot('view', $purchase_order)) {
nlog("access denied");
@ -493,7 +497,7 @@ class PurchaseOrderController extends BaseController
}
});
ZipInvoices::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
ZipPurchaseOrders::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
@ -579,7 +583,7 @@ class PurchaseOrderController extends BaseController
*/
public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action)
{
return $this->performAction($invoice, $action);
return $this->performAction($purchase_order, $action);
}
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
@ -627,8 +631,13 @@ class PurchaseOrderController extends BaseController
case 'email':
//check query parameter for email_type and set the template else use calculateTemplate
PurchaseOrderEmail::dispatch($purchase_order, $purchase_order->company);
if (! $bulk) {
return response()->json(['message' => 'email sent'], 200);
}
default:
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break;

View File

@ -60,6 +60,8 @@ class CheckClientExistence
session()->put('multiple_contacts', $multiple_contacts);
session()->put('is_silent', request()->has('silent'));
return $next($request);
}
}

View File

@ -23,7 +23,7 @@ class ShowInvoiceRequest extends Request
*/
public function authorize() : bool
{
return auth()->guard('contact')->user()->client_id === $this->invoice->client_id
return auth()->guard('contact')->user()->client_id === (int)$this->invoice->client_id
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_INVOICES;
}
}

View File

@ -19,7 +19,7 @@ class ShowQuoteRequest extends FormRequest
{
public function authorize()
{
return auth()->guard('contact')->user()->client->id === $this->quote->client_id
return auth()->guard('contact')->user()->client->id === (int)$this->quote->client_id
&& auth()->guard('contact')->user()->company->enabled_modules & PortalComposer::MODULE_QUOTES;
}

View File

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

View File

@ -47,7 +47,7 @@ class ClientLedgerBalanceUpdate implements ShouldQueue
*/
public function handle() :void
{
nlog("Updating company ledger for client ". $this->client->id);
// nlog("Updating company ledger for client ". $this->client->id);
MultiDB::setDb($this->company->db);

View File

@ -0,0 +1,102 @@
<?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\PurchaseOrder;
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Engine\PurchaseOrderEmailEngine;
use App\Mail\VendorTemplateEmail;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Utils\Ninja;
use Exception;
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;
class PurchaseOrderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public PurchaseOrder $purchase_order;
public Company $company;
public $template_data;
public $tries = 1;
public function __construct(PurchaseOrder $purchase_order, Company $company, $template_data = null)
{
$this->purchase_order = $purchase_order;
$this->company = $company;
$this->template_data = $template_data;
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$this->purchase_order->last_sent_date = now();
$this->purchase_order->invitations->load('contact.vendor.country', 'purchase_order.vendor.country', 'purchase_order.company')->each(function ($invitation) {
/* Don't fire emails if the company is disabled */
if ($this->company->is_disabled)
return true;
/* Set DB */
MultiDB::setDB($this->company->db);
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($invitation->contact->preferredLocale());
$t->replace(Ninja::transformTranslations($this->company->settings));
/* Mark entity sent */
$invitation->purchase_order->service()->markSent()->save();
$email_builder = (new PurchaseOrderEmailEngine($invitation, 'purchase_order', $this->template_data))->build();
$nmo = new NinjaMailerObject;
$nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $invitation->contact;
$nmo->entity_string = 'purchase_order';
$nmo->invitation = $invitation;
$nmo->reminder_template = 'purchase_order';
$nmo->entity = $invitation->purchase_order;
NinjaMailerJob::dispatchNow($nmo);
});
if ($this->purchase_order->invitations->count() >= 1) {
event(new PurchaseOrderWasEmailed($this->purchase_order->invitations->first(), $this->purchase_order->invitations->first()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
}
}

View File

@ -0,0 +1,126 @@
<?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\PurchaseOrder;
use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Util\UnlinkFile;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Libraries\MultiDB;
use App\Mail\DownloadInvoices;
use App\Mail\DownloadPurchaseOrders;
use App\Models\Company;
use App\Models\User;
use App\Utils\TempFile;
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\Mail;
use Illuminate\Support\Facades\Storage;
use ZipArchive;
class ZipPurchaseOrders implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $purchase_orders;
private $company;
private $user;
public $settings;
public $tries = 1;
/**
* @param $purchase_orders
* @param Company $company
* @param $email
* @deprecated confirm to be deleted
* Create a new job instance.
*
*/
public function __construct($purchase_orders, Company $company, User $user)
{
$this->purchase_orders = $purchase_orders;
$this->company = $company;
$this->user = $user;
$this->settings = $company->settings;
}
/**
* Execute the job.
*
* @return void
* @throws \ZipStream\Exception\FileNotFoundException
* @throws \ZipStream\Exception\FileNotReadableException
* @throws \ZipStream\Exception\OverflowException
*/
public function handle()
{
MultiDB::setDb($this->company->db);
# create new zip object
$zipFile = new \PhpZip\ZipFile();
$file_name = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
$invitation = $this->purchase_orders->first()->invitations->first();
$path = $this->purchase_orders->first()->vendor->purchase_order_filepath($invitation);
$this->purchase_orders->each(function ($purchase_order){
CreatePurchaseOrderPdf::dispatchNow($purchase_order->invitations()->first());
});
try{
foreach ($this->purchase_orders as $purchase_order) {
$file = $purchase_order->service()->getPurchaseOrderPdf();
$zip_file_name = basename($file);
$zipFile->addFromString($zip_file_name, Storage::get($file));
}
Storage::put($path.$file_name, $zipFile->outputAsString());
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadPurchaseOrders(Storage::url($path.$file_name), $this->company);
$nmo->to_user = $this->user;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));
}
catch(\PhpZip\Exception\ZipException $e){
nlog("could not make zip => ". $e->getMessage());
}
finally{
$zipFile->close();
}
}
}

View File

@ -0,0 +1,56 @@
<?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;
use App\Models\Company;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
class DownloadPurchaseOrders extends Mailable
{
// use Queueable, SerializesModels;
public $file_path;
public $company;
public function __construct($file_path, Company $company)
{
$this->file_path = $file_path;
$this->company = $company;
}
/**
* Build the message.
*/
public function build()
{
App::setLocale($this->company->getLocale());
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.download_files'))
->text('email.admin.download_invoices_text', [
'url' => $this->file_path,
])
->view('email.admin.download_purchase_orders', [
'url' => $this->file_path,
'logo' => $this->company->present()->logo,
'whitelabel' => $this->company->account->isPaid() ? true : false,
'settings' => $this->company->settings,
'greeting' => $this->company->present()->name(),
]);
}
}

View File

@ -0,0 +1,207 @@
<?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\Engine;
use App\DataMapper\EmailTemplateDefaults;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Account;
use App\Models\Expense;
use App\Models\PurchaseOrder;
use App\Models\Task;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesHash;
use App\Utils\VendorHtmlEngine;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
class PurchaseOrderEmailEngine extends BaseEmailEngine
{
use MakesHash;
public $invitation;
public Vendor $vendor;
public PurchaseOrder $purchase_order;
public $contact;
public $reminder_template;
public $template_data;
public function __construct($invitation, $reminder_template, $template_data)
{
$this->invitation = $invitation;
$this->reminder_template = $reminder_template; //'purchase_order'
$this->vendor = $invitation->contact->vendor;
$this->purchase_order = $invitation->purchase_order;
$this->contact = $invitation->contact;
$this->template_data = $template_data;
}
public function build()
{
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->vendor->company->settings));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];
} elseif (strlen($this->vendor->getSetting('email_template_'.$this->reminder_template)) > 0) {
$body_template = $this->vendor->getSetting('email_template_'.$this->reminder_template);
} else {
$body_template = EmailTemplateDefaults::getDefaultTemplate('email_template_'.$this->reminder_template, $this->vendor->company->locale());
}
/* Use default translations if a custom message has not been set*/
if (iconv_strlen($body_template) == 0) {
$body_template = trans(
'texts.invoice_message',
[
'invoice' => $this->purchase_order->number,
'company' => $this->purchase_order->company->present()->name(),
'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor),
],
null,
$this->vendor->company->locale()
);
$body_template .= '<div class="center">$view_button</div>';
}
$text_body = trans(
'texts.purchase_order_message',
[
'purchase_order' => $this->purchase_order->number,
'company' => $this->purchase_order->company->present()->name(),
'amount' => Number::formatMoney($this->purchase_order->balance, $this->vendor),
],
null,
$this->vendor->company->locale()
) . "\n\n" . $this->invitation->getLink();
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {
$subject_template = $this->template_data['subject'];
} elseif (strlen($this->vendor->getSetting('email_subject_'.$this->reminder_template)) > 0) {
$subject_template = $this->vendor->getSetting('email_subject_'.$this->reminder_template);
} else {
$subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_'.$this->reminder_template, $this->vendor->company->locale());
}
if (iconv_strlen($subject_template) == 0) {
$subject_template = trans(
'texts.purchase_order_subject',
[
'number' => $this->purchase_order->number,
'account' => $this->purchase_order->company->present()->name(),
],
null,
$this->vendor->company->locale()
);
}
$this->setTemplate($this->vendor->getSetting('email_style'))
->setContact($this->contact)
->setVariables((new VendorHtmlEngine($this->invitation))->makeValues())//move make values into the htmlengine
->setSubject($subject_template)
->setBody($body_template)
->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_purchase_order').'</a>')
->setViewLink($this->invitation->getLink())
->setViewText(ctrans('texts.view_purchase_order'))
->setInvitation($this->invitation)
->setTextBody($text_body);
if ($this->vendor->getSetting('pdf_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
if(Ninja::isHosted())
$this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation, 'url', true)]);
else
$this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation)]);
}
//attach third party documents
if($this->vendor->getSetting('document_email_attachment') !== false && $this->purchase_order->company->account->hasFeature(Account::FEATURE_DOCUMENTS)){
// Storage::url
foreach($this->purchase_order->documents as $document){
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
}
foreach($this->purchase_order->company->documents as $document){
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
}
// $line_items = $this->purchase_order->line_items;
// foreach($line_items as $item)
// {
// $expense_ids = [];
// if(property_exists($item, 'expense_id'))
// {
// $expense_ids[] = $item->expense_id;
// }
// if(count($expense_ids) > 0){
// $expenses = Expense::whereIn('id', $this->transformKeys($expense_ids))
// ->where('invoice_documents', 1)
// ->cursor()
// ->each(function ($expense){
// foreach($expense->documents as $document)
// {
// $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
// }
// });
// }
// $task_ids = [];
// if(property_exists($item, 'task_id'))
// {
// $task_ids[] = $item->task_id;
// }
// if(count($task_ids) > 0 && $this->purchase_order->company->purchase_order_task_documents){
// $tasks = Task::whereIn('id', $this->transformKeys($task_ids))
// ->cursor()
// ->each(function ($task){
// foreach($task->documents as $document)
// {
// $this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
// }
// });
// }
// }
}
return $this;
}
}

View File

@ -0,0 +1,132 @@
<?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;
use App\Jobs\Invoice\CreateUbl;
use App\Models\Account;
use App\Models\Client;
use App\Models\User;
use App\Models\VendorContact;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\TemplateEngine;
use App\Utils\VendorHtmlEngine;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class VendorTemplateEmail extends Mailable
{
private $build_email;
private $vendor;
private $contact;
private $company;
private $invitation;
public function __construct($build_email, VendorContact $contact, $invitation = null)
{
$this->build_email = $build_email;
$this->contact = $contact;
$this->vendor = $contact->vendor;
$this->company = $contact->company;
$this->invitation = $invitation;
}
public function build()
{
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client';
}
if($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
}
$settings = $this->company->settings;
if ($this->build_email->getTemplate() !== 'custom') {
$this->build_email->setBody(
DesignHelpers::parseMarkdownToHtml($this->build_email->getBody())
);
}
if($this->invitation)
{
$html_variables = (new VendorHtmlEngine($this->invitation))->makeValues();
$signature = str_replace(array_keys($html_variables), array_values($html_variables), $settings->email_signature);
}
else
$signature = $settings->email_signature;
if(property_exists($settings, 'email_from_name') && strlen($settings->email_from_name) > 1)
$email_from_name = $settings->email_from_name;
else
$email_from_name = $this->company->present()->name();
$this->from(config('mail.from.address'), $email_from_name);
if (strlen($settings->bcc_email) > 1)
$this->bcc(explode(",",str_replace(" ", "", $settings->bcc_email)));//remove whitespace if any has been inserted.
$this->subject($this->build_email->getSubject())
->text('email.template.text', [
'text_body' => $this->build_email->getTextBody(),
'whitelabel' => $this->vendor->user->account->isPaid() ? true : false,
'settings' => $settings,
])
->view($template_name, [
'greeting' => ctrans('texts.email_salutation', ['name' => $this->contact->present()->name()]),
'body' => $this->build_email->getBody(),
'footer' => $this->build_email->getFooter(),
'view_link' => $this->build_email->getViewLink(),
'view_text' => $this->build_email->getViewText(),
'title' => '',
'signature' => $signature,
'settings' => $settings,
'company' => $this->company,
'whitelabel' => $this->vendor->user->account->isPaid() ? true : false,
'logo' => $this->company->present()->logo($settings),
])
->withSwiftMessage(function ($message) {
$message->getHeaders()->addTextHeader('Tag', $this->company->company_key);
$message->invitation = $this->invitation;
});
/*In the hosted platform we need to slow things down a little for Storage to catch up.*/
if(Ninja::isHosted())
sleep(1);
foreach ($this->build_email->getAttachments() as $file) {
if(is_string($file))
$this->attach($file);
elseif(is_array($file))
$this->attach($file['path'], ['as' => $file['name'], 'mime' => $file['mime']]);
}
return $this;
}
}

View File

@ -57,6 +57,7 @@ class Account extends BaseModel
'utm_content',
'user_agent',
'platform',
// 'set_react_as_default_ap',
];
/**

View File

@ -1,4 +1,13 @@
<?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;
@ -9,6 +18,7 @@ use App\Utils\Traits\Inviteable;
use App\Utils\Traits\MakesDates;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
class PurchaseOrderInvitation extends BaseModel
{
@ -104,4 +114,37 @@ class PurchaseOrderInvitation extends BaseModel
}
public function getLink() :string
{
$entity_type = Str::snake(class_basename($this->entityType()));
if(Ninja::isHosted()){
$domain = $this->company->domain();
}
else
$domain = config('ninja.app_url');
switch ($this->company->portal_mode) {
case 'subdomain':
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
break;
case 'iframe':
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
break;
case 'domain':
return $domain.'/vendor/'.$entity_type.'/'.$this->key;
break;
default:
return '';
break;
}
}
public function getAdminLink() :string
{
return $this->getLink().'?silent=true';
}
}

View File

@ -110,7 +110,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference
public function sendPasswordResetNotification($token)
{
$this->notify(new ClientContactResetPassword($token));
// $this->notify(new ClientContactResetPassword($token));
}
public function preferredLocale()
@ -118,12 +118,9 @@ class VendorContact extends Authenticatable implements HasLocalePreference
$languages = Cache::get('languages');
return $languages->filter(function ($item) {
return $item->id == $this->client->getSetting('language_id');
return $item->id == $this->company->getSetting('language_id');
})->first()->locale;
//$lang = Language::find($this->client->getSetting('language_id'));
//return $lang->locale;
}
/**

View File

@ -235,7 +235,7 @@ class GoCardlessPaymentDriver extends BaseDriver
nlog("GoCardless Event");
nlog($request->all());
if(!is_array($request->events) || !is_object($request->events)){
if(!$request->has("events")){
nlog("No GoCardless events to process in response?");
return response()->json([], 200);
@ -251,12 +251,13 @@ class GoCardlessPaymentDriver extends BaseDriver
$payment = Payment::query()
->where('transaction_reference', $event['links']['payment'])
// ->where('company_id', $request->getCompany()->id)
->where('company_id', $request->getCompany()->id)
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
nlog("GoCardless completed");
}
else
nlog("I was unable to find the payment for this reference");
@ -268,12 +269,13 @@ class GoCardlessPaymentDriver extends BaseDriver
$payment = Payment::query()
->where('transaction_reference', $event['links']['payment'])
// ->where('company_id', $request->getCompany()->id)
->where('company_id', $request->getCompany()->id)
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_FAILED;
$payment->save();
nlog("GoCardless completed");
}
}
}

View File

@ -56,6 +56,10 @@ class Design extends BaseDesign
/** @var Payment[] */
public $payments;
public $settings_object;
public $company;
/** @var array */
public $aging = [];
@ -80,6 +84,7 @@ class Design extends BaseDesign
Str::endsWith('.html', $design) ? $this->design = $design : $this->design = "{$design}.html";
$this->options = $options;
}
public function html(): ?string
@ -574,19 +579,19 @@ class Design extends BaseDesign
foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) {
if (array_key_exists($column, $aliases)) {
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
} elseif ($column == '$product.discount' && !$this->client->company->enable_product_discount) {
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
} elseif ($column == '$product.discount' && !$this->company->enable_product_discount) {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
} elseif ($column == '$product.quantity' && !$this->client->company->enable_product_quantity) {
} elseif ($column == '$product.quantity' && !$this->company->enable_product_quantity) {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']];
} elseif ($column == '$product.tax_rate1') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
} elseif ($column == '$product.tax_rate2') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
} elseif ($column == '$product.tax_rate3') {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
} else {
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->client->getSetting('hide_empty_columns_on_pdf')]];
$elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->settings_object->getSetting('hide_empty_columns_on_pdf')]];
}
}
@ -677,9 +682,9 @@ class Design extends BaseDesign
if ($cell == '$task.rate') {
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']];
} elseif ($cell == '$product.discount' && !$this->client->company->enable_product_discount) {
} elseif ($cell == '$product.discount' && !$this->company->enable_product_discount) {
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']];
} elseif ($cell == '$product.quantity' && !$this->client->company->enable_product_quantity) {
} elseif ($cell == '$product.quantity' && !$this->company->enable_product_quantity) {
$element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']];
} elseif ($cell == '$task.hours') {
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']];
@ -799,7 +804,7 @@ class Design extends BaseDesign
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
$visible = $this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0';
$visible = (int)$this->entity->{$_variable} != 0 || $this->entity->{$_variable} != '0' || !$this->entity->{$_variable};
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
@ -807,7 +812,7 @@ class Design extends BaseDesign
]];
} elseif (Str::startsWith($variable, '$custom')) {
$field = explode('_', $variable);
$visible = is_object($this->client->company->custom_fields) && property_exists($this->client->company->custom_fields, $field[1]) && !empty($this->client->company->custom_fields->{$field[1]});
$visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]});
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],

View File

@ -60,6 +60,10 @@ trait DesignHelpers
$this->document();
$this->settings_object = $this->vendor ? $this->vendor->company : $this->client;
$this->company = $this->vendor ? $this->vendor->company : $this->client->company;
return $this;
}
@ -180,7 +184,7 @@ trait DesignHelpers
$key = array_search(sprintf('%s%s.tax', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true);
if ($key) {
if ($key !== false) {
array_splice($this->context['pdf_variables']["{$type}_columns"], $key, 1, $taxes);
}
}
@ -338,7 +342,7 @@ document.addEventListener('DOMContentLoaded', function() {
$key = array_search(sprintf('%s%s.description', '$', $type), $this->context['pdf_variables']["{$type}_columns"], true);
if ($key) {
if ($key !== false) {
array_splice($this->context['pdf_variables']["{$type}_columns"], $key + 1, 0, $custom_columns);
}
}

View File

@ -42,7 +42,7 @@ class CreateInvitations extends AbstractService
public function run()
{
$contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get();
$contacts = $this->purchase_order->vendor->contacts()->get();
if($contacts->count() == 0){
$this->createBlankContact();

View File

@ -31,7 +31,7 @@ class GetPurchaseOrderPdf extends AbstractService
{
if (! $this->contact) {
$this->contact = $this->purchase_order->vendor->contacts()->where('send_email', true)->first();
$this->contact = $this->purchase_order->vendor->contacts()->orderBy('send_email', 'DESC')->first();
}
$invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first();

View File

@ -11,11 +11,12 @@
namespace App\Services\PurchaseOrder;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\PurchaseOrder;
use App\Services\PurchaseOrder\ApplyNumber;
use App\Services\PurchaseOrder\CreateInvitations;
use App\Services\PurchaseOrder\GetPurchaseOrderPdf;
use App\Services\PurchaseOrder\TriggeredActions;
use App\Utils\Traits\MakesHash;
class PurchaseOrderService
@ -62,6 +63,13 @@ class PurchaseOrderService
return $this;
}
public function triggeredActions($request)
{
$this->purchase_order = (new TriggeredActions($this->purchase_order->load('invitations'), $request))->run();
return $this;
}
public function getPurchaseOrderPdf($contact = null)
{
return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run();
@ -81,6 +89,34 @@ class PurchaseOrderService
return $this;
}
public function touchPdf($force = false)
{
try {
if($force){
$this->purchase_order->invitations->each(function ($invitation) {
CreatePurchaseOrderPdf::dispatchNow($invitation);
});
return $this;
}
$this->purchase_order->invitations->each(function ($invitation) {
CreatePurchaseOrderPdf::dispatch($invitation);
});
}
catch(\Exception $e){
nlog("failed creating purchase orders in Touch PDF");
}
return $this;
}
/**
* Saves the purchase order.
* @return \App\Models\PurchaseOrder object

View File

@ -0,0 +1,65 @@
<?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\Events\Invoice\InvoiceWasEmailed;
use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Models\Invoice;
use App\Models\PurchaseOrder;
use App\Services\AbstractService;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Http\Request;
class TriggeredActions extends AbstractService
{
use GeneratesCounter;
private $request;
private $purchase_order;
public function __construct(PurchaseOrder $purchase_order, Request $request)
{
$this->request = $request;
$this->purchase_order = $purchase_order;
}
public function run()
{
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->purchase_order->service()->markSent()->touchPdf()->save();
$this->sendEmail();
}
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
$this->purchase_order = $this->purchase_order->service()->markSent()->touchPdf()->save();
}
// if ($this->request->has('cancel') && $this->request->input('cancel') == 'true') {
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
// }
return $this->purchase_order;
}
private function sendEmail()
{
PurchaseOrderEmail::dispatch($this->purchase_order, $this->purchase_order->company);
}
}

View File

@ -86,6 +86,7 @@ class AccountTransformer extends EntityTransformer
'hosted_client_count' => (int) $account->hosted_client_count,
'hosted_company_count' => (int) $account->hosted_company_count,
'is_hosted' => (bool) Ninja::isHosted(),
// 'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap
];
}

View File

@ -65,7 +65,7 @@ class ActivityTransformer extends EntityTransformer
'created_at' => (int) $activity->created_at,
'expense_id' => $activity->expense_id ? (string) $this->encodePrimaryKey($activity->expense_id) : '',
'is_system' => (bool) $activity->is_system,
'contact_id' => $activity->contact_id ? (string) $this->encodePrimaryKey($activity->contact_id) : '',
'contact_id' => $activity->client_contact_id ? (string) $this->encodePrimaryKey($activity->client_contact_id) : '',
'task_id' => $activity->task_id ? (string) $this->encodePrimaryKey($activity->task_id) : '',
'token_id' => $activity->token_id ? (string) $this->encodePrimaryKey($activity->token_id) : '',
'notes' => $activity->notes ? (string) $activity->notes : '',

View File

@ -265,6 +265,8 @@ trait MakesInvoiceValues
*/
public function transformLineItems($items, $table_type = '$product') :array
{
$entity = $this->client ? $this->client : $this->company;
$data = [];
if (! is_array($items)) {
@ -294,23 +296,23 @@ trait MakesInvoiceValues
$data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item;
$data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service;
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->client);
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->client);
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $entity);
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $entity);
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->client);
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->client);
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client);
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client);
$data[$key][$table_type . ".{$_table_type}1"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}1", $item->custom_value1, $entity);
$data[$key][$table_type . ".{$_table_type}2"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}2", $item->custom_value2, $entity);
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}3", $item->custom_value3, $entity);
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}4", $item->custom_value4, $entity);
if($item->quantity > 0 || $item->cost > 0){
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $this->client->currency());
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $entity->currency());
$data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $this->client);
$data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $entity);
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $entity);
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client);
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $entity);
}
else {
@ -326,13 +328,13 @@ trait MakesInvoiceValues
}
if(property_exists($item, 'gross_line_total'))
$data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $this->client);
$data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' :Number::formatMoney($item->gross_line_total, $entity);
else
$data[$key][$table_type.'.gross_line_total'] = '';
if (isset($item->discount) && $item->discount > 0) {
if ($item->is_amount_discount) {
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->client);
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $entity);
} else {
$data[$key][$table_type.'.discount'] = floatval($item->discount).'%';
}
@ -376,13 +378,14 @@ trait MakesInvoiceValues
private function makeLineTaxes() :string
{
$tax_map = $this->calc()->getTaxMap();
$entity = $this->client ? $this->client : $this->company;
$data = '';
foreach ($tax_map as $tax) {
$data .= '<tr class="line_taxes">';
$data .= '<td>'.$tax['name'].'</td>';
$data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>';
$data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>';
}
return $data;
@ -395,6 +398,7 @@ trait MakesInvoiceValues
private function makeTotalTaxes() :string
{
$data = '';
$entity = $this->client ? $this->client : $this->company;
if (! $this->calc()->getTotalTaxMap()) {
return $data;
@ -403,7 +407,7 @@ trait MakesInvoiceValues
foreach ($this->calc()->getTotalTaxMap() as $tax) {
$data .= '<tr class="total_taxes">';
$data .= '<td>'.$tax['name'].'</td>';
$data .= '<td>'.Number::formatMoney($tax['total'], $this->client).'</td></tr>';
$data .= '<td>'.Number::formatMoney($tax['total'], $entity).'</td></tr>';
}
return $data;
@ -427,13 +431,14 @@ trait MakesInvoiceValues
private function totalTaxValues() :string
{
$data = '';
$entity = $this->client ? $this->client : $this->company;
if (! $this->calc()->getTotalTaxMap()) {
return $data;
}
foreach ($this->calc()->getTotalTaxMap() as $tax) {
$data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>';
$data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>';
}
return $data;
@ -455,11 +460,12 @@ trait MakesInvoiceValues
private function lineTaxValues() :string
{
$tax_map = $this->calc()->getTaxMap();
$entity = $this->client ? $this->client : $this->company;
$data = '';
foreach ($tax_map as $tax) {
$data .= '<span>'.Number::formatMoney($tax['total'], $this->client).'</span>';
$data .= '<span>'.Number::formatMoney($tax['total'], $entity).'</span>';
}
return $data;
@ -481,7 +487,8 @@ trait MakesInvoiceValues
*/
public function generateCustomCSS() :string
{
$settings = $this->client->getMergedSettings();
$settings = $this->client ? $this->client->getMergedSettings() : $this->company->settings;
$header_and_footer = '
.header, .header-space {

View File

@ -134,20 +134,15 @@ class VendorHtmlEngine
$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['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_purchase_order').'</a>', 'label' => ctrans('texts.view_purchase_order')];
$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')];
@ -390,11 +385,6 @@ class VendorHtmlEngine
$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' => ''];

View File

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

View File

@ -0,0 +1,40 @@
<?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
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SetAccountFlagForReact extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Illuminate\Support\Facades\Artisan::call('ninja:design-update');
// Schema::table('accounts', function (Blueprint $table) {
// $table->boolean('set_react_as_default_ap')->default(0);
// });
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -4619,8 +4619,11 @@ $LANG = array(
'activity_134' => ':user restored purchase order :purchase_order',
'activity_135' => ':user emailed purchase order :purchase_order',
'activity_136' => ':contact viewed purchase order :purchase_order',
'purchase_order_subject' => '',
'purchase_order_message' => '',
'purchase_order_subject' => 'New Purchase Order :number from :account',
'purchase_order_message' => 'To view your purchase order for :amount, click the link below.',
'view_purchase_order' => 'View Purchase Order',
'purchase_orders_backup_subject' => 'Your purchase orders are ready for download',
);
return $LANG;

View File

@ -0,0 +1,10 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings])
<div class="center">
<h1>{{ ctrans('texts.purchase_orders_backup_subject') }}</h1>
<p>{{ ctrans('texts.download_timeframe') }}</p>
<a target="_blank" class="button" href="{{ $url }}">
{{ ctrans('texts.download') }}
</a>
</div>
@endcomponent

View File

@ -50,8 +50,8 @@ class CompanyTest extends TestCase
{
$this->withoutMiddleware(PasswordProtection::class);
$cc = Company::first();
$cc->delete();
// $cc = Company::first();
// $cc->delete();
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),