path('boboko') ->brandName('Boboko') ->brandLogo(asset('static/logos/core/boboko-logo.svg')) ->darkModeBrandLogo(asset('static/logos/core/boboko-logo-white.svg')) ->login(Login::class) ->resources([ LanguageLineResource::class, DataErasureRequestResource::class, DataExportRequestResource::class, CartResource::class, PaymentMethodResource::class, ShipmentResource::class, ManifestResource::class, ]) ->plugin(ShippingPlugin::make()); LunarPanel::extensions([ StaffResource::class => StaffResourceExtension::class, ProductResource::class => ProductResourceExtension::class, ProductOptionResource::class => ProductOptionResourceExtension::class, ValuesRelationManager::class => ValuesRelationManagerExtension::class, ShippingMethodResource::class => ShippingMethodResourceExtension::class, ListShippingMethod::class => ShippingMethodListExtension::class, ManageOrder::class => [OrderViewExtension::class, OrderActionsExtension::class, OrderTransactionsExtension::class, OrderPaymentMethodSummaryExtension::class, OrderShipmentsExtension::class], OrderItemsTable::class => OrderItemsTableExtension::class, // headerActions() is resolved per PAGE class, not per resource class — // unlike extendForm()/extendTable(), which really are resource-keyed // (called statically from the Resource class itself). Registering this // under CustomerResource::class would silently never fire; it has to be // keyed by each concrete page it should appear on. Layered with // whatever extension the consuming app registers for the same page — // LunarPanel::extensions() merges per key, and this one only touches // headerActions(), so it never conflicts with an app's own extension // (see docs/modules.md "Layering Module and App Configuration"). EditCustomer::class => CustomerErasureActionsExtension::class, ViewCustomer::class => CustomerErasureActionsExtension::class, // getRelations(), unlike headerActions(), genuinely is resolved // statically from the Resource class itself — CustomerResource::class // is the correct key here. CustomerResource::class => CustomerErasureRelationsExtension::class, ]); // resolveRelationUsing(), not macro() — Illuminate\Database\Eloquent\ // Model does not use the Macroable trait in this Laravel version, so // Product::macro(...)/Customer::macro(...)/$userModel::macro(...) // silently fall through to Model::__callStatic(), which instantiates // the model and tries to call the method as a real one, hitting // newQuery()->getConnection() — this crashes every console command // and every request, since CorePlugin::register() runs during // provider registration, before the DB connection is configured // ("Call to a member function connection() on null"). This bit us // once already; resolveRelationUsing() is Eloquent's real, intended, // connection-free extension point for exactly this (Order:: // resolveRelationUsing('shipments', ...) in ShippingServiceProvider // already uses it correctly). Product::resolveRelationUsing('reviews', function (Product $product): HasMany { return $product->hasMany(ProductReview::class); }); // Customer::erasureRequests()/exportRequests() and the User-model // equivalents below let a relation manager scope // DataErasureRequest/DataExportRequest to one specific subject — both // tables use a plain subject_type/subject_id pair rather than Laravel's // usual morphs() convention, since one column pair identifies either a // Customer or a User (see docs/privacy.md "User-scope vs Customer-scope"), // so this is a MorphMany built by hand rather than a bare Eloquent // convention lookup. Customer::resolveRelationUsing('erasureRequests', function (Customer $customer): MorphMany { return $customer->morphMany(DataErasureRequest::class, 'subject', 'subject_type', 'subject_id'); }); Customer::resolveRelationUsing('exportRequests', function (Customer $customer): MorphMany { return $customer->morphMany(DataExportRequest::class, 'subject', 'subject_type', 'subject_id'); }); $userModel = config('auth.providers.users.model'); $userModel::resolveRelationUsing('erasureRequests', function ($user): MorphMany { return $user->morphMany(DataErasureRequest::class, 'subject', 'subject_type', 'subject_id'); }); $userModel::resolveRelationUsing('exportRequests', function ($user): MorphMany { return $user->morphMany(DataExportRequest::class, 'subject', 'subject_type', 'subject_id'); }); LunarStaff::addActivitylogExcept([ 'otp_code', 'otp_expires_at', 'password', 'remember_token', 'email_verified_at', 'app_authentication_secret', 'app_authentication_recovery_codes', ]); LunarStaff::created(function (LunarStaff $staff) { Mail::to($staff->email)->send(new InviteMail($staff->first_name)); }); } public function boot(Panel $panel): void {} }