diff --git a/app/Casts/InvoiceSyncCast.php b/app/Casts/InvoiceSyncCast.php index fb88c83d7d..8c893c3f81 100644 --- a/app/Casts/InvoiceSyncCast.php +++ b/app/Casts/InvoiceSyncCast.php @@ -29,18 +29,39 @@ class InvoiceSyncCast implements CastsAttributes return null; } - $is = new InvoiceSync(); - $is->qb_id = $data['qb_id']; + $is = new InvoiceSync($data); + // $is->qb_id = $data['qb_id']; + // $is->email = $data['email']; return $is; } public function set($model, string $key, $value, array $attributes) { + $data = []; + + if(isset($value->qb_id) && strlen($value->qb_id) >= 1) + $data['qb_id'] = $value->qb_id; + + if (isset($value->email) && $value->email !== null) { + + $data['email'] = [ + 'body' => $value->email->body ?? '', + 'subject' => $value->email->subject ?? '', + 'template' => $value->email->template ?? '', + 'entity' => $value->email->entity ?? '', + 'entity_id' => $value->email->entity_id ?? '', + 'cc_email' => $value->email->cc_email ?? '', + 'action' => $value->email->action ?? '', + 'ids' => $value->email->ids ?? '', + 'type' => $value->email->email_type ?? '', + ]; + + } + return [ - $key => json_encode([ - 'qb_id' => $value->qb_id, - ]) + $key => json_encode($data) ]; + } } diff --git a/app/DataMapper/InvoiceSync.php b/app/DataMapper/InvoiceSync.php index b56b1a2239..3b4974f76c 100644 --- a/app/DataMapper/InvoiceSync.php +++ b/app/DataMapper/InvoiceSync.php @@ -21,9 +21,27 @@ class InvoiceSync implements Castable { public string $qb_id; + public mixed $email; + public function __construct(array $attributes = []) { + $this->qb_id = $attributes['qb_id'] ?? ''; + $this->email = new \stdClass(); + + if(isset($attributes['email'])) + { + + $this->email->body = $attributes['email']['body'] ?? ''; + $this->email->subject = $attributes['email']['subject'] ?? ''; + $this->email->template = $attributes['email']['template'] ?? ''; + $this->email->entity = $attributes['email']['entity'] ?? ''; + $this->email->entity_id = $attributes['email']['entity_id'] ?? ''; + $this->email->cc_email = $attributes['email']['cc_email'] ?? ''; + $this->email->action = $attributes['email']['action'] ?? ''; + $this->email->ids = $attributes['email']['ids'] ?? []; + $this->email->email_type = $attributes['email']['type'] ?? ''; + } } /** diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 88d81f55f0..9a11024815 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -11,6 +11,7 @@ namespace App\Http\Controllers; +use App\DataMapper\InvoiceSync; use App\Utils\Ninja; use App\Models\Quote; use App\Models\Credit; @@ -69,6 +70,28 @@ class EmailController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); + $company = $entity_obj->company; + + //Only handle Peppol Invoices for now. + if($entity_obj instanceof Invoice && !isset($entity_obj->sync->email)){ + // if($entity_obj instanceof Invoice && $company->isPeppolSender()){ + + $sync = $entity_obj->sync ?? new InvoiceSync(); + $sync->email->body = strlen($body) > 3 ? $body : null; + $sync->email->subject = strlen($subject) > 3 ? $subject : null; + $sync->email->template = $request->input('template'); + $sync->email->entity = $request->input('entity'); + $sync->email->entity_id = $request->input('entity_id'); + $sync->email->cc_email = $request->cc_email; + $entity_obj->sync = $sync; + $entity_obj->saveQuietly(); + $entity_obj->service()->markSent()->save(); + + \App\Services\EDocument\Jobs\SendEDocument::dispatch(get_class($entity_obj), $entity_obj->id, $company->db); + + nlog($sync); + return; + } if ($request->cc_email && (Ninja::isSelfHost() || $user->account->isPremium())) { @@ -92,6 +115,19 @@ class EmailController extends BaseController $entity_obj = $entity_obj->fresh(); $entity_obj->last_sent_date = now(); + + if(!empty($entity_obj->sync)) + { + $sync = $entity_obj->sync; + if (empty($sync->qb_id)) { + $sync = null; + } else { + unset($sync->email); + } + + $entity_obj->sync = $sync; + } + $entity_obj->save(); /*Only notify the admin ONCE, not once per contact/invite*/ @@ -139,21 +175,7 @@ class EmailController extends BaseController return $this->itemResponse($entity_obj->fresh()); } - // private function sendPurchaseOrder($entity_obj, $data, $template) - // { - // $this->entity_type = PurchaseOrder::class; - - // $this->entity_transformer = PurchaseOrderTransformer::class; - - // $data['template'] = $template; - - // PurchaseOrderEmail::dispatch($entity_obj, $entity_obj->company, $data); - // $entity_obj->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor"); - - // return $this->itemResponse($entity_obj); - // } - - private function resolveClass(string $entity): string + private function resolveClass(string $entity): string { $class = ''; diff --git a/app/Http/Controllers/MailgunController.php b/app/Http/Controllers/MailgunController.php index 0fd8d3c68c..9a637bb920 100644 --- a/app/Http/Controllers/MailgunController.php +++ b/app/Http/Controllers/MailgunController.php @@ -11,10 +11,13 @@ namespace App\Http\Controllers; +use App\Utils\Ninja; use App\Models\Company; use App\Libraries\MultiDB; use Illuminate\Http\Request; +use App\Utils\Traits\SavesDocuments; use App\Jobs\Mailgun\ProcessMailgunWebhook; +use App\Http\Requests\Email\SendEmailRequest; use App\Jobs\Mailgun\ProcessMailgunInboundWebhook; /** @@ -22,6 +25,8 @@ use App\Jobs\Mailgun\ProcessMailgunInboundWebhook; */ class MailgunController extends BaseController { + use SavesDocuments; + public function __construct() { } @@ -120,14 +125,68 @@ class MailgunController extends BaseController { $input = $request->all(); + + $authorizedByHash = \hash_equals(\hash_hmac('sha256', $input['timestamp'] . $input['token'], config('services.mailgun.webhook_signing_key')), $input['signature']); + $authorizedByToken = $request->has('token') && $request->get('token') == config('ninja.inbound_mailbox.inbound_webhook_token'); + if (!$authorizedByHash && !$authorizedByToken) { + return response()->json(['message' => 'Unauthorized'], 403); + } + nlog($input); - if($input['recipient'] == config('ninja.storecove_email_catchall') && stripos('no-reply@mailer.storecove.com', $input['from']) !== false){ - - foreach ($request->files as $file) { - // Process each file - nlog($file); + nlog("checks"); + /** Peppol invoice xml ingest */ + if(stripos($input['recipient'], "peppol_") !== false && stripos($input['from'], 'no-reply@mailer.storecove.com') !== false){ + + $email_parts = explode("@", $input['recipient']); + nlog($email_parts); + + $parts = explode("_", $email_parts[0]); + + nlog($parts); + + if(count($parts) != 4 && $parts[0] != 'peppol' && stripos('db-ninja-0', $parts[3]) !== false) + return; + + $entity = ucfirst($parts[1]); + $entity_id = $parts[2]; + $db = $parts[3]; + + MultiDB::setDB($db); + + $class = "\\App\\Models\\".ucfirst(\Illuminate\Support\Str::camel($entity)); + + /** @var \App\Models\Invoice $entity */ + $entity = $class::query()->withTrashed()->find($entity_id); + + if(!$entity){ + nlog("could not resolve entity for mailgun webhook"); + nlog($input); + return; } + foreach ($request->files as $file) { + $this->saveDocuments($file, $entity, true); + } + + + if(empty($entity->sync)) + return; //just save the document, do not email it! + + $sync = $entity->sync; + + $request = new SendEmailRequest([ + 'entity' => $sync->email->entity, + 'entity_id' => $entity->id, + 'template' => $sync->email->template, + 'subject' => $sync->email->subject, + 'body' => $sync->email->body, + 'cc_email' => $sync->email->cc_email, + ]); + + $request->setUserResolver(fn () => $entity->user); // Or auth()->user() + + app(\App\Http\Controllers\EmailController::class)->send($request); + return; } if (!array_key_exists('sender', $input) || !array_key_exists('recipient', $input) || !array_key_exists('message-url', $input)) { @@ -137,11 +196,7 @@ class MailgunController extends BaseController // @turbo124 TODO: how to check for services.mailgun.webhook_signing_key on company level, when custom credentials are defined // TODO: validation for client mail credentials by recipient - $authorizedByHash = \hash_equals(\hash_hmac('sha256', $input['timestamp'] . $input['token'], config('services.mailgun.webhook_signing_key')), $input['signature']); - $authorizedByToken = $request->has('token') && $request->get('token') == config('ninja.inbound_mailbox.inbound_webhook_token'); - if (!$authorizedByHash && !$authorizedByToken) - return response()->json(['message' => 'Unauthorized'], 403); - + /** @var \App\Models\Company $company */ $company = MultiDB::findAndSetDbByExpenseMailbox($input["recipient"]); diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index 6a5e56bcec..a88c2002de 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -112,7 +112,7 @@ class SendEmailRequest extends Request public function messages() { return [ - 'template.in' => 'Template :input is anot a valid template.', + 'template.in' => 'Template :input is not a valid template.', 'entity.in' => 'Entity :input is not a valid entity.' ]; } diff --git a/app/Models/Company.php b/app/Models/Company.php index b498bdacef..be91f303e3 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -980,4 +980,9 @@ class Company extends BaseModel return new CompanyService($this); } + public function isPeppolSender() + { + return Ninja::isHosted() && $this->account->isPaid() && $this->account->isEnterpriseClient() && $this->account->e_invoice_quota > 0 && $this->settings->e_invoice_type == 'PEPPOL' && $this->tax_data->acts_as_sender; + } + } diff --git a/app/Services/EDocument/Gateway/Storecove/Mutator.php b/app/Services/EDocument/Gateway/Storecove/Mutator.php index d3e9573dc1..042c6df86b 100644 --- a/app/Services/EDocument/Gateway/Storecove/Mutator.php +++ b/app/Services/EDocument/Gateway/Storecove/Mutator.php @@ -630,7 +630,7 @@ class Mutator implements MutatorInterface */ private function setEmailRouting(string $email): self { - $email = "peppol@mail.invoicing.co"; + $email = "peppol_invoice_{$this->invoice->id}_{$this->invoice->company->db}@mail.invoicing.co"; $meta = $this->getStorecoveMeta(); if(isset($meta['routing']['emails'])) { diff --git a/resources/views/portal/ninja2020/auth/login.blade.php b/resources/views/portal/ninja2020/auth/login.blade.php index 81c8d48f25..a7ff007a09 100644 --- a/resources/views/portal/ninja2020/auth/login.blade.php +++ b/resources/views/portal/ninja2020/auth/login.blade.php @@ -1,11 +1,15 @@ @extends('portal.ninja2020.layout.clean') @section('meta_title', ctrans('texts.login')) +@section('head') + @component('portal.ninja2020.components.test') - - + + @endcomponent +@endsection + @section('body')