Minor refactors for inbound email processing

This commit is contained in:
David Bomba 2024-08-30 10:57:43 +10:00
parent 8f88c408f7
commit 93c382eae1
9 changed files with 162 additions and 111 deletions

View File

@ -11,9 +11,11 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Jobs\Mailgun\ProcessMailgunInboundWebhook; use App\Models\Company;
use App\Jobs\Mailgun\ProcessMailgunWebhook; use App\Libraries\MultiDB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Jobs\Mailgun\ProcessMailgunWebhook;
use App\Jobs\Mailgun\ProcessMailgunInboundWebhook;
/** /**
* Class MailgunController. * Class MailgunController.
@ -126,9 +128,15 @@ class MailgunController extends BaseController
$authorizedByHash = \hash_equals(\hash_hmac('sha256', $input['timestamp'] . $input['token'], config('services.mailgun.webhook_signing_key')), $input['signature']); $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'); $authorizedByToken = $request->has('token') && $request->get('token') == config('ninja.inbound_mailbox.inbound_webhook_token');
if (!$authorizedByHash && !$authorizedByToken) if (!$authorizedByHash && !$authorizedByToken)
return response()->json(['message' => 'Unauthorized'], 403); return response()->json(['message' => 'Unauthorized'], 403);
ProcessMailgunInboundWebhook::dispatch($input["sender"], $input["recipient"], $input["message-url"])->delay(rand(2, 10)); /** @var \App\Models\Company $company */
$company = MultiDB::findAndSetDbByExpenseMailbox($input["recipient"]);
if(!$company)
return response()->json(['message' => 'Ok'], 200); // Fail gracefully
ProcessMailgunInboundWebhook::dispatch($input["sender"], $input["recipient"], $input["message-url"], $company)->delay(rand(2, 10));
return response()->json(['message' => 'Success.'], 200); return response()->json(['message' => 'Success.'], 200);
} }

View File

@ -280,20 +280,21 @@ class PostMarkController extends BaseController
nlog('Failed: Message could not be parsed, because required parameters are missing.'); nlog('Failed: Message could not be parsed, because required parameters are missing.');
return response()->json(['message' => 'Failed. Missing/Invalid Parameters.'], 400); return response()->json(['message' => 'Failed. Missing/Invalid Parameters.'], 400);
} }
$company = MultiDB::findAndSetDbByExpenseMailbox($input["To"]);
$inboundEngine = new InboundMailEngine(); if (!$company) {
nlog('[PostmarkInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from postmark: ' . $input["To"]);
// $inboundEngine->saveMeta($input["From"], $input["To"], true); // important to save this, to protect from spam
return response()->json(['message' => 'Ok'], 200);
}
$inboundEngine = new InboundMailEngine($company);
if ($inboundEngine->isInvalidOrBlocked($input["From"], $input["To"])) { if ($inboundEngine->isInvalidOrBlocked($input["From"], $input["To"])) {
return response()->json(['message' => 'Blocked.'], 403); return response()->json(['message' => 'Blocked.'], 403);
} }
$company = MultiDB::findAndSetDbByExpenseMailbox($input["To"]);
if (!$company) {
nlog('[PostmarkInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from postmark: ' . $input["To"]);
$inboundEngine->saveMeta($input["From"], $input["To"], true); // important to save this, to protect from spam
return;
}
try { // important to save meta if something fails here to prevent spam try { // important to save meta if something fails here to prevent spam
// prepare data for ingresEngine // prepare data for ingresEngine

View File

@ -23,10 +23,7 @@ class ValidExpenseMailbox implements Rule
{ {
private $validated_schema = false; private $validated_schema = false;
private $isEnterprise = false;
private array $endings; private array $endings;
private bool $hasCompanyKey;
private array $enterprise_endings;
public function __construct() public function __construct()
{ {

View File

@ -11,19 +11,20 @@
namespace App\Jobs\Brevo; namespace App\Jobs\Brevo;
use App\Libraries\MultiDB;
use App\Services\InboundMail\InboundMail;
use App\Services\InboundMail\InboundMailEngine;
use App\Utils\TempFile; use App\Utils\TempFile;
use Brevo\Client\Api\InboundParsingApi; use App\Libraries\MultiDB;
use Brevo\Client\Configuration;
use Illuminate\Support\Carbon;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Support\Carbon;
use Brevo\Client\Configuration;
use Illuminate\Http\UploadedFile;
use Illuminate\Queue\SerializesModels;
use Brevo\Client\Api\InboundParsingApi;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\InboundMail\InboundMail;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use App\Services\InboundMail\InboundMailEngine;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Http\UploadedFile;
class ProcessBrevoInboundWebhook implements ShouldQueue class ProcessBrevoInboundWebhook implements ShouldQueue
{ {
@ -111,7 +112,6 @@ class ProcessBrevoInboundWebhook implements ShouldQueue
*/ */
public function __construct(private array $input) public function __construct(private array $input)
{ {
$this->engine = new InboundMailEngine();
} }
/** /**
@ -134,18 +134,24 @@ class ProcessBrevoInboundWebhook implements ShouldQueue
// match company // match company
$company = MultiDB::findAndSetDbByExpenseMailbox($recipient); $company = MultiDB::findAndSetDbByExpenseMailbox($recipient);
if (!$company) { if (!$company) {
nlog('[ProcessBrevoInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from brevo: ' . $recipient); nlog('[ProcessBrevoInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from brevo: ' . $recipient);
continue; continue;
} }
$this->engine = new InboundMailEngine($company);
$foundOneRecipient = true; $foundOneRecipient = true;
try { // important to save meta if something fails here to prevent spam try { // important to save meta if something fails here to prevent spam
$company_brevo_secret = $company->settings?->email_sending_method === 'client_brevo' && $company->settings?->brevo_secret ? $company->settings?->brevo_secret : null; if(strlen($company->getSetting('brevo_secret') ?? '') < 2 && empty(config('services.brevo.secret'))){
if (empty($company_brevo_secret) && empty(config('services.brevo.secret'))) nlog("No Brevo Configuration available for this company");
throw new \Error("[ProcessBrevoInboundWebhook] no brevo credenitals found, we cannot get the attachement"); throw new \Error("[ProcessBrevoInboundWebhook] no brevo credenitals found, we cannot get the attachement");
}
$company_brevo_secret = strlen($company->getSetting('brevo_secret') ?? '') < 2 ? $company->getSetting('brevo_secret') : config('services.brevo.secret');
// prepare data for ingresEngine // prepare data for ingresEngine
$inboundMail = new InboundMail(); $inboundMail = new InboundMail();
@ -160,8 +166,10 @@ class ProcessBrevoInboundWebhook implements ShouldQueue
// parse documents as UploadedFile from webhook-data // parse documents as UploadedFile from webhook-data
foreach ($this->input["Attachments"] as $attachment) { foreach ($this->input["Attachments"] as $attachment) {
// @todo - i think this allows switching between client configured brevo AND system configured brevo
// download file and save to tmp dir // download file and save to tmp dir
if (!empty($company_brevo_secret)) { if (!empty($company_brevo_secret))
{
try { try {
@ -220,4 +228,17 @@ class ProcessBrevoInboundWebhook implements ShouldQueue
$this->engine->saveMeta($this->input["From"]["Address"], $recipient, true); $this->engine->saveMeta($this->input["From"]["Address"], $recipient, true);
} }
} }
public function middleware()
{
return [new WithoutOverlapping($this->input["From"]["Address"])];
}
public function failed($exception)
{
nlog("BREVO:: Ingest Exception:: => ".$exception->getMessage());
config(['queue.failed.driver' => null]);
}
} }

View File

@ -11,16 +11,17 @@
namespace App\Jobs\Mailgun; namespace App\Jobs\Mailgun;
use App\Libraries\MultiDB; use App\Models\Company;
use App\Services\InboundMail\InboundMail;
use App\Services\InboundMail\InboundMailEngine;
use App\Utils\TempFile; use App\Utils\TempFile;
use Illuminate\Support\Carbon; use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Support\Carbon;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\InboundMail\InboundMail;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use App\Services\InboundMail\InboundMailEngine;
use Illuminate\Queue\SerializesModels;
class ProcessMailgunInboundWebhook implements ShouldQueue class ProcessMailgunInboundWebhook implements ShouldQueue
{ {
@ -34,9 +35,9 @@ class ProcessMailgunInboundWebhook implements ShouldQueue
* Create a new job instance. * Create a new job instance.
* $input consists of 3 informations: sender/from|recipient/to|messageUrl * $input consists of 3 informations: sender/from|recipient/to|messageUrl
*/ */
public function __construct(private string $sender, private string $recipient, private string $message_url) public function __construct(private string $sender, private string $recipient, private string $message_url, private Company $company)
{ {
$this->engine = new InboundMailEngine(); $this->engine = new InboundMailEngine($company);
} }
/** /**
@ -176,19 +177,20 @@ class ProcessMailgunInboundWebhook implements ShouldQueue
return; return;
} }
// lets assess this at a higher level to ensure that only valid email inboxes are processed.
// match company // match company
$company = MultiDB::findAndSetDbByExpenseMailbox($to); // $company = MultiDB::findAndSetDbByExpenseMailbox($to);
if (!$company) { // if (!$company) {
nlog('[ProcessMailgunInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from mailgun: ' . $to); // nlog('[ProcessMailgunInboundWebhook] unknown Expense Mailbox occured while handling an inbound email from mailgun: ' . $to);
$this->engine->saveMeta($from, $to, true); // important to save this, to protect from spam // $this->engine->saveMeta($from, $to, true); // important to save this, to protect from spam
return; // return;
} // }
try { // important to save meta if something fails here to prevent spam try { // important to save meta if something fails here to prevent spam
// fetch message from mailgun-api // fetch message from mailgun-api
$company_mailgun_domain = $company->getSetting('email_sending_method') == 'client_mailgun' && strlen($company->getSetting('mailgun_domain') ?? '') > 2 ? $company->getSetting('mailgun_domain') : null; $company_mailgun_domain = $this->company->getSetting('email_sending_method') == 'client_mailgun' && strlen($this->company->getSetting('mailgun_domain') ?? '') > 2 ? $this->company->getSetting('mailgun_domain') : null;
$company_mailgun_secret = $company->getSetting('email_sending_method') == 'client_mailgun' && strlen($company->getSetting('mailgun_secret') ?? '') > 2 ? $company->getSetting('mailgun_secret') : null; $company_mailgun_secret = $this->company->getSetting('email_sending_method') == 'client_mailgun' && strlen($this->company->getSetting('mailgun_secret') ?? '') > 2 ? $this->company->getSetting('mailgun_secret') : null;
if (!($company_mailgun_domain && $company_mailgun_secret) && !(config('services.mailgun.domain') && config('services.mailgun.secret'))) if (!($company_mailgun_domain && $company_mailgun_secret) && !(config('services.mailgun.domain') && config('services.mailgun.secret')))
throw new \Error("[ProcessMailgunInboundWebhook] no mailgun credentials found, we cannot get the attachements and files"); throw new \Error("[ProcessMailgunInboundWebhook] no mailgun credentials found, we cannot get the attachements and files");

View File

@ -47,8 +47,10 @@ class MindeeEDocument extends AbstractService
public function run(): Expense public function run(): Expense
{ {
$api_key = config('services.mindee.api_key'); $api_key = config('services.mindee.api_key');
if (!$api_key) if (!$api_key)
throw new Exception('Mindee API key not configured'); throw new Exception('Mindee API key not configured');
$this->checkLimits(); $this->checkLimits();
// perform parsing // perform parsing
@ -69,42 +71,52 @@ class MindeeEDocument extends AbstractService
$invoiceCurrency = $prediction->locale->currency; $invoiceCurrency = $prediction->locale->currency;
$country = $prediction->locale->country; $country = $prediction->locale->country;
$expense = Expense::where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first(); $expense = Expense::query()
if (empty($expense)) { ->where('company_id', $this->company->id)
->where('amount', $grandTotalAmount)
->where("transaction_reference", $documentno)
->whereDate("date", $documentdate)
->first();
if (!$expense) {
// The document does not exist as an expense // The document does not exist as an expense
// Handle accordingly // Handle accordingly
/** @var \App\Models\Currency $currency */
$currency = app('currencies')->first(function ($c) use ($invoiceCurrency){
/** @var \App\Models\Currency $c */
return $c->code == $invoiceCurrency;
});
$expense = ExpenseFactory::create($this->company->id, $this->company->owner()->id); $expense = ExpenseFactory::create($this->company->id, $this->company->owner()->id);
$expense->date = $documentdate; $expense->date = $documentdate;
$expense->user_id = $this->company->owner()->id;
$expense->company_id = $this->company->id;
$expense->public_notes = $documentno; $expense->public_notes = $documentno;
$expense->currency_id = Currency::whereCode($invoiceCurrency)->first()?->id || $this->company->settings->currency_id; $expense->currency_id = $currency ? $currency->id : $this->company->settings->currency_id;
$expense->save(); $expense->save();
$this->saveDocuments([ $this->saveDocuments([
$this->file, $this->file,
TempFile::UploadedFileFromRaw(strval($result->document), $documentno . "_mindee_orc_result.txt", "text/plain") TempFile::UploadedFileFromRaw(strval($result->document), $documentno . "_mindee_orc_result.txt", "text/plain")
], $expense); ], $expense);
$expense->saveQuietly(); // $expense->saveQuietly();
$expense->uses_inclusive_taxes = True; $expense->uses_inclusive_taxes = True;
$expense->amount = $grandTotalAmount; $expense->amount = $grandTotalAmount;
$counter = 1; $counter = 1;
foreach ($prediction->taxes as $taxesElem) { foreach ($prediction->taxes as $taxesElem) {
$expense->{"tax_amount$counter"} = $taxesElem->amount; $expense->{"tax_amount{$counter}"} = $taxesElem->amount;
$expense->{"tax_rate$counter"} = $taxesElem->rate; $expense->{"tax_rate{$counter}"} = $taxesElem->rate;
$counter++; $counter++;
} }
$vendor = null; /** @var \App\Models\VendorContact $vendor_contact */
$vendor_contact = VendorContact::where("company_id", $this->company->id)->where("email", $prediction->supplierEmail)->first(); $vendor_contact = VendorContact::query()->where("company_id", $this->company->id)->where("email", $prediction->supplierEmail)->first();
if ($vendor_contact)
$vendor = $vendor_contact->vendor; /** @var \App\Models\Vendor|null $vendor */
if (!$vendor) $vendor = $vendor_contact ? $vendor_contact->vendor : Vendor::query()->where("company_id", $this->company->id)->where("name", $prediction->supplierName)->first();
$vendor = Vendor::where("company_id", $this->company->id)->where("name", $prediction->supplierName)->first();
if ($vendor) { if ($vendor) {
// Vendor found
$expense->vendor_id = $vendor->id; $expense->vendor_id = $vendor->id;
} else { } else {
$vendor = VendorFactory::create($this->company->id, $this->company->owner()->id); $vendor = VendorFactory::create($this->company->id, $this->company->owner()->id);
@ -116,15 +128,19 @@ class MindeeEDocument extends AbstractService
// $vendor->address2 = $address_2; // $vendor->address2 = $address_2;
// $vendor->city = $city; // $vendor->city = $city;
// $vendor->postal_code = $postcode; // $vendor->postal_code = $postcode;
/** @var ?\App\Models\Country $country */
$country = app('countries')->first(function ($c) use ($country) { $country = app('countries')->first(function ($c) use ($country) {
/** @var \App\Models\Country $c */
return $c->iso_3166_2 == $country || $c->iso_3166_3 == $country; return $c->iso_3166_2 == $country || $c->iso_3166_3 == $country;
}); });
if ($country) if ($country)
$vendor->country_id = $country->id; $vendor->country_id = $country->id;
$vendor->save(); $vendor->save();
if ($prediction->supplierEmail) { if (strlen($prediction->supplierEmail ?? '') > 2) {
$vendor_contact = VendorContactFactory::create($this->company->id, $this->company->owner()->id); $vendor_contact = VendorContactFactory::create($this->company->id, $this->company->owner()->id);
$vendor_contact->vendor_id = $vendor->id; $vendor_contact->vendor_id = $vendor->id;
$vendor_contact->email = $prediction->supplierEmail; $vendor_contact->email = $prediction->supplierEmail;
@ -138,8 +154,9 @@ class MindeeEDocument extends AbstractService
// The document exists as an expense // The document exists as an expense
// Handle accordingly // Handle accordingly
nlog("Mindee: Document already exists"); nlog("Mindee: Document already exists");
$expense->private_notes = $expense->private_notes . ctrans("texts.edocument_import_already_exists", ["date" => time()]); $expense->private_notes = $expense->private_notes . ctrans("texts.edocument_import_already_exists", ["date" => now()->format('Y-m-d')]);
} }
$expense->save(); $expense->save();
return $expense; return $expense;
} }

View File

@ -102,17 +102,20 @@ class ZugferdEDocument extends AbstractService
if (array_key_exists("VA", $taxtype)) { if (array_key_exists("VA", $taxtype)) {
$taxid = $taxtype["VA"]; $taxid = $taxtype["VA"];
} }
$vendor = Vendor::where("company_id", $user->company()->id)->where('vat_number', $taxid)->first();
if (!$vendor) {
$vendor_contact = VendorContact::where("company_id", $user->company()->id)->where("email", $contact_email)->first();
if ($vendor_contact)
$vendor = $vendor_contact->vendor;
}
if (!$vendor)
$vendor = Vendor::where("company_id", $user->company()->id)->where("name", $person_name)->first();
if (!empty($vendor)) { $vendor = Vendor::query()
// Vendor found ->where("company_id", $user->company()->id)
->where(function ($q) use($taxid, $person_name, $contact_email){
$q->when(!is_null($taxid), function ($when_query) use($taxid){
$when_query->orWhere('vat_number', $taxid);
})
->orWhere("name", $person_name)
->orWhereHas('contacts', function ($qq) use ($contact_email){
$qq->where("email", $contact_email);
});
})->first();
if ($vendor) {
$expense->vendor_id = $vendor->id; $expense->vendor_id = $vendor->id;
} else { } else {
$vendor = VendorFactory::create($this->company->id, $user->id); $vendor = VendorFactory::create($this->company->id, $user->id);

View File

@ -35,7 +35,7 @@ class InboundMailEngine
private array $globalBlacklist; private array $globalBlacklist;
private array $globalWhitelist; // only for global validation, not for allowing to send something into the company, should be used to disabled blocking for mass-senders private array $globalWhitelist; // only for global validation, not for allowing to send something into the company, should be used to disabled blocking for mass-senders
public function __construct() public function __construct(private Company $company)
{ {
// only for global validation, not for allowing to send something into the company, should be used to disabled blocking for mass-senders // only for global validation, not for allowing to send something into the company, should be used to disabled blocking for mass-senders
@ -52,19 +52,19 @@ class InboundMailEngine
return; return;
// Expense Mailbox => will create an expense // Expense Mailbox => will create an expense
$company = MultiDB::findAndSetDbByExpenseMailbox($email->to); // $company = MultiDB::findAndSetDbByExpenseMailbox($email->to);
if (!$company) { // if (!$company) {
$this->saveMeta($email->from, $email->to, true); // $this->saveMeta($email->from, $email->to, true);
return; // return;
} // }
// check if company plan matches requirements // check if company plan matches requirements
if (Ninja::isHosted() && !($company->account->isPaid() && $company->account->plan == 'enterprise')) { if (Ninja::isHosted() && !($this->company->account->isPaid() && $this->company->account->plan == 'enterprise')) {
$this->saveMeta($email->from, $email->to); $this->saveMeta($email->from, $email->to);
return; return;
} }
$this->createExpenses($company, $email); $this->createExpenses($email);
$this->saveMeta($email->from, $email->to); $this->saveMeta($email->from, $email->to);
} }
@ -145,6 +145,8 @@ class InboundMailEngine
// TODO: ignore, when known sender (for heavy email-usage mostly on isHosted()) // TODO: ignore, when known sender (for heavy email-usage mostly on isHosted())
// TODO: handle external blocking // TODO: handle external blocking
} }
//@todo - refactor
public function saveMeta(string $from, string $to, bool $isUnknownRecipent = false) public function saveMeta(string $from, string $to, bool $isUnknownRecipent = false)
{ {
// save cache // save cache
@ -161,24 +163,24 @@ class InboundMailEngine
} }
// MAIN-PROCESSORS // MAIN-PROCESSORS
protected function createExpenses(Company $company, InboundMail $email) protected function createExpenses(InboundMail $email)
{ {
// Skipping executions: will not result in not saving Metadata to prevent usage of these conditions, to spam // Skipping executions: will not result in not saving Metadata to prevent usage of these conditions, to spam
if (!($company?->expense_mailbox_active ?: false)) { if (!$this->company->expense_mailbox_active) {
$this->logBlocked($company, 'mailbox not active for this company. from: ' . $email->from); $this->logBlocked($this->company, 'mailbox not active for this company. from: ' . $email->from);
return; return;
} }
if (!$this->validateExpenseSender($company, $email)) { if (!$this->validateExpenseSender($email)) {
$this->logBlocked($company, 'invalid sender of an ingest email for this company. from: ' . $email->from); $this->logBlocked($this->company, 'invalid sender of an ingest email for this company. from: ' . $email->from);
return; return;
} }
if (sizeOf($email->documents) == 0) { if (sizeOf($email->documents) == 0) {
$this->logBlocked($company, 'email does not contain any attachments and is likly not an expense. from: ' . $email->from); $this->logBlocked($this->company, 'email does not contain any attachments and is likly not an expense. from: ' . $email->from);
return; return;
} }
// prepare data // prepare data
$expense_vendor = $this->getVendor($company, $email); $expense_vendor = $this->getVendor($email);
$this->processHtmlBodyToDocument($email); $this->processHtmlBodyToDocument($email);
$parsed_expense_ids = []; // used to check if an expense was already matched within this job $parsed_expense_ids = []; // used to check if an expense was already matched within this job
@ -192,7 +194,7 @@ class InboundMailEngine
// check if document can be parsed to an expense // check if document can be parsed to an expense
try { try {
$expense = (new ParseEDocument($document, $company))->run(); $expense = (new ParseEDocument($document, $this->company))->run();
// check if expense was already matched within this job and skip if true // check if expense was already matched within this job and skip if true
if (array_search($expense->id, $parsed_expense_ids)) if (array_search($expense->id, $parsed_expense_ids))
@ -213,7 +215,7 @@ class InboundMailEngine
// populate missing data with data from email // populate missing data with data from email
if (!$expense) if (!$expense)
$expense = ExpenseFactory::create($company->id, $company->owner()->id); $expense = ExpenseFactory::create($this->company->id, $this->company->owner()->id);
$is_imported_by_parser = array_search($expense->id, $parsed_expense_ids); $is_imported_by_parser = array_search($expense->id, $parsed_expense_ids);
@ -256,61 +258,61 @@ class InboundMailEngine
$email->body_document = TempFile::UploadedFileFromRaw($email->body, "E-Mail.html", "text/html"); $email->body_document = TempFile::UploadedFileFromRaw($email->body, "E-Mail.html", "text/html");
} }
private function validateExpenseSender(Company $company, InboundMail $email) private function validateExpenseSender(InboundMail $email)
{ {
$parts = explode('@', $email->from); $parts = explode('@', $email->from);
$domain = array_pop($parts); $domain = array_pop($parts);
// whitelists // whitelists
$whitelist = explode(",", $company->inbound_mailbox_whitelist); $whitelist = explode(",", $this->company->inbound_mailbox_whitelist);
if (in_array($email->from, $whitelist)) if (in_array($email->from, $whitelist))
return true; return true;
if (in_array($domain, $whitelist)) if (in_array($domain, $whitelist))
return true; return true;
$blacklist = explode(",", $company->inbound_mailbox_blacklist); $blacklist = explode(",", $this->company->inbound_mailbox_blacklist);
if (in_array($email->from, $blacklist)) if (in_array($email->from, $blacklist))
return false; return false;
if (in_array($domain, $blacklist)) if (in_array($domain, $blacklist))
return false; return false;
// allow unknown // allow unknown
if ($company->inbound_mailbox_allow_unknown) if ($this->company->inbound_mailbox_allow_unknown)
return true; return true;
// own users // own users
if ($company->inbound_mailbox_allow_company_users && $company->users()->where("email", $email->from)->exists()) if ($this->company->inbound_mailbox_allow_company_users && $this->company->users()->where("email", $email->from)->exists())
return true; return true;
// from vendors // from vendors
if ($company->inbound_mailbox_allow_vendors && VendorContact::where("company_id", $company->id)->where("email", $email->from)->exists()) if ($this->company->inbound_mailbox_allow_vendors && VendorContact::where("company_id", $this->company->id)->where("email", $email->from)->exists())
return true; return true;
// from clients // from clients
if ($company->inbound_mailbox_allow_clients && ClientContact::where("company_id", $company->id)->where("email", $email->from)->exists()) if ($this->company->inbound_mailbox_allow_clients && ClientContact::where("company_id", $this->company->id)->where("email", $email->from)->exists())
return true; return true;
// denie // denie
return false; return false;
} }
private function getClient(Company $company, InboundMail $email)
{
$clientContact = ClientContact::where("company_id", $company->id)->where("email", $email->from)->first();
if (!$clientContact)
return null;
return $clientContact->client(); // private function getClient(InboundMail $email)
} // {
private function getVendor(Company $company, InboundMail $email) // $clientContact = ClientContact::where("company_id", $this->company->id)->where("email", $email->from)->first();
{ // if (!$clientContact)
$vendorContact = VendorContact::where("company_id", $company->id)->where("email", $email->from)->first(); // return null;
if (!$vendorContact)
return null;
return $vendorContact->vendor(); // return $clientContact->client();
// }
private function getVendor(InboundMail $email)
{
$vendorContact = VendorContact::with('vendor')->where("company_id", $this->company->id)->where("email", $email->from)->first();
return $vendorContact ? $vendorContact->vendor : null;
} }
private function logBlocked(Company $company, string $data) private function logBlocked(Company $company, string $data)
{ {
nlog("[InboundMailEngine][company:" . $company->company_key . "] " . $data); nlog("[InboundMailEngine][company:" . $this->company->company_key . "] " . $data);
( (
new SystemLogger( new SystemLogger(

View File

@ -11,7 +11,7 @@ return new class extends Migration {
public function up(): void public function up(): void
{ {
Schema::table('companies', function (Blueprint $table) { Schema::table('companies', function (Blueprint $table) {
$table->boolean("expense_mailbox_active")->default(true); $table->boolean("expense_mailbox_active")->default(false);
$table->string("expense_mailbox")->nullable(); $table->string("expense_mailbox")->nullable();
$table->boolean("inbound_mailbox_allow_company_users")->default(false); $table->boolean("inbound_mailbox_allow_company_users")->default(false);
$table->boolean("inbound_mailbox_allow_vendors")->default(false); $table->boolean("inbound_mailbox_allow_vendors")->default(false);