Merge branch 'master' into Privacy

This commit is contained in:
2026-09-16 00:13:41 +03:00
322 changed files with 21329 additions and 658 deletions
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\ServiceProvider;
use Modules\Core\Cart\Commands\DetectAbandonedCarts;
class CartServiceProvider extends ServiceProvider
{
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->commands([DetectAbandonedCarts::class]);
}
$this->app->booted(function () {
$this->app->make(Schedule::class)
->command(DetectAbandonedCarts::class)
->hourly();
});
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Lunar\Models\Product;
use Lunar\Models\ProductOption;
use Lunar\Models\ProductOptionValue;
use Modules\Core\Catalog\Events\ProductDeleted;
use Modules\Core\Catalog\Events\ProductSaved;
use Modules\Core\Catalog\Listeners\ReindexProductsRecommendingProduct;
use Modules\Core\Catalog\Observers\ProductOptionReindexObserver;
use Modules\Core\Catalog\OptionTypes\ColorOptionType;
use Modules\Core\Catalog\Services\ProductOptionTypeManager;
class CatalogServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../../config/catalog.php', 'catalog');
}
public function boot(): void
{
$this->publishes([
__DIR__ . '/../../config/catalog.php' => config_path('catalog.php'),
], 'core-config');
ProductOptionTypeManager::get()->register([
ColorOptionType::class,
]);
$observer = new ProductOptionReindexObserver;
ProductOption::saved(fn (ProductOption $option) => $observer->optionSaved($option));
ProductOption::deleted(fn (ProductOption $option) => $observer->optionDeleted($option));
ProductOptionValue::saved(fn (ProductOptionValue $value) => $observer->valueSaved($value));
ProductOptionValue::deleted(fn (ProductOptionValue $value) => $observer->valueDeleted($value));
Product::saved(fn (Product $product) => Event::dispatch(new ProductSaved($product)));
Product::deleted(fn (Product $product) => Event::dispatch(new ProductDeleted($product->id)));
Event::listen(ProductSaved::class, [ReindexProductsRecommendingProduct::class, 'handleSaved']);
Event::listen(ProductDeleted::class, [ReindexProductsRecommendingProduct::class, 'handleDeleted']);
$this->app->booted(function () {
// A full nightly reindex, on top of the per-event reindexing
// above — catches everything event-driven reindexing
// deliberately doesn't cover: a newly-created product not yet
// appearing as a recommendation elsewhere, in_stock/price
// drifting from an order decrementing stock outside a product
// save, and any other staleness ProductIndexer's own docblock
// already documents as accepted between reindexes. --refresh
// re-syncs filterable/sortable field settings too, not just
// documents, so a deploy that changed ProductIndexer's field
// list self-heals here even if `lunar:meilisearch:setup`
// wasn't run manually after that deploy.
$this->app->make(Schedule::class)
->command('lunar:search:index', ['Lunar\\Models\\Product', '--refresh'])
->dailyAt('03:00');
});
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Support\ServiceProvider;
class CheckoutServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../../config/legal.php', 'legal');
}
}
+4 -1
View File
@@ -5,12 +5,14 @@ namespace Modules\Core\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Modules\Core\Command\AnonymizeCommand;
use Modules\Core\Command\BackfillMissingSkusCommand;
use Modules\Core\Command\ExportCleanupCommand;
use Modules\Core\Command\ExportCommand;
use Modules\Core\Command\ImportCommand;
use Modules\Core\Command\InstallLunarCommand;
use Modules\Core\Command\MigrateImportCommand;
use Modules\Core\Command\ProcessErasureRequestsCommand;
use Modules\Core\Command\TuneProductSearchCommand;
class CoreServiceProvider extends ServiceProvider
{
@@ -24,6 +26,7 @@ class CoreServiceProvider extends ServiceProvider
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'core');
Blade::anonymousComponentPath(__DIR__ . '/../../resources/views', 'core');
$this->loadMigrationsFrom(__DIR__ . '/../../database/migrations');
$this->loadTranslationsFrom(__DIR__ . '/../../lang', 'core');
$this->publishes([
__DIR__ . '/../../config/core.php' => config_path('core.php'),
@@ -36,7 +39,7 @@ class CoreServiceProvider extends ServiceProvider
], 'core-assets');
if ($this->app->runningInConsole()) {
$this->commands([AnonymizeCommand::class, ExportCommand::class, ExportCleanupCommand::class, ImportCommand::class, MigrateImportCommand::class, ProcessErasureRequestsCommand::class]);
$this->commands([AnonymizeCommand::class, ExportCommand::class, ExportCleanupCommand::class, ImportCommand::class, MigrateImportCommand::class, TuneProductSearchCommand::class, BackfillMissingSkusCommand::class, ProcessErasureRequestsCommand::class]);
//Overriding lunar:install
$this->app->booted(fn() => $this->commands([InstallLunarCommand::class]));
+10
View File
@@ -7,7 +7,12 @@ use Illuminate\Support\ServiceProvider;
use Lunar\Facades\ModelManifest;
use Lunar\Models\Contracts\Customer as LunarCustomer;
use Modules\Core\Auth\Events\UserCreated;
use Modules\Core\Customer\Events\CustomerAddressCreated;
use Modules\Core\Customer\Events\CustomerAddressDeleted;
use Modules\Core\Customer\Events\CustomerAddressUpdated;
use Modules\Core\Customer\Events\CustomerProfileUpdated;
use Modules\Core\Customer\Listeners\CreateCustomerForUser;
use Modules\Core\Customer\Listeners\LogCustomerAccountActivity;
use Modules\Core\Customer\Models\Customer;
class CustomerServiceProvider extends ServiceProvider
@@ -17,5 +22,10 @@ class CustomerServiceProvider extends ServiceProvider
ModelManifest::replace(LunarCustomer::class, Customer::class);
Event::listen(UserCreated::class, CreateCustomerForUser::class);
Event::listen(CustomerAddressCreated::class, [LogCustomerAccountActivity::class, 'handleAddressCreated']);
Event::listen(CustomerAddressUpdated::class, [LogCustomerAccountActivity::class, 'handleAddressUpdated']);
Event::listen(CustomerAddressDeleted::class, [LogCustomerAccountActivity::class, 'handleAddressDeleted']);
Event::listen(CustomerProfileUpdated::class, [LogCustomerAccountActivity::class, 'handleProfileUpdated']);
}
}
+13 -2
View File
@@ -11,15 +11,26 @@ use Modules\Core\Localization\Events\LanguageUpdated;
use Modules\Core\Localization\Events\TranslationCreated;
use Modules\Core\Localization\Events\TranslationDeleted;
use Modules\Core\Localization\Events\TranslationUpdated;
use Modules\Core\Localization\LanguageCacheObserver;
use Modules\Core\Localization\Listeners\FlushLanguageCache;
use Modules\Core\Localization\Listeners\FlushTranslationCache;
use Modules\Core\Localization\Listeners\LogTranslationActivity;
use Modules\Core\Localization\Listeners\MigrateTranslationsForRenamedLanguage;
use Modules\Core\Localization\LocaleMiddleware;
use Modules\Core\Localization\Middleware\LocaleMiddleware;
use Modules\Core\Localization\Models\LanguageLine;
use Modules\Core\Localization\Observers\LanguageCacheObserver;
class LocalizationServiceProvider extends ServiceProvider
{
public function register(): void
{
// Must run before Spatie\TranslationLoader\TranslationServiceProvider's
// register() merges its own config defaults - mergeConfigFrom() only fills
// in keys not already set, so setting this here (regardless of provider
// boot order) makes it win over the package's default
// Spatie\TranslationLoader\LanguageLine::class.
config(['translation-loader.model' => LanguageLine::class]);
}
public function boot(): void
{
$this->app['router']->aliasMiddleware('locale', LocaleMiddleware::class);
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Console\Scheduling\Schedule as ConsoleSchedule;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Lunar\Models\Order;
use Lunar\Models\Transaction;
use Modules\Core\Checkout\Events\OrderPlaced;
use Modules\Core\Notification\NotificationRegistry;
use Modules\Core\Order\Commands\CloseExpiredReturnWindows;
use Modules\Core\Order\Events\OrderDelivered;
use Modules\Core\Order\Events\OrderPaidChanged;
use Modules\Core\Order\Events\OrderPickedUp;
use Modules\Core\Order\Events\OrderStatusChanged;
use Modules\Core\Order\Listeners\AdvanceFulfillmentOnCarrierCheckpoint;
use Modules\Core\Order\Listeners\AdvanceFulfillmentOnDelivered;
use Modules\Core\Order\Listeners\ApplyResolvedPaymentStatus;
use Modules\Core\Order\Listeners\CompleteOrderOnPickedUp;
use Modules\Core\Order\Listeners\DecrementStockOnOrderPlaced;
use Modules\Core\Order\Listeners\DeriveOrderDeliveredFromShipment;
use Modules\Core\Order\Listeners\MarkDeliveryFailedOnCarrierCheckpoint;
use Modules\Core\Order\Listeners\RecordPaymentTransaction;
use Modules\Core\Order\Listeners\RecordStatusTransition;
use Modules\Core\Order\Models\OrderStatusTransition;
use Modules\Core\Order\Notifications\OrderCapturedNotification;
use Modules\Core\Order\Notifications\OrderCompletedNotification;
use Modules\Core\Order\Notifications\OrderDeliveredNotification;
use Modules\Core\Order\Notifications\OrderDispatchedNotification;
use Modules\Core\Order\Notifications\OrderPickupReadyNotification;
use Modules\Core\Order\Notifications\OrderPlacedNotification;
use Modules\Core\Order\Notifications\OrderRefundedNotification;
use Modules\Core\Order\Notifications\OrderStatusUpdatedNotification;
use Modules\Core\Order\Observers\OrderObserver;
use Modules\Core\Order\Observers\TransactionObserver;
use Modules\Core\Order\Support\OrderStatus;
use Modules\Core\Payment\Events\PaymentAuthorized;
use Modules\Core\Payment\Events\PaymentCaptured;
use Modules\Core\Payment\Events\PaymentRefunded;
use Modules\Core\Payment\Events\PaymentVoided;
use Modules\Core\Shipping\Events\ShipmentStatusUpdatedByCarrier;
class OrderServiceProvider extends ServiceProvider
{
public function boot(): void
{
Order::observe(OrderObserver::class);
Transaction::observe(TransactionObserver::class);
Order::macro('paymentStatus', fn () => OrderStatus::payment($this));
Order::macro('fulfillmentStatus', fn () => OrderStatus::fulfillment($this));
Order::resolveRelationUsing('statusTransitions', function ($order) {
return $order->hasMany(OrderStatusTransition::class);
});
Event::listen(ShipmentStatusUpdatedByCarrier::class, DeriveOrderDeliveredFromShipment::class);
Event::listen(ShipmentStatusUpdatedByCarrier::class, AdvanceFulfillmentOnCarrierCheckpoint::class);
Event::listen(ShipmentStatusUpdatedByCarrier::class, MarkDeliveryFailedOnCarrierCheckpoint::class);
Event::listen(OrderDelivered::class, AdvanceFulfillmentOnDelivered::class);
Event::listen(OrderPickedUp::class, CompleteOrderOnPickedUp::class);
Event::listen(PaymentCaptured::class, ApplyResolvedPaymentStatus::class);
Event::listen(PaymentAuthorized::class, ApplyResolvedPaymentStatus::class);
Event::listen(PaymentCaptured::class, RecordPaymentTransaction::class);
Event::listen(PaymentAuthorized::class, RecordPaymentTransaction::class);
Event::listen(PaymentVoided::class, RecordPaymentTransaction::class);
// RecordPaymentTransaction must run before ApplyResolvedPaymentStatus
// for PaymentRefunded specifically — the latter now reads
// $order->transactions (via OrderStatus::payment()) to resolve
// Refunded vs PartiallyRefunded, which requires the refund
// Transaction row to already exist.
Event::listen(PaymentRefunded::class, RecordPaymentTransaction::class);
Event::listen(PaymentRefunded::class, ApplyResolvedPaymentStatus::class);
Event::listen(OrderPlaced::class, DecrementStockOnOrderPlaced::class);
Event::listen(OrderStatusChanged::class, [RecordStatusTransition::class, 'handleStatusChanged']);
Event::listen(OrderPaidChanged::class, [RecordStatusTransition::class, 'handlePaidChanged']);
NotificationRegistry::get()->register([
OrderDeliveredNotification::class,
OrderStatusUpdatedNotification::class,
OrderRefundedNotification::class,
OrderCapturedNotification::class,
OrderPlacedNotification::class,
OrderPickupReadyNotification::class,
OrderDispatchedNotification::class,
OrderCompletedNotification::class,
]);
// Lets the consuming app override copy/markup without forking core
// — published into resources/views/vendor/core/order/notifications,
// which loadViewsFrom() (CoreServiceProvider) already resolves
// ahead of the package's own views for the `core::` namespace.
$this->publishes([
__DIR__ . '/../../resources/views/order/notifications' => resource_path('views/vendor/core/order/notifications'),
], 'core-views');
if ($this->app->runningInConsole()) {
$this->commands([CloseExpiredReturnWindows::class]);
}
// Exact midnight per an explicit compliance requirement — not a
// loose dailyAt() offset or plain daily().
$this->app->booted(function () {
$this->app->make(ConsoleSchedule::class)
->command(CloseExpiredReturnWindows::class)
->dailyAt('00:00');
});
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Lunar\Facades\ModelManifest;
use Lunar\Models\Contracts\Transaction as TransactionContract;
use Lunar\Pipelines\Cart\ApplyShipping;
use Modules\Core\Command\SyncPaymentDriversCommand;
use Modules\Core\Payment\Drivers\BankTransferPaymentDriver;
use Modules\Core\Payment\Drivers\CashOnDeliveryPaymentDriver;
use Modules\Core\Payment\Drivers\OfflinePaymentDriver;
use Modules\Core\Payment\Drivers\StripePaymentDriver;
use Modules\Core\Payment\Events\PaymentMethodCreated;
use Modules\Core\Payment\Events\PaymentMethodDeleted;
use Modules\Core\Payment\Events\PaymentMethodUpdated;
use Modules\Core\Payment\Listeners\LogPaymentMethodActivity;
use Modules\Core\Payment\Models\CoreTransaction;
use Modules\Core\Payment\Services\PaymentDriverRegistry;
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../../config/payment.php', 'payment');
$this->app->singleton(PaymentDriverRegistry::class);
}
public function boot(): void
{
$registry = $this->app->make(PaymentDriverRegistry::class);
$registry->register('offline', OfflinePaymentDriver::class, 'Offline / Manual');
$registry->register('stripe', StripePaymentDriver::class, 'Stripe');
$registry->register('bank-transfer', BankTransferPaymentDriver::class, 'Bank Transfer');
$registry->register('cash-on-delivery', CashOnDeliveryPaymentDriver::class, 'Cash on Delivery');
$cartPipeline = config('lunar.cart.pipelines.cart', []);
$insertAfter = array_search(ApplyShipping::class, $cartPipeline, true);
foreach (config('payment.cart_pipeline', []) as $pipe) {
if (in_array($pipe, $cartPipeline, true)) {
continue;
}
if ($insertAfter === false) {
$cartPipeline[] = $pipe;
} else {
array_splice($cartPipeline, $insertAfter + 1, 0, [$pipe]);
$insertAfter++;
}
}
config(['lunar.cart.pipelines.cart' => $cartPipeline]);
// Same contract-swap mechanism this codebase already uses for
// Customer/Staff (see e.g. consuming apps' own AppServiceProvider,
// ModelManifest::replace(Contracts\Customer::class, ...)) — every
// place Lunar's own code resolves a transaction via
// Transaction::modelClass() (Order::transactions()'s own relation
// included) gets Modules\Core\Payment\Models\CoreTransaction
// instead of the vendor's own Transaction. That subclass's
// driver() override is the ENTIRE fix for the admin panel's
// refund/capture actions silently landing on our real payment
// system — see CoreTransaction's own docblock. Lunar's own
// Payments facade/PaymentManager is never touched at all.
ModelManifest::replace(TransactionContract::class, CoreTransaction::class);
Event::listen(PaymentMethodCreated::class, [LogPaymentMethodActivity::class, 'handleCreated']);
Event::listen(PaymentMethodUpdated::class, [LogPaymentMethodActivity::class, 'handleUpdated']);
Event::listen(PaymentMethodDeleted::class, [LogPaymentMethodActivity::class, 'handleDeleted']);
$this->loadRoutesFrom(__DIR__ . '/../Payment/routes/webhooks.php');
if ($this->app->runningInConsole()) {
$this->commands([SyncPaymentDriversCommand::class]);
}
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ use Modules\Core\Review\Models\ProductReview;
* Keeps a product's Meilisearch document in sync with its reviews. A review is
* created/edited independently of its product (customer submission, staff reply),
* so the product's own save/update events never fire for it — without this listener,
* Modules\Core\Search\ProductIndexer's review data would only refresh on the next
* full product reindex.
* Modules\Core\Catalog\Services\ProductIndexer's review data would only refresh on
* the next full product reindex.
*/
class ReviewServiceProvider extends ServiceProvider
{
+157
View File
@@ -0,0 +1,157 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Console\Scheduling\Schedule as ConsoleSchedule;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Livewire\Livewire;
use Livewire\Mechanisms\ComponentRegistry;
use Lunar\Models\Order;
use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages\ManageShippingRates as VendorManageShippingRates;
use Lunar\Shipping\Models\ShippingMethod;
use Modules\Core\Cart\Events\CartCleared;
use Modules\Core\Cart\Events\CartLineAdded;
use Modules\Core\Cart\Events\CartLineRemoved;
use Modules\Core\Cart\Events\CartLineUpdated;
use Modules\Core\Checkout\Events\ShippingAddressSet;
use Modules\Core\Shipping\Carriers\Acs\AcsClient;
use Modules\Core\Shipping\Carriers\Acs\AcsFulfillmentService;
use Modules\Core\Shipping\Carriers\Acs\AcsRateDriver;
use Modules\Core\Shipping\Carriers\Acs\Jobs\WarmAcsAreaCacheJob;
use Modules\Core\Shipping\Carriers\BoxNow\BoxNowClient;
use Modules\Core\Shipping\Carriers\BoxNow\BoxNowFulfillmentService;
use Modules\Core\Shipping\Carriers\BoxNow\BoxNowRateDriver;
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
use Modules\Core\Shipping\Filament\Pages\ManageShippingRates;
use Modules\Core\Shipping\Jobs\PollShipmentTrackingJob;
use Modules\Core\Shipping\Listeners\InvalidateShippingOptions;
use Modules\Core\Shipping\Support\FulfillmentType;
use Modules\Core\Shipping\Models\Shipment;
class ShippingServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__ . '/../../config/shippingCarriers/acs.php', 'acs');
$this->mergeConfigFrom(__DIR__ . '/../../config/shippingCarriers/boxnow.php', 'boxnow');
$this->app->singleton(AcsClient::class, fn () => new AcsClient(config('acs')));
$this->app->singleton(BoxNowClient::class, fn () => new BoxNowClient(config('boxnow')));
$this->app->bind(CarrierFulfillmentInterface::class, function ($app, array $params) {
return match ($params['carrier'] ?? null) {
'acs' => $app->make(AcsFulfillmentService::class),
'box-now' => $app->make(BoxNowFulfillmentService::class),
default => null,
};
});
// The vendor Rates page has no extension hook, so we swap it for
// our subclass everywhere. Route::get($path, VendorClass::class)
// instantiates the vendor class directly via the container for the
// initial full-page load (bypassing Livewire's component registry
// entirely), so this container bind is required in addition to the
// Livewire::component() re-registration below — the bind covers
// first load, the Livewire registration covers every AJAX
// round-trip (form submits, table interactions) afterwards.
$this->app->bind(VendorManageShippingRates::class, ManageShippingRates::class);
}
public function boot(): void
{
$this->publishes([
__DIR__ . '/../../config/shippingCarriers/acs.php' => config_path('shippingCarriers/acs.php'),
__DIR__ . '/../../config/shippingCarriers/boxnow.php' => config_path('shippingCarriers/boxnow.php'),
], 'core-config');
// Signed-URL auth only, same model as Lunar's own vendor
// lunar.pdf.download route — see Modules\Core\Shipping\Http\
// Controllers\DownloadShipmentLabelController's own docblock.
$this->loadRoutesFrom(__DIR__ . '/../Shipping/routes/web.php');
Order::resolveRelationUsing('shipments', function ($order) {
return $order->hasMany(Shipment::class);
});
// Order has no direct ShippingMethod relation — shippingAddress.
// shipping_option is only ever a code string (see
// Modules\Core\Shipping\Extensions\OrderViewExtension::
// resolveCarrier() for the same lookup pattern already used to
// resolve a carrier driver from it).
//
// Resolves via Modules\Core\Shipping\Support\FulfillmentType (driver-
// declared for acs/box-now, merchant-configured data['fulfillment_type']
// fallback for table-rate-shipping's generic drivers) rather than
// through a ShippingMethod::macro('isStorePickup', ...) —
// Lunar\Base\Traits\HasModelExtending::__callStatic() (used by
// Lunar\Shipping\Models\ShippingMethod via Lunar\Base\BaseModel)
// intercepts EVERY unmatched static call, including macro()
// itself, and dispatches it as (new static)->macro(...) instead
// of forwarding to Macroable — so a macro registered this way
// silently never gets stored, and hasMacro() always returns
// false. (Lunar\Models\Order is unaffected because it declares
// its own macro() method directly, bypassing __callStatic
// entirely — that's why Order::macro('isStorePickupOrder', ...)
// below still works.)
Order::macro('isStorePickupOrder', function () {
/** @var Order $this */
$code = $this->shippingAddress?->shipping_option;
if (! $code) {
return false;
}
// ->value('data') is deliberately avoided here — on Postgres,
// ->value()/->pluck() on a JSON column silently return the
// wrong result (works fine in ->where(), not in a column
// projection); loading the model and reading its cast
// attribute avoids that entirely.
$method = ShippingMethod::where('code', $code)->first();
return $method && FulfillmentType::isStorePickup($method);
});
foreach ([CartLineAdded::class, CartLineUpdated::class, CartLineRemoved::class, CartCleared::class, ShippingAddressSet::class] as $event) {
Event::listen($event, [InvalidateShippingOptions::class, 'handle']);
}
// Deferred: the Shipping facade resolves a binding registered in
// lunarphp/table-rate-shipping's own ShippingServiceProvider::boot(),
// and provider boot order between packages isn't guaranteed.
$this->app->booted(function () {
Shipping::extend('acs', fn ($app) => $app->make(AcsRateDriver::class));
Shipping::extend('box-now', fn ($app) => $app->make(BoxNowRateDriver::class));
$this->app->make(ConsoleSchedule::class)
->job(new WarmAcsAreaCacheJob)
->dailyAt('06:00')
->when(fn () => ShippingMethod::where('driver', 'acs')->exists());
$this->app->make(ConsoleSchedule::class)
->job(new PollShipmentTrackingJob)
->everyThirtyMinutes();
$this->overrideRatesPageLivewireComponent();
});
}
/**
* The vendor Rates page has no extension hook, so we swap it for our
* subclass (see Shipping/Filament/Pages/ManageShippingRates). Filament
* already registered the vendor class as a Livewire component under a
* name derived from its class string (see
* Panel::registerLivewireComponents()); Livewire's own registry is a
* simple last-write-wins name => class map, so re-registering the same
* derived name against our subclass here overrides it — keeping the
* route, sub-navigation, and every Livewire round-trip (including form
* submissions) pointed at one consistent component identity.
*/
private function overrideRatesPageLivewireComponent(): void
{
$name = $this->app->make(ComponentRegistry::class)->getName(VendorManageShippingRates::class);
Livewire::component($name, ManageShippingRates::class);
}
}