components->info('Seeding default Lunar store data...'); if (! Country::count()) { $this->components->info('Importing countries'); $this->call('lunar:import:address-data'); } DB::transaction(function () { if (! Channel::whereDefault(true)->exists()) { $this->components->info('Setting up default channel'); Channel::create([ 'name' => 'Webstore', 'handle' => 'webstore', 'default' => true, 'url' => 'http://localhost', ]); } if (! Language::count()) { $this->components->info('Adding default language'); Language::create([ 'code' => 'en', 'name' => 'English', 'default' => true, ]); } if (! Language::where('code', 'el')->exists()) { $this->components->info('Adding Greek language'); Language::create([ 'code' => 'el', 'name' => 'Greek', 'default' => false, ]); } if (! Currency::whereDefault(true)->exists()) { $this->components->info('Adding a default currency (USD)'); Currency::create([ 'code' => 'USD', 'name' => 'US Dollar', 'exchange_rate' => 1, 'decimal_places' => 2, 'default' => true, 'enabled' => true, ]); } if (! CustomerGroup::whereDefault(true)->exists()) { $this->components->info('Adding a default customer group.'); CustomerGroup::create([ 'name' => 'Retail', 'handle' => 'retail', 'default' => true, ]); } if (! CollectionGroup::count()) { $this->components->info('Adding an initial collection group'); CollectionGroup::create([ 'name' => 'Main', 'handle' => 'main', ]); } if (! TaxClass::count()) { $this->components->info('Adding a default tax class.'); TaxClass::create([ 'name' => 'Default Tax Class', 'default' => true, ]); } if (! TaxZone::count()) { $this->components->info('Adding a default tax zone.'); $taxZone = TaxZone::create([ 'name' => 'Default Tax Zone', 'zone_type' => 'country', 'price_display' => 'tax_exclusive', 'default' => true, 'active' => true, ]); $taxZone->countries()->createMany( Country::get()->map(fn ($country) => [ 'country_id' => $country->id, ]) ); } if (! Attribute::count()) { $this->components->info('Setting up initial attributes'); $group = AttributeGroup::create([ 'attributable_type' => Product::morphName(), 'name' => collect([ 'en' => 'Details', ]), 'handle' => 'details', 'position' => 1, ]); $collectionGroup = AttributeGroup::create([ 'attributable_type' => Collection::morphName(), 'name' => collect([ 'en' => 'Details', ]), 'handle' => 'collection_details', 'position' => 1, ]); Attribute::create([ 'attribute_type' => 'product', 'attribute_group_id' => $group->id, 'position' => 1, 'name' => [ 'en' => 'Name', ], 'handle' => 'name', 'section' => 'main', 'type' => TranslatedText::class, 'required' => true, 'default_value' => null, 'configuration' => [ 'richtext' => false, ], 'system' => true, 'description' => [ 'en' => '', ], ]); Attribute::create([ 'attribute_type' => 'collection', 'attribute_group_id' => $collectionGroup->id, 'position' => 1, 'name' => [ 'en' => 'Name', ], 'handle' => 'name', 'section' => 'main', 'type' => TranslatedText::class, 'required' => true, 'default_value' => null, 'configuration' => [ 'richtext' => false, ], 'system' => true, 'description' => [ 'en' => '', ], ]); Attribute::create([ 'attribute_type' => 'product', 'attribute_group_id' => $group->id, 'position' => 2, 'name' => [ 'en' => 'Description', ], 'handle' => 'description', 'section' => 'main', 'type' => TranslatedText::class, 'required' => false, 'default_value' => null, 'configuration' => [ 'richtext' => true, ], 'system' => false, 'description' => [ 'en' => '', ], ]); Attribute::create([ 'attribute_type' => 'collection', 'attribute_group_id' => $collectionGroup->id, 'position' => 2, 'name' => [ 'en' => 'Description', ], 'handle' => 'description', 'section' => 'main', 'type' => TranslatedText::class, 'required' => false, 'default_value' => null, 'configuration' => [ 'richtext' => true, ], 'system' => false, 'description' => [ 'en' => '', ], ]); } if (! ProductType::count()) { $this->components->info('Adding a product type.'); $type = ProductType::create([ 'name' => 'Stock', ]); $type->mappedAttributes()->attach( Attribute::whereAttributeType( Product::morphName() )->get()->pluck('id') ); } }); $this->components->info('Seeding storefront label translations'); $this->seedStorefrontLabels($translations); $this->components->info('Seeding payment method settings'); $this->seedPaymentMethods(); $this->components->info('Publishing Filament assets'); $this->call('filament:assets'); $this->components->info('Lunar default data seeded.'); } /** * Per-key upsert, not an all-or-nothing "only seed if the group is empty" guard — * a key already present in the database (including one an admin has since edited * via the Filament Languages resource) is left untouched; only keys missing * entirely are created. This is what makes it safe to add new keys to * StorefrontLabels later and re-run this on an already-installed store without * either skipping the new keys (the old all-or-nothing guard) or reverting an * admin's edits back to the hardcoded default (a naive updateOrCreate would). */ private function seedStorefrontLabels(TranslationService $translations): void { $labels = StorefrontLabels::all(); $existingKeys = LanguageLine::where('group', 'storefront') ->whereIn('key', array_keys($labels)) ->pluck('key'); foreach ($labels as $key => $text) { if ($existingKeys->contains($key)) { continue; } $translations->create('storefront', $key, $text); } } /** * A single, deliberately opinionated starter row on fresh install — * `PaymentMethod` is now fully admin-creatable/deletable (see * docs/payments.md), so this is no longer "seed every config-defined * type," it's "give a fresh store one reasonable payment method to * start from instead of zero." Every value here is a plain literal in * THIS command, not sourced from config or PaymentDriverRegistry — a * driver has no business carrying opinions about what its captured * order status should be called; that's a merchant decision. * * Skip-if-exists on `type`, same idempotent convention as * seedStorefrontLabels() — an admin who has since edited or deleted * this row (via the Filament Payment Methods resource) is left alone; * re-running lunar:install never recreates a deleted starter row. * * Seeded disabled — shouldn't go live for shoppers before staff have * actually reviewed it and turned it on via the Payment Methods * resource. See CheckoutService::getPaymentMethods(). */ private function seedPaymentMethods(): void { if (PaymentMethod::where('type', 'cash-on-delivery')->exists()) { return; } PaymentMethod::create([ 'type' => 'cash-on-delivery', 'name' => [ 'en' => 'Cash on Delivery', 'el' => 'Αντικαταβολή', ], 'driver' => 'cash-on-delivery', 'capture_mode' => 'pay', 'position' => 0, 'enabled' => false, 'data' => [], ]); } }