From 481c015efbf5e632ae87659264a8043200a2fcaf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 12:18:02 +1000 Subject: [PATCH 01/11] Clean up logging --- app/Jobs/Ledger/ClientLedgerBalanceUpdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/Ledger/ClientLedgerBalanceUpdate.php b/app/Jobs/Ledger/ClientLedgerBalanceUpdate.php index 64df6d3abf..b532296a11 100644 --- a/app/Jobs/Ledger/ClientLedgerBalanceUpdate.php +++ b/app/Jobs/Ledger/ClientLedgerBalanceUpdate.php @@ -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); From f9ade5af77932280af4bf045625ed02f172e39d6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 12:23:46 +1000 Subject: [PATCH 02/11] Hide surcharges on PDF if the value is blank --- app/Services/PdfMaker/Design.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 9e4b5efeec..dcbc8f39bf 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -799,7 +799,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']], From c285e26302e1a3b99f5e0d585118d92c3c24ba37 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 12:39:52 +1000 Subject: [PATCH 03/11] Ensure client contact id is being return in activity --- app/Transformers/ActivityTransformer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Transformers/ActivityTransformer.php b/app/Transformers/ActivityTransformer.php index c49ae00386..ccf8558698 100644 --- a/app/Transformers/ActivityTransformer.php +++ b/app/Transformers/ActivityTransformer.php @@ -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 : '', From 7885813c1ad049715a67d8595cb4cb5b71ab1859 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 13:03:05 +1000 Subject: [PATCH 04/11] Store silent in session to prevent entities being viewed by admins --- app/Http/Controllers/ClientPortal/EntityViewController.php | 6 ++++-- app/Http/Controllers/ClientPortal/InvitationController.php | 6 ++++-- app/Http/Controllers/ClientPortal/InvoiceController.php | 2 +- app/Http/Middleware/CheckClientExistence.php | 2 ++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/EntityViewController.php b/app/Http/Controllers/ClientPortal/EntityViewController.php index 8060391fd8..f521f21fa7 100644 --- a/app/Http/Controllers/ClientPortal/EntityViewController.php +++ b/app/Http/Controllers/ClientPortal/EntityViewController.php @@ -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})]); diff --git a/app/Http/Controllers/ClientPortal/InvitationController.php b/app/Http/Controllers/ClientPortal/InvitationController.php index 48b4a45575..1595b5483f 100644 --- a/app/Http/Controllers/ClientPortal/InvitationController.php +++ b/app/Http/Controllers/ClientPortal/InvitationController.php @@ -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'; diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index 3aa5436147..f3a49b20ea 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -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(); diff --git a/app/Http/Middleware/CheckClientExistence.php b/app/Http/Middleware/CheckClientExistence.php index dfb894fac1..f218c39a32 100644 --- a/app/Http/Middleware/CheckClientExistence.php +++ b/app/Http/Middleware/CheckClientExistence.php @@ -60,6 +60,8 @@ class CheckClientExistence session()->put('multiple_contacts', $multiple_contacts); + session()->put('is_silent', request()->has('silent')); + return $next($request); } } From 646091f89e1cabd9fb3f451b904d63bcc2e11048 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 15:15:26 +1000 Subject: [PATCH 05/11] Fixes for quote form request --- app/Http/Requests/ClientPortal/Invoices/ShowInvoiceRequest.php | 2 +- app/Http/Requests/ClientPortal/Quotes/ShowQuoteRequest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/ClientPortal/Invoices/ShowInvoiceRequest.php b/app/Http/Requests/ClientPortal/Invoices/ShowInvoiceRequest.php index 01be13261d..f9573fd1c3 100644 --- a/app/Http/Requests/ClientPortal/Invoices/ShowInvoiceRequest.php +++ b/app/Http/Requests/ClientPortal/Invoices/ShowInvoiceRequest.php @@ -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; } } diff --git a/app/Http/Requests/ClientPortal/Quotes/ShowQuoteRequest.php b/app/Http/Requests/ClientPortal/Quotes/ShowQuoteRequest.php index ee32441744..a510377f32 100644 --- a/app/Http/Requests/ClientPortal/Quotes/ShowQuoteRequest.php +++ b/app/Http/Requests/ClientPortal/Quotes/ShowQuoteRequest.php @@ -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; } From 90bff41600c7aa3b53bc759febc1b16b1512d41d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 17:04:16 +1000 Subject: [PATCH 06/11] Purchase Order Emails --- app/DataMapper/EmailTemplateDefaults.php | 4 +- .../PurchaseOrder/PurchaseOrderWasEmailed.php | 7 +- .../Controllers/PurchaseOrderController.php | 4 +- app/Jobs/PurchaseOrder/PurchaseOrderEmail.php | 102 +++++++++ app/Mail/Engine/PurchaseOrderEmailEngine.php | 207 ++++++++++++++++++ app/Mail/VendorTemplateEmail.php | 132 +++++++++++ app/Models/Account.php | 1 + app/Models/VendorContact.php | 7 +- app/Services/PdfMaker/Design.php | 25 ++- .../Designs/Utilities/DesignHelpers.php | 4 + .../PurchaseOrder/CreateInvitations.php | 2 +- .../PurchaseOrder/PurchaseOrderService.php | 38 +++- .../PurchaseOrder/TriggeredActions.php | 65 ++++++ app/Transformers/AccountTransformer.php | 1 + app/Utils/Traits/MakesInvoiceValues.php | 41 ++-- app/Utils/VendorHtmlEngine.php | 12 +- ...6_10_030503_set_account_flag_for_react.php | 40 ++++ resources/lang/en/texts.php | 6 +- 18 files changed, 645 insertions(+), 53 deletions(-) create mode 100644 app/Jobs/PurchaseOrder/PurchaseOrderEmail.php create mode 100644 app/Mail/Engine/PurchaseOrderEmailEngine.php create mode 100644 app/Mail/VendorTemplateEmail.php create mode 100644 app/Services/PurchaseOrder/TriggeredActions.php create mode 100644 database/migrations/2022_06_10_030503_set_account_flag_for_react.php diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php index 03e2125571..5cdc7b915c 100644 --- a/app/DataMapper/EmailTemplateDefaults.php +++ b/app/DataMapper/EmailTemplateDefaults.php @@ -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() diff --git a/app/Events/PurchaseOrder/PurchaseOrderWasEmailed.php b/app/Events/PurchaseOrder/PurchaseOrderWasEmailed.php index 7836a84426..7674039d70 100644 --- a/app/Events/PurchaseOrder/PurchaseOrderWasEmailed.php +++ b/app/Events/PurchaseOrder/PurchaseOrderWasEmailed.php @@ -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; } diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 61877a56d6..ad25c6fabe 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -23,6 +23,7 @@ use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest; +use App\Jobs\Invoice\PurchaseOrderEmail; use App\Jobs\Invoice\ZipInvoices; use App\Models\Client; use App\Models\PurchaseOrder; @@ -183,6 +184,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))); @@ -627,7 +629,7 @@ 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); default: return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400); diff --git a/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php b/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php new file mode 100644 index 0000000000..d27ffdd21a --- /dev/null +++ b/app/Jobs/PurchaseOrder/PurchaseOrderEmail.php @@ -0,0 +1,102 @@ +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))); + } + + } + +} diff --git a/app/Mail/Engine/PurchaseOrderEmailEngine.php b/app/Mail/Engine/PurchaseOrderEmailEngine.php new file mode 100644 index 0000000000..d3b2fcbad5 --- /dev/null +++ b/app/Mail/Engine/PurchaseOrderEmailEngine.php @@ -0,0 +1,207 @@ +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 .= '
$view_button
'; + + } + $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("".ctrans('texts.view_purchase_order').'') + ->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; + } +} \ No newline at end of file diff --git a/app/Mail/VendorTemplateEmail.php b/app/Mail/VendorTemplateEmail.php new file mode 100644 index 0000000000..2612f90c62 --- /dev/null +++ b/app/Mail/VendorTemplateEmail.php @@ -0,0 +1,132 @@ +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; + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index 8af56a3a59..4f9f9a333e 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -57,6 +57,7 @@ class Account extends BaseModel 'utm_content', 'user_agent', 'platform', + // 'set_react_as_default_ap', ]; /** diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 2f5f9447cc..d4a4bd92f9 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -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; } /** diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index dcbc8f39bf..01be541aa1 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -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']]; @@ -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']], diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index 944c518db1..ff449e8f1a 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -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; } diff --git a/app/Services/PurchaseOrder/CreateInvitations.php b/app/Services/PurchaseOrder/CreateInvitations.php index fa01a8457b..cd8634fc5d 100644 --- a/app/Services/PurchaseOrder/CreateInvitations.php +++ b/app/Services/PurchaseOrder/CreateInvitations.php @@ -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(); diff --git a/app/Services/PurchaseOrder/PurchaseOrderService.php b/app/Services/PurchaseOrder/PurchaseOrderService.php index 69e6066b99..89d2b64a8b 100644 --- a/app/Services/PurchaseOrder/PurchaseOrderService.php +++ b/app/Services/PurchaseOrder/PurchaseOrderService.php @@ -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 diff --git a/app/Services/PurchaseOrder/TriggeredActions.php b/app/Services/PurchaseOrder/TriggeredActions.php new file mode 100644 index 0000000000..76735129cf --- /dev/null +++ b/app/Services/PurchaseOrder/TriggeredActions.php @@ -0,0 +1,65 @@ +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); + + } +} diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 661ad79b2c..502f0b9ded 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -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 ]; } diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index e59c59a614..1b5c698515 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -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 .= ''; $data .= ''.$tax['name'].''; - $data .= ''.Number::formatMoney($tax['total'], $this->client).''; + $data .= ''.Number::formatMoney($tax['total'], $entity).''; } 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 .= ''; $data .= ''.$tax['name'].''; - $data .= ''.Number::formatMoney($tax['total'], $this->client).''; + $data .= ''.Number::formatMoney($tax['total'], $entity).''; } 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 .= ''.Number::formatMoney($tax['total'], $this->client).''; + $data .= ''.Number::formatMoney($tax['total'], $entity).''; } 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 .= ''.Number::formatMoney($tax['total'], $this->client).''; + $data .= ''.Number::formatMoney($tax['total'], $entity).''; } 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 { diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index 7e20a95168..36cc99a9fb 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -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' => ''.ctrans('texts.pay_now').'', '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 ?: ' ', 'label' => ctrans('texts.purchase_order_number')]; $data['$number_short'] = ['value' => $this->entity->number ?: ' ', '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' => ''.ctrans('texts.view_invoice').'', 'label' => ctrans('texts.view_invoice')]; + $data['$view_link'] = ['value' => ''.ctrans('texts.view_purchase_order').'', '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()) ?: ' ', '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' => '']; diff --git a/database/migrations/2022_06_10_030503_set_account_flag_for_react.php b/database/migrations/2022_06_10_030503_set_account_flag_for_react.php new file mode 100644 index 0000000000..a81f53fa4c --- /dev/null +++ b/database/migrations/2022_06_10_030503_set_account_flag_for_react.php @@ -0,0 +1,40 @@ +boolean('set_react_as_default_ap')->default(0); + // }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 59a0282515..413a966fbe 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4619,8 +4619,10 @@ $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', + ); return $LANG; From b1c3878da28dc576563b4097fa6f6af232bd2e9a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 18:00:07 +1000 Subject: [PATCH 07/11] Bulk actions for Purchase Orders --- .../Controllers/PurchaseOrderController.php | 10 +- .../StorePurchaseOrderRequest.php | 2 +- app/Jobs/PurchaseOrder/ZipPurchaseOrders.php | 125 ++++++++++++++++++ app/Models/PurchaseOrderInvitation.php | 43 ++++++ tests/Feature/CompanyTest.php | 4 +- 5 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 app/Jobs/PurchaseOrder/ZipPurchaseOrders.php diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index ad25c6fabe..6eb345259c 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -23,8 +23,9 @@ use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest; use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest; -use App\Jobs\Invoice\PurchaseOrderEmail; 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; @@ -495,7 +496,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); } @@ -631,6 +632,11 @@ class PurchaseOrderController extends BaseController //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; diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php index ef97d446ad..5b63416e46 100644 --- a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -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'; diff --git a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php new file mode 100644 index 0000000000..34d926c0ee --- /dev/null +++ b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php @@ -0,0 +1,125 @@ +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 DownloadInvoices(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(); + } + + + } + +} diff --git a/app/Models/PurchaseOrderInvitation.php b/app/Models/PurchaseOrderInvitation.php index beff770596..dfeaeefbe7 100644 --- a/app/Models/PurchaseOrderInvitation.php +++ b/app/Models/PurchaseOrderInvitation.php @@ -1,4 +1,13 @@ 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'; + } + + } diff --git a/tests/Feature/CompanyTest.php b/tests/Feature/CompanyTest.php index ecf2c61fbb..2afc847ac5 100644 --- a/tests/Feature/CompanyTest.php +++ b/tests/Feature/CompanyTest.php @@ -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'), From 6e357d8c7bb7a64df02bc68daffeffd23cbd4566 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Jun 2022 18:29:15 +1000 Subject: [PATCH 08/11] Purchase Order Download emails --- .../Controllers/PurchaseOrderController.php | 5 +- app/Jobs/PurchaseOrder/ZipPurchaseOrders.php | 3 +- app/Mail/DownloadPurchaseOrders.php | 56 +++++++++++++++++++ .../PurchaseOrder/GetPurchaseOrderPdf.php | 2 +- resources/lang/en/texts.php | 1 + .../admin/download_purchase_orders.blade.php | 10 ++++ 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 app/Mail/DownloadPurchaseOrders.php create mode 100644 resources/views/email/admin/download_purchase_orders.blade.php diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 6eb345259c..75f0fb4657 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -33,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 { @@ -488,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"); @@ -582,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) diff --git a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php index 34d926c0ee..e95d86cf72 100644 --- a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php +++ b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php @@ -18,6 +18,7 @@ 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; @@ -101,7 +102,7 @@ class ZipPurchaseOrders implements ShouldQueue Storage::put($path.$file_name, $zipFile->outputAsString()); $nmo = new NinjaMailerObject; - $nmo->mailable = new DownloadInvoices(Storage::url($path.$file_name), $this->company); + $nmo->mailable = new DownloadPurchaseOrders(Storage::url($path.$file_name), $this->company); $nmo->to_user = $this->user; $nmo->settings = $this->settings; $nmo->company = $this->company; diff --git a/app/Mail/DownloadPurchaseOrders.php b/app/Mail/DownloadPurchaseOrders.php new file mode 100644 index 0000000000..27fcd51a7b --- /dev/null +++ b/app/Mail/DownloadPurchaseOrders.php @@ -0,0 +1,56 @@ +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(), + ]); + } +} diff --git a/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php b/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php index 8c75b87899..7d074ce4bb 100644 --- a/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php +++ b/app/Services/PurchaseOrder/GetPurchaseOrderPdf.php @@ -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(); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 413a966fbe..ff9b7280a4 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -4622,6 +4622,7 @@ $LANG = array( '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', ); diff --git a/resources/views/email/admin/download_purchase_orders.blade.php b/resources/views/email/admin/download_purchase_orders.blade.php new file mode 100644 index 0000000000..ce40579d97 --- /dev/null +++ b/resources/views/email/admin/download_purchase_orders.blade.php @@ -0,0 +1,10 @@ +@component('email.template.admin', ['logo' => $logo, 'settings' => $settings]) +
+

{{ ctrans('texts.purchase_orders_backup_subject') }}

+

{{ ctrans('texts.download_timeframe') }}

+ + + {{ ctrans('texts.download') }} + +
+@endcomponent From 54c97a8755c42957560562eb7126d69e98f4a025 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Jun 2022 07:02:24 +1000 Subject: [PATCH 09/11] Fixes for GoCardless webhooks --- app/PaymentDrivers/GoCardlessPaymentDriver.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index c438924e7d..d42bf5e748 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -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"); } } } From 50000144c6d96525b63d8fb62d8492f2cb8f3651 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Jun 2022 09:07:09 +1000 Subject: [PATCH 10/11] Fixes for falsey conditions --- app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index ff449e8f1a..8318d24349 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -184,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); } } @@ -342,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); } } From 3b9cb7c3be58993fe6a6fdb02761818d513e4355 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 11 Jun 2022 15:17:35 +1000 Subject: [PATCH 11/11] v5.3.99 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index c9d11220ab..7fb66c74d1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.98 \ No newline at end of file +5.3.99 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index d6db23e121..65016615d0 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -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', ''),