mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #8181 from turbo124/v5-develop
Send payment emails to all contacts on invoice
This commit is contained in:
commit
e2c7d42247
@ -28,10 +28,14 @@ use App\Models\Invoice;
|
|||||||
use App\Models\InvoiceInvitation;
|
use App\Models\InvoiceInvitation;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\Paymentable;
|
use App\Models\Paymentable;
|
||||||
|
use App\Models\PurchaseOrder;
|
||||||
|
use App\Models\PurchaseOrderInvitation;
|
||||||
|
use App\Models\Quote;
|
||||||
use App\Models\QuoteInvitation;
|
use App\Models\QuoteInvitation;
|
||||||
use App\Models\RecurringInvoiceInvitation;
|
use App\Models\RecurringInvoiceInvitation;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Vendor;
|
use App\Models\Vendor;
|
||||||
|
use App\Models\VendorContact;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
@ -397,30 +401,50 @@ class CheckData extends Command
|
|||||||
QuoteInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]);
|
QuoteInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]);
|
||||||
CreditInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]);
|
CreditInvitation::where('deleted_at',"0000-00-00 00:00:00.000000")->withTrashed()->update(['deleted_at' => null]);
|
||||||
|
|
||||||
$entities = ['invoice', 'quote', 'credit', 'recurring_invoice'];
|
|
||||||
|
|
||||||
foreach($entities as $entity)
|
collect([Invoice::class, Quote::class, Credit::class, PurchaseOrder::class])->each(function ($entity){
|
||||||
|
|
||||||
|
if($entity::doesntHave('invitations')->count() > 0)
|
||||||
{
|
{
|
||||||
$table = "{$entity}s";
|
|
||||||
$invitation_table = "{$entity}_invitations";
|
|
||||||
|
|
||||||
$entities = DB::table($table)
|
$entity::doesntHave('invitations')->cursor()->each(function ($entity) {
|
||||||
->leftJoin($invitation_table, function ($join) use($invitation_table, $table, $entity){
|
|
||||||
$join->on("{$invitation_table}.{$entity}_id", '=', "{$table}.id");
|
|
||||||
// ->whereNull("{$invitation_table}.deleted_at");
|
|
||||||
})
|
|
||||||
->groupBy("{$table}.id", "{$table}.user_id", "{$table}.company_id", "{$table}.client_id")
|
|
||||||
->havingRaw("count({$invitation_table}.id) = 0")
|
|
||||||
->get(["{$table}.id", "{$table}.user_id", "{$table}.company_id", "{$table}.client_id"]);
|
|
||||||
|
|
||||||
|
$client_vendor_key = 'client_id';
|
||||||
|
$contact_id = 'client_contact_id';
|
||||||
|
$contact_class = ClientContact::class;
|
||||||
|
|
||||||
$this->logMessage($entities->count()." {$table} without any invitations");
|
$entity_key = \Illuminate\Support\Str::of(class_basename($entity))->snake()->append('_id')->value;
|
||||||
|
$entity_obj = get_class($entity).'Invitation';
|
||||||
|
|
||||||
if ($this->option('fix') == 'true')
|
if($entity instanceof PurchaseOrder){
|
||||||
$this->fixInvitations($entities, $entity);
|
$client_vendor_key = 'vendor_id';
|
||||||
|
$contact_id = 'vendor_contact_id';
|
||||||
|
$contact_class = VendorContact::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invitation = new $entity_obj();
|
||||||
|
$invitation->company_id = $entity->company_id;
|
||||||
|
$invitation->user_id = $entity->user_id;
|
||||||
|
$invitation->{$entity_key} = $entity->id;
|
||||||
|
$invitation->{$contact_id} = $contact_class::where('company_id', $entity->company_id)->where($client_vendor_key,$entity->{$client_vendor_key})->first()->id;
|
||||||
|
$invitation->key = Str::random(config('ninja.key_length'));
|
||||||
|
|
||||||
|
$this->logMessage("Add invitation for {$entity_key} - {$entity->id}");
|
||||||
|
|
||||||
|
try{
|
||||||
|
$invitation->save();
|
||||||
|
}
|
||||||
|
catch(\Exception $e){
|
||||||
|
$this->logMessage($e->getMessage());
|
||||||
|
$invitation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fixInvitations($entities, $entity)
|
private function fixInvitations($entities, $entity)
|
||||||
|
@ -51,6 +51,7 @@ class RecurringInvoiceController extends Controller
|
|||||||
*
|
*
|
||||||
* @param ShowRecurringInvoiceRequest $request
|
* @param ShowRecurringInvoiceRequest $request
|
||||||
* @param RecurringInvoice $recurring_invoice
|
* @param RecurringInvoice $recurring_invoice
|
||||||
|
*
|
||||||
* @return Factory|View
|
* @return Factory|View
|
||||||
*/
|
*/
|
||||||
public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
|
public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice)
|
||||||
@ -60,20 +61,24 @@ class RecurringInvoiceController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the request cancellation notification
|
||||||
|
*
|
||||||
|
* @param RequestCancellationRequest $request [description]
|
||||||
|
* @param RecurringInvoice $recurring_invoice [description]
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
public function requestCancellation(RequestCancellationRequest $request, RecurringInvoice $recurring_invoice)
|
public function requestCancellation(RequestCancellationRequest $request, RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
nlog('outside cancellation');
|
|
||||||
|
|
||||||
if ($recurring_invoice->subscription?->allow_cancellation) {
|
if ($recurring_invoice->subscription?->allow_cancellation) {
|
||||||
nlog('inside the cancellation');
|
|
||||||
|
|
||||||
$nmo = new NinjaMailerObject;
|
$nmo = new NinjaMailerObject;
|
||||||
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->user()))->build()));
|
$nmo->mailable = (new NinjaMailer((new ClientContactRequestCancellationObject($recurring_invoice, auth()->user(), false))->build()));
|
||||||
$nmo->company = $recurring_invoice->company;
|
$nmo->company = $recurring_invoice->company;
|
||||||
$nmo->settings = $recurring_invoice->company->settings;
|
$nmo->settings = $recurring_invoice->company->settings;
|
||||||
|
|
||||||
// $notifiable_users = $this->filterUsersByPermissions($recurring_invoice->company->company_users, $recurring_invoice, ['recurring_cancellation']);
|
|
||||||
|
|
||||||
$recurring_invoice->company->company_users->each(function ($company_user) use ($nmo) {
|
$recurring_invoice->company->company_users->each(function ($company_user) use ($nmo) {
|
||||||
$methods = $this->findCompanyUserNotificationType($company_user, ['recurring_cancellation', 'all_notifications']);
|
$methods = $this->findCompanyUserNotificationType($company_user, ['recurring_cancellation', 'all_notifications']);
|
||||||
|
|
||||||
@ -86,15 +91,6 @@ class RecurringInvoiceController extends Controller
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// $notifiable_users->each(function ($company_user) use($nmo){
|
|
||||||
|
|
||||||
// $nmo->to_user = $company_user->user;
|
|
||||||
// NinjaMailerJob::dispatch($nmo);
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
//$recurring_invoice->user->notify(new ClientContactRequestCancellation($recurring_invoice, auth()->user()));
|
|
||||||
|
|
||||||
return $this->render('recurring_invoices.cancellation.index', [
|
return $this->render('recurring_invoices.cancellation.index', [
|
||||||
'invoice' => $recurring_invoice,
|
'invoice' => $recurring_invoice,
|
||||||
]);
|
]);
|
||||||
|
@ -86,7 +86,7 @@ class CreateAccount
|
|||||||
$sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies');
|
$sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies');
|
||||||
$sp794f3f->account_sms_verified = true;
|
$sp794f3f->account_sms_verified = true;
|
||||||
|
|
||||||
if(in_array($this->getDomain($this->request['email']), ['givmail.com','yopmail.com','gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com', 'aol.com', 'mail.ru'])){
|
if(in_array($this->getDomain($this->request['email']), ['givmail.com','yopmail.com','gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com', 'aol.com', 'mail.ru','brand-app.biz','proton.me','ema-sofia.eu'])){
|
||||||
$sp794f3f->account_sms_verified = false;
|
$sp794f3f->account_sms_verified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,6 +506,12 @@ class CompanyImport implements ShouldQueue
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Ninja::isHosted())
|
||||||
|
{
|
||||||
|
$this->company->portal_mode = 'sub_domain';
|
||||||
|
$this->company->portal_domain = '';
|
||||||
|
}
|
||||||
|
|
||||||
$this->company->save();
|
$this->company->save();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -19,6 +19,7 @@ use App\Mail\Admin\InventoryNotificationObject;
|
|||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
|
use App\Utils\Traits\Notifications\UserNotifies;
|
||||||
use App\Utils\Traits\NumberFormatter;
|
use App\Utils\Traits\NumberFormatter;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@ -29,7 +30,7 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
|
|
||||||
class AdjustProductInventory implements ShouldQueue
|
class AdjustProductInventory implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
|
||||||
|
|
||||||
public Company $company;
|
public Company $company;
|
||||||
|
|
||||||
@ -146,8 +147,22 @@ class AdjustProductInventory implements ShouldQueue
|
|||||||
$nmo->mailable = new NinjaMailer((new InventoryNotificationObject($product, $notification_level))->build());
|
$nmo->mailable = new NinjaMailer((new InventoryNotificationObject($product, $notification_level))->build());
|
||||||
$nmo->company = $this->company;
|
$nmo->company = $this->company;
|
||||||
$nmo->settings = $this->company->settings;
|
$nmo->settings = $this->company->settings;
|
||||||
|
|
||||||
|
// $product->company_users->each(function ($cu) use($product, $nmo){
|
||||||
|
|
||||||
|
// if($this->checkNotificationExists($cu, $product, ['inventory_all', 'inventory_user']))
|
||||||
|
// {
|
||||||
|
|
||||||
|
// $nmo->to_user = $cu->user;
|
||||||
|
// (new NinjaMailerJob($nmo))->handle();
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// });
|
||||||
|
|
||||||
$nmo->to_user = $this->company->owner();
|
$nmo->to_user = $this->company->owner();
|
||||||
|
|
||||||
NinjaMailerJob::dispatch($nmo);
|
(new NinjaMailerJob($nmo))->handle();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -516,6 +516,12 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Ninja::isHosted())
|
||||||
|
{
|
||||||
|
$data['portal_mode'] = 'subdomain';
|
||||||
|
$data['portal_domain'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
$data['settings'] = $company_settings;
|
$data['settings'] = $company_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,6 +570,11 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
TaxRate::reguard();
|
TaxRate::reguard();
|
||||||
|
|
||||||
|
if(TaxRate::count() > 0){
|
||||||
|
$this->company->enabled_tax_rates = 2;
|
||||||
|
$this->company->save();
|
||||||
|
}
|
||||||
|
|
||||||
/*Improve memory handling by setting everything to null when we have finished*/
|
/*Improve memory handling by setting everything to null when we have finished*/
|
||||||
$data = null;
|
$data = null;
|
||||||
$rules = null;
|
$rules = null;
|
||||||
|
@ -101,6 +101,11 @@ class Document extends BaseModel
|
|||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class)->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
public function generateUrl($absolute = false)
|
public function generateUrl($absolute = false)
|
||||||
{
|
{
|
||||||
$url = Storage::disk($this->disk)->url($this->url);
|
$url = Storage::disk($this->disk)->url($this->url);
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\QuoteInvitation;
|
||||||
|
use App\Models\RecurringInvoiceInvitation;
|
||||||
|
|
||||||
class ClientContactObserver
|
class ClientContactObserver
|
||||||
{
|
{
|
||||||
@ -45,10 +48,38 @@ class ClientContactObserver
|
|||||||
*/
|
*/
|
||||||
public function deleted(ClientContact $clientContact)
|
public function deleted(ClientContact $clientContact)
|
||||||
{
|
{
|
||||||
|
$client_contact_id = $clientContact->id;
|
||||||
|
|
||||||
$clientContact->invoice_invitations()->delete();
|
$clientContact->invoice_invitations()->delete();
|
||||||
$clientContact->quote_invitations()->delete();
|
$clientContact->quote_invitations()->delete();
|
||||||
$clientContact->credit_invitations()->delete();
|
$clientContact->credit_invitations()->delete();
|
||||||
$clientContact->recurring_invoice_invitations()->delete();
|
$clientContact->recurring_invoice_invitations()->delete();
|
||||||
|
|
||||||
|
//ensure entity state is preserved
|
||||||
|
|
||||||
|
InvoiceInvitation::withTrashed()->where('client_contact_id', 1)->cursor()->each(function ($invite){
|
||||||
|
|
||||||
|
if($invite->invoice()->doesnthave('invitations'))
|
||||||
|
$invite->invoice->service()->createInvitations();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
QuoteInvitation::withTrashed()->where('client_contact_id', 1)->cursor()->each(function ($invite){
|
||||||
|
|
||||||
|
if($invite->invoice()->doesnthave('invitations'))
|
||||||
|
$invite->quote->service()->createInvitations();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
RecurringInvoiceInvitation::withTrashed()->where('client_contact_id', 1)->cursor()->each(function ($invite){
|
||||||
|
|
||||||
|
if($invite->recurring_invoice()->doesnthave('invitations'))
|
||||||
|
$invite->quote->service()->createInvitations();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
85
app/Observers/VendorContactObserver.php
Normal file
85
app/Observers/VendorContactObserver.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Models\PurchaseOrderInvitation;
|
||||||
|
use App\Models\VendorContact;
|
||||||
|
|
||||||
|
class VendorContactObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the vendor contact "created" event.
|
||||||
|
*
|
||||||
|
* @param VendorContact $vendorContact
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function created(VendorContact $vendorContact)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the vendor contact "updated" event.
|
||||||
|
*
|
||||||
|
* @param VendorContact $vendorContact
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updated(VendorContact $vendorContact)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the vendor contact "deleted" event.
|
||||||
|
*
|
||||||
|
* @param VendorContact $vendorContact
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleted(VendorContact $vendorContact)
|
||||||
|
{
|
||||||
|
|
||||||
|
$vendor_contact_id = $vendorContact->id;
|
||||||
|
|
||||||
|
$vendorContact->purchase_order_invitations()->delete();
|
||||||
|
|
||||||
|
PurchaseOrderInvitation::withTrashed()->where('vendor_contact_id', 1)->cursor()->each(function ($invite){
|
||||||
|
|
||||||
|
if($invite->purchase_order()->doesnthave('invitations'))
|
||||||
|
$invite->purchase_order->service()->createInvitations();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the vendor contact "restored" event.
|
||||||
|
*
|
||||||
|
* @param VendorContact $vendorContact
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restored(VendorContact $vendorContact)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the vendor contact "force deleted" event.
|
||||||
|
*
|
||||||
|
* @param VendorContact $vendorContact
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function forceDeleted(VendorContact $vendorContact)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
namespace App\Policies;
|
namespace App\Policies;
|
||||||
|
|
||||||
|
@ -42,6 +42,17 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// DB::listen(function($query) {
|
||||||
|
// nlog(
|
||||||
|
// $query->sql,
|
||||||
|
// [
|
||||||
|
// 'bindings' => $query->bindings,
|
||||||
|
// 'time' => $query->time
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
Relation::morphMap([
|
Relation::morphMap([
|
||||||
'invoices' => Invoice::class,
|
'invoices' => Invoice::class,
|
||||||
'proposals' => Proposal::class,
|
'proposals' => Proposal::class,
|
||||||
|
@ -241,6 +241,7 @@ use App\Models\Quote;
|
|||||||
use App\Models\Subscription;
|
use App\Models\Subscription;
|
||||||
use App\Models\Task;
|
use App\Models\Task;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\VendorContact;
|
||||||
use App\Observers\AccountObserver;
|
use App\Observers\AccountObserver;
|
||||||
use App\Observers\ClientContactObserver;
|
use App\Observers\ClientContactObserver;
|
||||||
use App\Observers\ClientObserver;
|
use App\Observers\ClientObserver;
|
||||||
@ -257,6 +258,7 @@ use App\Observers\ProposalObserver;
|
|||||||
use App\Observers\PurchaseOrderObserver;
|
use App\Observers\PurchaseOrderObserver;
|
||||||
use App\Observers\QuoteObserver;
|
use App\Observers\QuoteObserver;
|
||||||
use App\Observers\SubscriptionObserver;
|
use App\Observers\SubscriptionObserver;
|
||||||
|
use App\Observers\VendorContactObserver;
|
||||||
use App\Observers\TaskObserver;
|
use App\Observers\TaskObserver;
|
||||||
use App\Observers\UserObserver;
|
use App\Observers\UserObserver;
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
@ -649,6 +651,7 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
Quote::observe(QuoteObserver::class);
|
Quote::observe(QuoteObserver::class);
|
||||||
Task::observe(TaskObserver::class);
|
Task::observe(TaskObserver::class);
|
||||||
User::observe(UserObserver::class);
|
User::observe(UserObserver::class);
|
||||||
|
VendorContact::observe(VendorContactObserver::class);
|
||||||
PurchaseOrder::observe(PurchaseOrderObserver::class);
|
PurchaseOrder::observe(PurchaseOrderObserver::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +526,7 @@ class InvoiceService
|
|||||||
$this->invoice->exchange_rate = $this->invoice->client->currency()->exchange_rate;
|
$this->invoice->exchange_rate = $this->invoice->client->currency()->exchange_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($settings->auto_bill_standard_invoices) {
|
if ($this->invoice->client->getSetting('auto_bill_standard_invoices')) {
|
||||||
$this->invoice->auto_bill_enabled = true;
|
$this->invoice->auto_bill_enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +32,27 @@ class SendEmail
|
|||||||
*/
|
*/
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$this->payment->load('company', 'client.contacts');
|
$this->payment->load('company', 'client.contacts','invoices');
|
||||||
|
|
||||||
$contact = $this->payment->client->contacts()->first();
|
$contact = $this->payment->client->contacts()->first();
|
||||||
|
|
||||||
if ($contact?->email)
|
// if ($contact?->email)
|
||||||
EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(8));
|
// EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(2));
|
||||||
|
|
||||||
|
|
||||||
|
$this->payment->invoices->sortByDesc('id')->first(function ($invoice){
|
||||||
|
|
||||||
|
$invoice->invitations->each(function ($invitation) {
|
||||||
|
|
||||||
|
if(!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||||
|
|
||||||
|
EmailPayment::dispatch($this->payment, $this->payment->company, $invitation->contact)->delay(now()->addSeconds(2));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ use App\Models\Client;
|
|||||||
use App\Models\Credit;
|
use App\Models\Credit;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
|
use App\Models\Product;
|
||||||
use App\Models\PurchaseOrder;
|
use App\Models\PurchaseOrder;
|
||||||
use App\Models\Quote;
|
use App\Models\Quote;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ trait UserNotifies
|
|||||||
}
|
}
|
||||||
|
|
||||||
//if a user owns this record or is assigned to it, they are attached the permission for notification.
|
//if a user owns this record or is assigned to it, they are attached the permission for notification.
|
||||||
if ($invitation->{$entity_name}->user_id == $company_user->_user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) {
|
if ($invitation->{$entity_name}->user_id == $company_user->user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) {
|
||||||
$required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
|
$required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
|
||||||
} else {
|
} else {
|
||||||
$required_permissions = $this->removeSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
|
$required_permissions = $this->removeSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
|
||||||
@ -67,7 +68,7 @@ trait UserNotifies
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($entity->user_id == $company_user->_user_id || $entity->assigned_user_id == $company_user->user_id) {
|
if ($entity->user_id == $company_user->user_id || $entity->assigned_user_id == $company_user->user_id) {
|
||||||
$required_permissions = $this->addSpecialUserPermissionForEntity($entity, $required_permissions);
|
$required_permissions = $this->addSpecialUserPermissionForEntity($entity, $required_permissions);
|
||||||
} else {
|
} else {
|
||||||
$required_permissions = $this->removeSpecialUserPermissionForEntity($entity, $required_permissions);
|
$required_permissions = $this->removeSpecialUserPermissionForEntity($entity, $required_permissions);
|
||||||
@ -87,22 +88,18 @@ trait UserNotifies
|
|||||||
switch ($entity) {
|
switch ($entity) {
|
||||||
case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
|
case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
|
||||||
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'payment_failure_user', 'payment_success_user']);
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'payment_failure_user', 'payment_success_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Invoice:
|
case $entity instanceof Invoice:
|
||||||
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Quote:
|
case $entity instanceof Quote:
|
||||||
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Credit:
|
case $entity instanceof Credit:
|
||||||
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof PurchaseOrder:
|
case $entity instanceof PurchaseOrder:
|
||||||
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
|
||||||
break;
|
case $entity instanceof Product:
|
||||||
|
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'inventory_user', 'inventory_all']);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,19 +110,16 @@ trait UserNotifies
|
|||||||
switch ($entity) {
|
switch ($entity) {
|
||||||
case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
|
case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
|
||||||
return array_diff($required_permissions, ['all_user_notifications', 'payment_failure_user', 'payment_success_user']);
|
return array_diff($required_permissions, ['all_user_notifications', 'payment_failure_user', 'payment_success_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Invoice:
|
case $entity instanceof Invoice:
|
||||||
return array_diff($required_permissions, ['all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
|
return array_diff($required_permissions, ['all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Quote:
|
case $entity instanceof Quote:
|
||||||
return array_diff($required_permissions, ['all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
|
return array_diff($required_permissions, ['all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof Credit:
|
case $entity instanceof Credit:
|
||||||
return array_diff($required_permissions, ['all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
|
return array_diff($required_permissions, ['all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
|
||||||
break;
|
|
||||||
case $entity instanceof PurchaseOrder:
|
case $entity instanceof PurchaseOrder:
|
||||||
return array_diff($required_permissions, ['all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
|
return array_diff($required_permissions, ['all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
|
||||||
break;
|
case $entity instanceof Product:
|
||||||
|
return array_diff($required_permissions, ['all_user_notifications', 'inventory_user']);
|
||||||
default:
|
default:
|
||||||
// code...
|
// code...
|
||||||
break;
|
break;
|
||||||
@ -165,13 +159,23 @@ trait UserNotifies
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkNotificationExists($company_user, $entity, $required_notification)
|
/**
|
||||||
|
* Underrated method right here, last ones
|
||||||
|
* are always the best
|
||||||
|
*
|
||||||
|
* @param CompanyUser $company_user
|
||||||
|
* @param Invoice | Quote | Credit | PurchaseOrder | Product $entity
|
||||||
|
* @param array $required_notification
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function checkNotificationExists($company_user, $entity, $required_notification): bool
|
||||||
{
|
{
|
||||||
/* Always make sure we push the `all_notificaitons` into the mix */
|
/* Always make sure we push the `all_notificaitons` into the mix */
|
||||||
array_push($required_notification, 'all_notifications');
|
array_push($required_notification, 'all_notifications');
|
||||||
|
|
||||||
/* Selectively add the all_user if the user is associated with the entity */
|
/* Selectively add the all_user if the user is associated with the entity */
|
||||||
if ($entity->user_id == $company_user->_user_id || $entity->assigned_user_id == $company_user->user_id) {
|
if ($entity->user_id == $company_user->user_id || $entity->assigned_user_id == $company_user->user_id) {
|
||||||
array_push($required_notification, 'all_user_notifications');
|
array_push($required_notification, 'all_user_notifications');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
183
tests/Feature/Notify/NotificationTest.php
Normal file
183
tests/Feature/Notify/NotificationTest.php
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Feature\Notify;
|
||||||
|
|
||||||
|
|
||||||
|
use App\DataMapper\CompanySettings;
|
||||||
|
use App\Models\CompanyToken;
|
||||||
|
use App\Models\CompanyUser;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Utils\Traits\Notifications\UserNotifies;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers App\Utils\Traits\Notifications\UserNotifies
|
||||||
|
*/
|
||||||
|
class NotificationTest extends TestCase
|
||||||
|
{
|
||||||
|
use UserNotifies;
|
||||||
|
use MockAccountData;
|
||||||
|
|
||||||
|
protected function setUp() :void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->withoutMiddleware(
|
||||||
|
ThrottleRequests::class
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->makeTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotificationFound()
|
||||||
|
{
|
||||||
|
$notifications = new \stdClass;
|
||||||
|
$notifications->email = ["inventory_all"];
|
||||||
|
|
||||||
|
$this->user->company_users()->where('company_id', $this->company->id)->update(['notifications' => (array)$notifications]);
|
||||||
|
|
||||||
|
$this->assertTrue(property_exists($this->cu->notifications,'email'));
|
||||||
|
|
||||||
|
$p = Product::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'company_id' => $this->company->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$notification_users = $this->filterUsersByPermissions($this->company->company_users, $p, ['inventory_all']);
|
||||||
|
$this->assertCount(1, $notification_users->toArray());
|
||||||
|
|
||||||
|
$notification_users = $this->filterUsersByPermissions($this->company->company_users, $p, ['inventory_user']);
|
||||||
|
$this->assertCount(0, $notification_users->toArray());
|
||||||
|
|
||||||
|
$notification_users = $this->filterUsersByPermissions($this->company->company_users, $p, ['inventory_user','invalid notification']);
|
||||||
|
$this->assertCount(0, $notification_users->toArray());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllNotificationsFires()
|
||||||
|
{
|
||||||
|
$notifications = new \stdClass;
|
||||||
|
$notifications->email = ["all_notifications"];
|
||||||
|
|
||||||
|
$p = Product::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'company_id' => $this->company->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->user->company_users()->where('company_id', $this->company->id)->update(['notifications' => (array)$notifications]);
|
||||||
|
|
||||||
|
$notification_users = $this->filterUsersByPermissions($this->company->company_users, $p, ['inventory_all']);
|
||||||
|
$this->assertCount(1, $notification_users->toArray());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAllNotificationsFiresForUser()
|
||||||
|
{
|
||||||
|
$notifications = new \stdClass;
|
||||||
|
$notifications->email = ["all_user_notifications"];
|
||||||
|
|
||||||
|
$p = Product::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'company_id' => $this->company->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->user->company_users()->where('company_id', $this->company->id)->update(['notifications' => (array)$notifications]);
|
||||||
|
|
||||||
|
$notification_users = $this->filterUsersByPermissions($this->company->company_users, $p, ['all_user_notifications']);
|
||||||
|
$this->assertCount(1, $notification_users->toArray());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testAllNotificationsDoesNotFiresForUser()
|
||||||
|
{
|
||||||
|
$u = User::factory()->create([
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'email' => $this->faker->safeEmail(),
|
||||||
|
'confirmation_code' => uniqid("st",true),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$company_token = new CompanyToken;
|
||||||
|
$company_token->user_id = $u->id;
|
||||||
|
$company_token->company_id = $this->company->id;
|
||||||
|
$company_token->account_id = $this->account->id;
|
||||||
|
$company_token->name = 'test token';
|
||||||
|
$company_token->token = Str::random(64);
|
||||||
|
$company_token->is_system = true;
|
||||||
|
$company_token->save();
|
||||||
|
|
||||||
|
$u->companies()->attach($this->company->id, [
|
||||||
|
'account_id' => $this->account->id,
|
||||||
|
'is_owner' => 1,
|
||||||
|
'is_admin' => 1,
|
||||||
|
'is_locked' => 0,
|
||||||
|
'notifications' => CompanySettings::notificationDefaults(),
|
||||||
|
'settings' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$p = Product::factory()->create([
|
||||||
|
'user_id' => $u->id,
|
||||||
|
'company_id' => $this->company->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$notifications = new \stdClass;
|
||||||
|
$notifications->email = ["all_user_notifications"];
|
||||||
|
$this->user->company_users()->where('company_id', $this->company->id)->update(['notifications' => (array)$notifications]);
|
||||||
|
|
||||||
|
$methods = $this->findUserEntityNotificationType($p, $this->cu, ['all_user_notifications']);
|
||||||
|
$this->assertCount(0, $methods);
|
||||||
|
|
||||||
|
$methods = $this->findUserEntityNotificationType($p, $this->cu, ['all_notifications']);
|
||||||
|
$this->assertCount(0, $methods);
|
||||||
|
|
||||||
|
$notifications = [];
|
||||||
|
$notifications['email'] = ["all_notifications"];
|
||||||
|
|
||||||
|
$cu = CompanyUser::where('company_id', $this->company->id)->where('user_id', $this->user->id)->first();
|
||||||
|
$cu->notifications = $notifications;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$methods = $this->findUserEntityNotificationType($p, $cu, ["all_notifications"]);
|
||||||
|
|
||||||
|
$this->assertCount(1, $methods);
|
||||||
|
|
||||||
|
$notifications = [];
|
||||||
|
$notifications['email'] = ["inventory_user"];
|
||||||
|
|
||||||
|
$cu = CompanyUser::where('company_id', $this->company->id)->where('user_id', $this->user->id)->first();
|
||||||
|
$cu->notifications = $notifications;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$methods = $this->findUserEntityNotificationType($p, $cu, ["all_notifications"]);
|
||||||
|
$this->assertCount(0, $methods);
|
||||||
|
|
||||||
|
$p = Product::factory()->create([
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'company_id' => $this->company->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$methods = $this->findUserEntityNotificationType($p, $cu, []);
|
||||||
|
|
||||||
|
nlog($methods);
|
||||||
|
|
||||||
|
$this->assertCount(1, $methods);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user