Updates for merge/purge activity events

This commit is contained in:
David Bomba 2025-08-28 15:31:00 +10:00
parent ce599d5787
commit 7dd0963553
12 changed files with 344 additions and 2 deletions

View File

@ -0,0 +1,37 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Client;
use App\Models\Client;
use App\Models\Company;
use Illuminate\Queue\SerializesModels;
/**
* Class ClientWasMerged.
*/
class ClientWasMerged
{
use SerializesModels;
/**
* Create a new event instance.
*
* @param string $mergeable_client
* @param Client $client
* @param Company $company
* @param array $event_vars
*/
public function __construct(public string $mergeable_client, public Client $client, public Company $company, public array $event_vars)
{
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Client;
use App\Models\User;
use App\Models\Company;
use Illuminate\Queue\SerializesModels;
/**
* Class ClientWasMerged.
*/
class ClientWasPurged
{
use SerializesModels;
/**
* Create a new event instance.
*
* @param string $purged_client
* @param User $user
* @param Company $company
* @param array $event_vars
*/
public function __construct(public string $purged_client, public User $user, public Company $company, public array $event_vars)
{
}
}

37
app/Events/Vendor/VendorWasMerged.php vendored Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Vendor;
use App\Models\Vendor;
use App\Models\Company;
use Illuminate\Queue\SerializesModels;
/**
* Class ClientWasMerged.
*/
class VendorWasMerged
{
use SerializesModels;
/**
* Create a new event instance.
*
* @param string $mergeable_vendor
* @param Vendor $vendor
* @param Company $company
* @param array $event_vars
*/
public function __construct(public string $mergeable_vendor, public Vendor $vendor, public Company $company, public array $event_vars)
{
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Activity;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class ClientMergedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$client = $event->client;
$fields = new stdClass();
$user_id = array_key_exists('user_id', $event->event_vars)
? $event->event_vars['user_id']
: $event->client->user_id;
$fields->client_id = $client->id;
$fields->user_id = $user_id;
$fields->company_id = $client->company_id;
$fields->activity_type_id = Activity::MERGE_CLIENT;
$fields->notes = $event->mergeable_client;
$this->activity_repo->save($fields, $client, $event->event_vars);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Activity;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class ClientPurgedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$client = $event->client;
$fields = new stdClass();
$user_id = array_key_exists('user_id', $event->event_vars)
? $event->event_vars['user_id']
: $event->client->user_id;
$fields->client_id = $client->id;
$fields->user_id = $user_id;
$fields->company_id = $client->company_id;
$fields->activity_type_id = Activity::PURGE_CLIENT;
$fields->notes = $event->purged_client;
$this->activity_repo->save($fields, $client, $event->event_vars);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Activity;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class VendorMergedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$vendor = $event->vendor;
$fields = new stdClass();
$user_id = array_key_exists('user_id', $event->event_vars)
? $event->event_vars['user_id']
: $event->client->user_id;
$fields->vendor_id = $vendor->id;
$fields->user_id = $user_id;
$fields->company_id = $vendor->company_id;
$fields->activity_type_id = Activity::MERGE_VENDOR;
$fields->notes = $event->mergeable_vendor;
$this->activity_repo->save($fields, $vendor, $event->event_vars);
}
}

View File

@ -282,6 +282,13 @@ class Activity extends StaticModel
public const ACCOUNT_DELETED = 150; public const ACCOUNT_DELETED = 150;
public const MERGE_CLIENT = 151;
public const MERGE_VENDOR = 152;
public const PURGE_CLIENT = 153;
protected $casts = [ protected $casts = [
'is_system' => 'boolean', 'is_system' => 'boolean',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',

View File

@ -61,17 +61,22 @@ use App\Events\Task\TaskWasRestored;
use App\Events\User\UserLoginFailed; use App\Events\User\UserLoginFailed;
use App\Events\User\UserWasArchived; use App\Events\User\UserWasArchived;
use App\Events\User\UserWasRestored; use App\Events\User\UserWasRestored;
use App\Listeners\LogRequestSending;
use App\Events\Quote\QuoteWasCreated; use App\Events\Quote\QuoteWasCreated;
use App\Events\Quote\QuoteWasDeleted; use App\Events\Quote\QuoteWasDeleted;
use App\Events\Quote\QuoteWasEmailed; use App\Events\Quote\QuoteWasEmailed;
use App\Events\Quote\QuoteWasUpdated; use App\Events\Quote\QuoteWasUpdated;
use App\Events\Account\AccountCreated; use App\Events\Account\AccountCreated;
use App\Events\Account\AccountDeleted; use App\Events\Account\AccountDeleted;
use App\Events\Client\ClientWasMerged;
use App\Events\Client\ClientWasPurged;
use App\Events\Credit\CreditWasViewed; use App\Events\Credit\CreditWasViewed;
use App\Events\Invoice\InvoiceWasPaid; use App\Events\Invoice\InvoiceWasPaid;
use App\Events\Quote\QuoteWasApproved; use App\Events\Quote\QuoteWasApproved;
use App\Events\Quote\QuoteWasArchived; use App\Events\Quote\QuoteWasArchived;
use App\Events\Quote\QuoteWasRestored; use App\Events\Quote\QuoteWasRestored;
use App\Events\Vendor\VendorWasMerged;
use App\Listeners\LogResponseReceived;
use Illuminate\Queue\Events\JobFailed; use Illuminate\Queue\Events\JobFailed;
use App\Events\Client\ClientWasCreated; use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasDeleted; use App\Events\Client\ClientWasDeleted;
@ -166,12 +171,15 @@ use App\Listeners\Activity\TaskUpdatedActivity;
use App\Listeners\Invoice\InvoiceEmailActivity; use App\Listeners\Invoice\InvoiceEmailActivity;
use App\Listeners\SendVerificationNotification; use App\Listeners\SendVerificationNotification;
use App\Events\Credit\CreditWasEmailedAndFailed; use App\Events\Credit\CreditWasEmailedAndFailed;
use App\Listeners\Activity\ClientMergedActivity;
use App\Listeners\Activity\ClientPurgedActivity;
use App\Listeners\Activity\CreatedQuoteActivity; use App\Listeners\Activity\CreatedQuoteActivity;
use App\Listeners\Activity\DeleteClientActivity; use App\Listeners\Activity\DeleteClientActivity;
use App\Listeners\Activity\DeleteCreditActivity; use App\Listeners\Activity\DeleteCreditActivity;
use App\Listeners\Activity\QuoteUpdatedActivity; use App\Listeners\Activity\QuoteUpdatedActivity;
use App\Listeners\Activity\TaskArchivedActivity; use App\Listeners\Activity\TaskArchivedActivity;
use App\Listeners\Activity\TaskRestoredActivity; use App\Listeners\Activity\TaskRestoredActivity;
use App\Listeners\Activity\VendorMergedActivity;
use App\Listeners\Credit\CreditRestoredActivity; use App\Listeners\Credit\CreditRestoredActivity;
use App\Listeners\Invoice\CreateInvoiceActivity; use App\Listeners\Invoice\CreateInvoiceActivity;
use App\Listeners\Invoice\InvoiceViewedActivity; use App\Listeners\Invoice\InvoiceViewedActivity;
@ -194,6 +202,7 @@ use App\Listeners\Payment\PaymentBalanceActivity;
use App\Listeners\Payment\PaymentEmailedActivity; use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Quote\QuoteCreatedNotification; use App\Listeners\Quote\QuoteCreatedNotification;
use App\Listeners\Quote\QuoteEmailedNotification; use App\Listeners\Quote\QuoteEmailedNotification;
use Illuminate\Http\Client\Events\RequestSending;
use App\Events\Invoice\InvoiceWasEmailedAndFailed; use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Events\Payment\PaymentWasEmailedAndFailed; use App\Events\Payment\PaymentWasEmailedAndFailed;
use App\Listeners\Activity\ArchivedClientActivity; use App\Listeners\Activity\ArchivedClientActivity;
@ -212,6 +221,8 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
use App\Listeners\Invoice\InvoiceReversedActivity; use App\Listeners\Invoice\InvoiceReversedActivity;
use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\Payment\PaymentRestoredActivity;
use App\Listeners\Quote\QuoteApprovedNotification; use App\Listeners\Quote\QuoteApprovedNotification;
use SocialiteProviders\Apple\AppleExtendSocialite;
use SocialiteProviders\Manager\SocialiteWasCalled;
use App\Events\Subscription\SubscriptionWasCreated; use App\Events\Subscription\SubscriptionWasCreated;
use App\Events\Subscription\SubscriptionWasDeleted; use App\Events\Subscription\SubscriptionWasDeleted;
use App\Events\Subscription\SubscriptionWasUpdated; use App\Events\Subscription\SubscriptionWasUpdated;
@ -223,6 +234,7 @@ use App\Listeners\Credit\CreditCreatedNotification;
use App\Listeners\Credit\CreditEmailedNotification; use App\Listeners\Credit\CreditEmailedNotification;
use App\Listeners\Invoice\InvoiceCancelledActivity; use App\Listeners\Invoice\InvoiceCancelledActivity;
use App\Listeners\Quote\QuoteReminderEmailActivity; use App\Listeners\Quote\QuoteReminderEmailActivity;
use Illuminate\Http\Client\Events\ResponseReceived;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed; use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Events\Subscription\SubscriptionWasArchived; use App\Events\Subscription\SubscriptionWasArchived;
use App\Events\Subscription\SubscriptionWasRestored; use App\Events\Subscription\SubscriptionWasRestored;
@ -238,6 +250,7 @@ use App\Listeners\Statement\StatementEmailedActivity;
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted; use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
use App\Events\PurchaseOrder\PurchaseOrderWasArchived; use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
use App\Events\PurchaseOrder\PurchaseOrderWasRestored; use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
use App\Listeners\Payment\PaymentEmailFailureActivity;
use App\Listeners\Vendor\UpdateVendorContactLastLogin; use App\Listeners\Vendor\UpdateVendorContactLastLogin;
use App\Events\RecurringQuote\RecurringQuoteWasCreated; use App\Events\RecurringQuote\RecurringQuoteWasCreated;
use App\Events\RecurringQuote\RecurringQuoteWasDeleted; use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
@ -254,6 +267,7 @@ use App\Listeners\Activity\SubscriptionRestoredActivity;
use App\Listeners\Invoice\InvoiceAutoBillFailedActivity; use App\Listeners\Invoice\InvoiceAutoBillFailedActivity;
use App\Listeners\Invoice\InvoiceAutoBillSuccessActivity; use App\Listeners\Invoice\InvoiceAutoBillSuccessActivity;
use App\Listeners\Invoice\InvoiceFailedEmailNotification; use App\Listeners\Invoice\InvoiceFailedEmailNotification;
use SocialiteProviders\Microsoft\MicrosoftExtendSocialite;
use App\Events\RecurringExpense\RecurringExpenseWasCreated; use App\Events\RecurringExpense\RecurringExpenseWasCreated;
use App\Events\RecurringExpense\RecurringExpenseWasDeleted; use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
use App\Events\RecurringExpense\RecurringExpenseWasUpdated; use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
@ -385,6 +399,12 @@ class EventServiceProvider extends ServiceProvider
ClientWasRestored::class => [ ClientWasRestored::class => [
RestoreClientActivity::class, RestoreClientActivity::class,
], ],
ClientWasMerged::class => [
ClientMergedActivity::class,
],
ClientWasPurged::class => [
ClientPurgedActivity::class,
],
// Documents // Documents
DocumentWasCreated::class => [ DocumentWasCreated::class => [
], ],
@ -671,6 +691,9 @@ class EventServiceProvider extends ServiceProvider
VendorContactLoggedIn::class => [ VendorContactLoggedIn::class => [
UpdateVendorContactLastLogin::class, UpdateVendorContactLastLogin::class,
], ],
VendorWasMerged::class => [
VendorMergedActivity::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
// ... Manager won't register drivers that are not added to this listener. // ... Manager won't register drivers that are not added to this listener.
\SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle', \SocialiteProviders\Apple\AppleExtendSocialite::class.'@handle',

View File

@ -147,6 +147,12 @@ class ClientRepository extends BaseRepository
public function purge($client) public function purge($client)
{ {
$purged_client = $client->present()->name();
$user = auth()->user() ?? $client->user;
$company = $client->company;
event(new \App\Events\Client\ClientWasPurged($purged_client, $user, $company, \App\Utils\Ninja::eventVars()));
nlog("Purging client id => {$client->id} => {$client->number}"); nlog("Purging client id => {$client->id} => {$client->number}");
$client->contacts()->forceDelete(); $client->contacts()->forceDelete();

View File

@ -36,6 +36,8 @@ class Merge extends AbstractService
nlog("balance pre {$this->client->balance}"); nlog("balance pre {$this->client->balance}");
nlog("paid_to_date pre {$this->client->paid_to_date}"); nlog("paid_to_date pre {$this->client->paid_to_date}");
$mergeable_client = $this->mergable_client->present()->name();
$this->client->balance += $this->mergable_client->balance; $this->client->balance += $this->mergable_client->balance;
$this->client->paid_to_date += $this->mergable_client->paid_to_date; $this->client->paid_to_date += $this->mergable_client->paid_to_date;
$this->client->save(); $this->client->save();
@ -71,11 +73,14 @@ class Merge extends AbstractService
} }
}); });
$this->mergable_client->forceDelete(); $this->mergable_client->forceDelete();
$this->client->credit_balance = $this->client->service()->getCreditBalance(); $this->client->credit_balance = $this->client->service()->getCreditBalance();
$this->client->saveQuietly(); $this->client->saveQuietly();
event(new \App\Events\Client\ClientWasMerged($mergeable_client, $this->client, $this->client->company, \App\Utils\Ninja::eventVars()));
return $this->client; return $this->client;
} }

View File

@ -33,6 +33,8 @@ class Merge extends AbstractService
public function run() public function run()
{ {
$_mergeable_vendor = $this->mergable_vendor->present()->name();
$this->mergable_vendor->activities()->update(['vendor_id' => $this->vendor->id]); $this->mergable_vendor->activities()->update(['vendor_id' => $this->vendor->id]);
$this->mergable_vendor->contacts()->update(['vendor_id' => $this->vendor->id]); $this->mergable_vendor->contacts()->update(['vendor_id' => $this->vendor->id]);
$this->mergable_vendor->credits()->update(['vendor_id' => $this->vendor->id]); $this->mergable_vendor->credits()->update(['vendor_id' => $this->vendor->id]);
@ -56,6 +58,8 @@ class Merge extends AbstractService
$this->mergable_vendor->forceDelete(); $this->mergable_vendor->forceDelete();
event(new \App\Events\Vendor\VendorWasMerged($_mergeable_vendor, $this->vendor, $this->vendor->company, \App\Utils\Ninja::eventVars()));
return $this->vendor; return $this->vendor;
} }

View File

@ -5617,8 +5617,11 @@ $lang = array(
'creator' => 'Created by', 'creator' => 'Created by',
'ses_topic_arn_help' => 'The SES topic (optional, only for webhook tracking)', 'ses_topic_arn_help' => 'The SES topic (optional, only for webhook tracking)',
'ses_region_help' => 'The AWS region, ie us-east-1', 'ses_region_help' => 'The AWS region, ie us-east-1',
'ses_secret_key' => 'SES Secret Key', 'ses_secret_key' => 'SES Secret Key',
'ses_access_key' => 'SES Access Key ID' 'ses_access_key' => 'SES Access Key ID',
'activity_151' => 'Client :notes merged into :client by :user',
'activity_152' => 'Vendor :notes merged into :vendor by :user',
'activity_153' => 'Client :notes purged by :user',
); );
return $lang; return $lang;