Merge branch 'master' into Payment-Methods

This commit is contained in:
2026-08-29 14:17:48 +03:00
131 changed files with 10881 additions and 42 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();
});
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Support\ServiceProvider;
use Lunar\Models\ProductOption;
use Lunar\Models\ProductOptionValue;
use Modules\Core\Catalog\Observers\ProductOptionReindexObserver;
use Modules\Core\Catalog\OptionTypes\ColorOptionType;
use Modules\Core\Catalog\Services\ProductOptionTypeManager;
class CatalogServiceProvider extends ServiceProvider
{
public function boot(): void
{
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));
}
}
@@ -0,0 +1,50 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Lunar\Models\Language;
use Modules\Core\Localization\Events\LanguageCreated;
use Modules\Core\Localization\Events\LanguageDeleted;
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\Listeners\FlushLanguageCache;
use Modules\Core\Localization\Listeners\FlushTranslationCache;
use Modules\Core\Localization\Listeners\LogTranslationActivity;
use Modules\Core\Localization\Listeners\MigrateTranslationsForRenamedLanguage;
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);
Language::observe(LanguageCacheObserver::class);
foreach ([TranslationCreated::class, TranslationUpdated::class, TranslationDeleted::class] as $event) {
Event::listen($event, FlushTranslationCache::class);
Event::listen($event, LogTranslationActivity::class);
}
foreach ([LanguageCreated::class, LanguageUpdated::class, LanguageDeleted::class] as $event) {
Event::listen($event, FlushLanguageCache::class);
}
Event::listen(LanguageUpdated::class, MigrateTranslationsForRenamedLanguage::class);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Modules\Core\Providers;
use Illuminate\Support\ServiceProvider;
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\Catalog\Services\ProductIndexer's review data would only refresh on
* the next full product reindex.
*/
class ReviewServiceProvider extends ServiceProvider
{
public function boot(): void
{
ProductReview::created(fn (ProductReview $review) => $review->product?->searchable());
ProductReview::updated(fn (ProductReview $review) => $review->product?->searchable());
ProductReview::deleted(fn (ProductReview $review) => $review->product?->searchable());
}
}
+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 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\FlushLivePricingCache;
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');
Order::resolveRelationUsing('shipments', function ($order) {
return $order->hasMany(Shipment::class);
});
foreach ([CartLineAdded::class, CartLineUpdated::class, CartLineRemoved::class, CartCleared::class, ShippingAddressSet::class] as $event) {
Event::listen($event, [FlushLivePricingCache::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);
}
}