From a1b980164fddf651ae13e8757220d7c3a92f7dfd Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 14 Oct 2025 10:49:44 +0100 Subject: [PATCH 01/50] Allow Dashboard to be extended (#2312) This PR looks to add extending functionality to the Dashboard page so that developers can add to the existing widgets on the Dashboard, or, completely override them and build a custom dashboard to their needs. --- docs/.vitepress/config.js | 1 + docs/admin/extending/dashboard.md | 64 +++++++++++++++++++ .../admin/src/Filament/Pages/Dashboard.php | 31 ++++++++- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 docs/admin/extending/dashboard.md diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 484bbef2d4..92b1545854 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -170,6 +170,7 @@ export default defineConfig({ {text: 'Add-ons', link: '/admin/extending/addons'}, {text: 'Attributes', link: '/admin/extending/attributes'}, {text: 'Panel', link: '/admin/extending/panel'}, + {text: 'Dashboard', link: '/admin/extending/dashboard'}, {text: 'Pages', link: '/admin/extending/pages'}, {text: 'Resources', link: '/admin/extending/resources'}, {text: 'Relation Managers', link: '/admin/extending/relation-managers'}, diff --git a/docs/admin/extending/dashboard.md b/docs/admin/extending/dashboard.md new file mode 100644 index 0000000000..998635a5cc --- /dev/null +++ b/docs/admin/extending/dashboard.md @@ -0,0 +1,64 @@ +# Extending The Dashboard + +You may customise the Lunar Dashboard when registering it in your app service provider. + +```php + App\Filament\Extensions\DashboardExtension::class, + ]); +} +``` diff --git a/packages/admin/src/Filament/Pages/Dashboard.php b/packages/admin/src/Filament/Pages/Dashboard.php index 0c3b933f14..a675799201 100644 --- a/packages/admin/src/Filament/Pages/Dashboard.php +++ b/packages/admin/src/Filament/Pages/Dashboard.php @@ -10,23 +10,52 @@ use Lunar\Admin\Filament\Widgets\Dashboard\Orders\OrderStatsOverview; use Lunar\Admin\Filament\Widgets\Dashboard\Orders\OrderTotalsChart; use Lunar\Admin\Filament\Widgets\Dashboard\Orders\PopularProductsTable; +use Lunar\Admin\Support\Concerns\CallsHooks; use Lunar\Admin\Support\Pages\BaseDashboard; class Dashboard extends BaseDashboard { + use CallsHooks; + protected static ?int $navigationSort = 1; public function getWidgets(): array + { + return self::callLunarHook('getWidgets', $this->getDefaultWidgets()); + } + + public function getDefaultWidgets(): array { return [ + ...$this->getDefaultOverviewWidgets(), + ...$this->getDefaultChartsWidgets(), + ...$this->getDefaultTableWidgets(), + ]; + } + + public function getDefaultOverviewWidgets(): array + { + return self::callLunarHook('getOverviewWidgets', [ OrderStatsOverview::class, + ]); + } + + public function getDefaultChartsWidgets(): array + { + return self::callLunarHook('getChartWidgets', [ OrderTotalsChart::class, OrdersSalesChart::class, AverageOrderValueChart::class, NewVsReturningCustomersChart::class, + ]); + } + + public function getDefaultTableWidgets(): array + { + return self::callLunarHook('getTableWidgets', [ PopularProductsTable::class, LatestOrdersTable::class, - ]; + ]); } public static function getNavigationIcon(): ?string From 0d2a4c617f6dcb53656140758354075f4152ea3c Mon Sep 17 00:00:00 2001 From: Tobi Date: Tue, 14 Oct 2025 13:29:10 +0200 Subject: [PATCH 02/50] Make Admin Dashboard compatible with immutable Carbon Dates (#2307) --- .../Widgets/Dashboard/Orders/AverageOrderValueChart.php | 3 ++- .../Widgets/Dashboard/Orders/NewVsReturningCustomersChart.php | 3 ++- .../Filament/Widgets/Dashboard/Orders/OrderStatsOverview.php | 3 ++- .../src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php | 3 ++- .../src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php index 031701b041..8e2ba11ece 100644 --- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php +++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php @@ -2,6 +2,7 @@ namespace Lunar\Admin\Filament\Widgets\Dashboard\Orders; +use Carbon\CarbonInterface; use Carbon\CarbonPeriod; use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget; use Lunar\Facades\DB; @@ -23,7 +24,7 @@ protected function getHeading(): ?string return __('lunarpanel::widgets.dashboard.orders.average_order_value.heading'); } - protected function getOrderQuery(?\DateTime $from = null, ?\DateTime $to = null) + protected function getOrderQuery(\DateTime|CarbonInterface|null $from = null, \DateTime|CarbonInterface|null $to = null) { return Order::whereNotNull('placed_at') ->with(['currency']) diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/NewVsReturningCustomersChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/NewVsReturningCustomersChart.php index 686aea1cbf..261ed94e90 100644 --- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/NewVsReturningCustomersChart.php +++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/NewVsReturningCustomersChart.php @@ -2,6 +2,7 @@ namespace Lunar\Admin\Filament\Widgets\Dashboard\Orders; +use Carbon\CarbonInterface; use Carbon\CarbonPeriod; use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget; use Lunar\Facades\DB; @@ -22,7 +23,7 @@ protected function getHeading(): ?string return __('lunarpanel::widgets.dashboard.orders.new_returning_customers.heading'); } - protected function getOrderQuery(?\DateTime $from = null, ?\DateTime $to = null) + protected function getOrderQuery(\DateTime|CarbonInterface|null $from = null, \DateTime|CarbonInterface|null $to = null) { return Order::whereNotNull('placed_at') ->whereBetween('placed_at', [ diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderStatsOverview.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderStatsOverview.php index 01247ad7a8..48427028e1 100644 --- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderStatsOverview.php +++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderStatsOverview.php @@ -2,6 +2,7 @@ namespace Lunar\Admin\Filament\Widgets\Dashboard\Orders; +use Carbon\CarbonInterface; use Filament\Support\Facades\FilamentIcon; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Stat; @@ -12,7 +13,7 @@ class OrderStatsOverview extends BaseWidget { protected static ?string $pollingInterval = '60s'; - protected function getOrderQuery(?\DateTime $from = null, ?\DateTime $to = null) + protected function getOrderQuery(\DateTime|CarbonInterface|null $from = null, \DateTime|CarbonInterface|null $to = null) { return Order::whereNotNull('placed_at') ->whereBetween('placed_at', [ diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php index 40a8ab4f2b..c6c85b3f86 100644 --- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php +++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php @@ -2,6 +2,7 @@ namespace Lunar\Admin\Filament\Widgets\Dashboard\Orders; +use Carbon\CarbonInterface; use Carbon\CarbonPeriod; use Filament\Widgets\Concerns\InteractsWithPageFilters; use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget; @@ -25,7 +26,7 @@ protected function getHeading(): ?string return __('lunarpanel::widgets.dashboard.orders.order_totals_chart.heading'); } - protected function getOrderQuery(?\DateTime $from = null, ?\DateTime $to = null) + protected function getOrderQuery(\DateTime|CarbonInterface|null $from = null, \DateTime|CarbonInterface|null $to = null) { return Order::whereNotNull('placed_at') ->with(['currency']) diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php index 8c8da66d47..1c93b3cefa 100644 --- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php +++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php @@ -2,6 +2,7 @@ namespace Lunar\Admin\Filament\Widgets\Dashboard\Orders; +use Carbon\CarbonInterface; use Filament\Widgets\Concerns\InteractsWithPageFilters; use Leandrocfe\FilamentApexCharts\Widgets\ApexChartWidget; use Lunar\Facades\DB; @@ -24,7 +25,7 @@ protected function getHeading(): ?string return __('lunarpanel::widgets.dashboard.orders.order_sales_chart.heading'); } - protected function getOrderQuery(?\DateTime $from = null, ?\DateTime $to = null) + protected function getOrderQuery(\DateTime|CarbonInterface|null $from = null, \DateTime|CarbonInterface|null $to = null) { return Order::whereNotNull('placed_at') ->with(['currency']) From f2f4535318d83667e5697ef4e22f43981c6a1e43 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Wed, 29 Oct 2025 09:26:19 +0000 Subject: [PATCH 03/50] Add some additional database indexes (#2321) --- ...9_084000_add_missing_indexes_to_tables.php | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 packages/core/database/migrations/2025_10_29_084000_add_missing_indexes_to_tables.php diff --git a/packages/core/database/migrations/2025_10_29_084000_add_missing_indexes_to_tables.php b/packages/core/database/migrations/2025_10_29_084000_add_missing_indexes_to_tables.php new file mode 100644 index 0000000000..a00ee885cb --- /dev/null +++ b/packages/core/database/migrations/2025_10_29_084000_add_missing_indexes_to_tables.php @@ -0,0 +1,64 @@ + [ + 'enabled', + 'starts_at', + ], + 'products' => [ + 'deleted_at', + ], + 'product_variants' => [ + 'deleted_at', + ], + ]; + + /** + * Run the migrations. + */ + public function up(): void + { + foreach ($this->columnsToUpdate as $table => $columns) { + $fullTableName = $this->getTableName($table); + + Schema::table($fullTableName, function (Blueprint $tableBlueprint) use ($columns) { + foreach ($columns as $column) { + $tableBlueprint->index($column); + } + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach ($this->columnsToUpdate as $table => $columns) { + $fullTableName = $this->getTableName($table); + + Schema::table($fullTableName, function (Blueprint $tableBlueprint) use ($columns) { + foreach ($columns as $column) { + $tableBlueprint->dropIndex([$column]); + } + }); + } + } + + /** + * Get the full table name with appropriate prefix. + */ + private function getTableName(string $table): string + { + return in_array($table, ['activity_log', 'media']) ? $table : $this->prefix.$table; + } +}; From fccd6c4324b16d7fd1cbd10dd270577f8cf351ad Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 4 Nov 2025 11:25:10 +0000 Subject: [PATCH 04/50] Update bug-report.md --- .github/ISSUE_TEMPLATE/bug-report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index a4433edf11..b18d2a4700 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,8 @@ name: "\U0001F41B Bug report" about: Report something that's broken. title: '' -labels: bug,unconfirmed +type: bug +labels: unconfirmed assignees: '' --- From 8d07f51881e56815c9531dc7c8da118d7a349fa5 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 4 Nov 2025 11:25:51 +0000 Subject: [PATCH 05/50] Update bug-report.md --- .github/ISSUE_TEMPLATE/bug-report.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index b18d2a4700..5f20067225 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -3,7 +3,6 @@ name: "\U0001F41B Bug report" about: Report something that's broken. title: '' type: bug -labels: unconfirmed assignees: '' --- From 7c75648b1d73f71ba4bf8fe7b7919be675807ad0 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 4 Nov 2025 12:38:14 +0000 Subject: [PATCH 06/50] Update bug-report.md --- .github/ISSUE_TEMPLATE/bug-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 5f20067225..bec79b268e 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -3,6 +3,7 @@ name: "\U0001F41B Bug report" about: Report something that's broken. title: '' type: bug +projects: ['lunarphp/9'] assignees: '' --- From 8934e73cfe06bc5f2a3a68cd4321674ddd354768 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 4 Nov 2025 13:03:19 +0000 Subject: [PATCH 07/50] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b3658ef311..3687e7a09c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repository serves as a monorepo for the main packages that make up Lunar. ## Documentation -- [v1.0 documentation](https://docs-v1.lunarphp.io/) +- [v1.0 documentation](https://docs.lunarphp.com/) ## Contribution @@ -18,8 +18,8 @@ This repository serves as a monorepo for the main packages that make up Lunar. ## Community -- [Join our discord server](https://discord.gg/v6qVWaf) and chat to the developers and people using Lunar. -- [We have a roadmap](https://github.com/orgs/lunarphp/projects/8) where we will be detailing which features are next. +- [Join our discord server](https://lunarphp.com/discord) and chat to the developers and people using Lunar. +- [We have a roadmap](https://github.com/orgs/lunarphp/projects/9) where we will be detailing which features are next. ## Packages in this monorepo From 118d33052d7b2bcc8e1075468411a17af25ce490 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 4 Nov 2025 13:03:50 +0000 Subject: [PATCH 08/50] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3687e7a09c..b80df30e68 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -

Lunar

+

Lunar

-[Lunar](https://lunarphp.io) is a set of Laravel packages that bring functionality akin to Shopify and other e-commerce platforms to +[Lunar](https://lunarphp.com) is a set of Laravel packages that bring functionality akin to Shopify and other e-commerce platforms to Laravel. You have complete freedom to create your own storefront(s), but we've already done the hard work for you in the backend. From 2c0ccfd8e6a3e073745e6aae47bea1bd1a2e8422 Mon Sep 17 00:00:00 2001 From: VincentTeresa Date: Tue, 11 Nov 2025 15:48:28 +0100 Subject: [PATCH 09/50] Add translation support for EditShippingZone navigation label (#2317) This PR adds the missing getNavigationLabel() method to EditShippingZone to maintain consistency with other pages in the module (ManageShippingRates and ManageShippingExclusions). Fixes issue #2316 --------- Co-authored-by: Glenn Jacobs --- .../ShippingMethodResource/Pages/EditShippingMethod.php | 6 ++++++ .../ShippingZoneResource/Pages/EditShippingZone.php | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php index 7279be485a..ce9c463a76 100644 --- a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php +++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php @@ -17,6 +17,12 @@ protected function getDefaultHeaderActions(): array ]; } + public static function getNavigationLabel(): string + { + return __('filament-panels::resources/pages/edit-record.title', [ + 'label' => __('lunarpanel.shipping::shippingmethod.label'), + ]); + } protected function getRedirectUrl(): string { return $this->getResource()::getUrl('index'); diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php index 754106f684..fd4075aebe 100644 --- a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php +++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php @@ -17,6 +17,13 @@ protected function getDefaultHeaderActions(): array ]; } + public static function getNavigationLabel(): string + { + return __('filament-panels::resources/pages/edit-record.title', [ + 'label' => __('lunarpanel.shipping::shippingzone.label'), + ]); + } + protected function getRedirectUrl(): string { return $this->getResource()::getUrl('index'); From c1f755c4ae99bea49462cba55f2164a4770789f0 Mon Sep 17 00:00:00 2001 From: Huncsuga <91792987+Huncsuga@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:50:00 +0200 Subject: [PATCH 10/50] Add Romanian translations (#2311) I added the Romanian translations to the package. --------- Co-authored-by: Glenn Jacobs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/admin/resources/lang/ro/actions.php | 52 +++ packages/admin/resources/lang/ro/activity.php | 29 ++ packages/admin/resources/lang/ro/address.php | 99 +++++ .../admin/resources/lang/ro/attribute.php | 55 +++ .../resources/lang/ro/attributegroup.php | 46 +++ packages/admin/resources/lang/ro/auth.php | 32 ++ packages/admin/resources/lang/ro/brand.php | 75 ++++ packages/admin/resources/lang/ro/channel.php | 39 ++ .../admin/resources/lang/ro/collection.php | 45 +++ .../resources/lang/ro/collectiongroup.php | 37 ++ .../admin/resources/lang/ro/components.php | 117 ++++++ packages/admin/resources/lang/ro/currency.php | 58 +++ packages/admin/resources/lang/ro/customer.php | 63 ++++ .../admin/resources/lang/ro/customergroup.php | 40 ++ packages/admin/resources/lang/ro/discount.php | 353 ++++++++++++++++++ .../admin/resources/lang/ro/fieldtypes.php | 72 ++++ packages/admin/resources/lang/ro/global.php | 12 + packages/admin/resources/lang/ro/language.php | 33 ++ packages/admin/resources/lang/ro/order.php | 305 +++++++++++++++ packages/admin/resources/lang/ro/product.php | 131 +++++++ .../admin/resources/lang/ro/productoption.php | 127 +++++++ .../admin/resources/lang/ro/producttype.php | 52 +++ .../resources/lang/ro/productvariant.php | 105 ++++++ .../resources/lang/ro/relationmanagers.php | 285 ++++++++++++++ packages/admin/resources/lang/ro/staff.php | 81 ++++ packages/admin/resources/lang/ro/tag.php | 21 ++ packages/admin/resources/lang/ro/tax.php | 9 + packages/admin/resources/lang/ro/taxclass.php | 32 ++ packages/admin/resources/lang/ro/taxrate.php | 33 ++ packages/admin/resources/lang/ro/taxzone.php | 69 ++++ packages/admin/resources/lang/ro/user.php | 29 ++ packages/admin/resources/lang/ro/widgets.php | 118 ++++++ packages/core/resources/lang/ro/base.php | 9 + .../core/resources/lang/ro/exceptions.php | 21 ++ .../resources/lang/ro/plugin.php | 7 + .../resources/lang/ro/relationmanagers.php | 80 ++++ .../lang/ro/shippingexclusionlist.php | 19 + .../resources/lang/ro/shippingmethod.php | 58 +++ .../resources/lang/ro/shippingzone.php | 50 +++ 39 files changed, 2898 insertions(+) create mode 100644 packages/admin/resources/lang/ro/actions.php create mode 100644 packages/admin/resources/lang/ro/activity.php create mode 100644 packages/admin/resources/lang/ro/address.php create mode 100644 packages/admin/resources/lang/ro/attribute.php create mode 100644 packages/admin/resources/lang/ro/attributegroup.php create mode 100644 packages/admin/resources/lang/ro/auth.php create mode 100644 packages/admin/resources/lang/ro/brand.php create mode 100644 packages/admin/resources/lang/ro/channel.php create mode 100644 packages/admin/resources/lang/ro/collection.php create mode 100644 packages/admin/resources/lang/ro/collectiongroup.php create mode 100644 packages/admin/resources/lang/ro/components.php create mode 100644 packages/admin/resources/lang/ro/currency.php create mode 100644 packages/admin/resources/lang/ro/customer.php create mode 100644 packages/admin/resources/lang/ro/customergroup.php create mode 100644 packages/admin/resources/lang/ro/discount.php create mode 100644 packages/admin/resources/lang/ro/fieldtypes.php create mode 100644 packages/admin/resources/lang/ro/global.php create mode 100644 packages/admin/resources/lang/ro/language.php create mode 100644 packages/admin/resources/lang/ro/order.php create mode 100644 packages/admin/resources/lang/ro/product.php create mode 100644 packages/admin/resources/lang/ro/productoption.php create mode 100644 packages/admin/resources/lang/ro/producttype.php create mode 100644 packages/admin/resources/lang/ro/productvariant.php create mode 100644 packages/admin/resources/lang/ro/relationmanagers.php create mode 100644 packages/admin/resources/lang/ro/staff.php create mode 100644 packages/admin/resources/lang/ro/tag.php create mode 100644 packages/admin/resources/lang/ro/tax.php create mode 100644 packages/admin/resources/lang/ro/taxclass.php create mode 100644 packages/admin/resources/lang/ro/taxrate.php create mode 100644 packages/admin/resources/lang/ro/taxzone.php create mode 100644 packages/admin/resources/lang/ro/user.php create mode 100644 packages/admin/resources/lang/ro/widgets.php create mode 100644 packages/core/resources/lang/ro/base.php create mode 100644 packages/core/resources/lang/ro/exceptions.php create mode 100644 packages/table-rate-shipping/resources/lang/ro/plugin.php create mode 100644 packages/table-rate-shipping/resources/lang/ro/relationmanagers.php create mode 100644 packages/table-rate-shipping/resources/lang/ro/shippingexclusionlist.php create mode 100644 packages/table-rate-shipping/resources/lang/ro/shippingmethod.php create mode 100644 packages/table-rate-shipping/resources/lang/ro/shippingzone.php diff --git a/packages/admin/resources/lang/ro/actions.php b/packages/admin/resources/lang/ro/actions.php new file mode 100644 index 0000000000..42b316669f --- /dev/null +++ b/packages/admin/resources/lang/ro/actions.php @@ -0,0 +1,52 @@ + [ + 'create_root' => [ + 'label' => 'Creează colecție rădăcină', + ], + 'create_child' => [ + 'label' => 'Creează colecție copil', + ], + 'move' => [ + 'label' => 'Mută colecția', + ], + 'delete' => [ + 'label' => 'Șterge', + 'notifications' => [ + 'cannot_delete' => [ + 'title' => 'Nu se poate șterge', + 'body' => 'Această colecție are colecții copil și nu poate fi ștearsă.', + ], + ], + ], + ], + 'orders' => [ + 'update_status' => [ + 'label' => 'Actualizează starea', + 'wizard' => [ + 'step_one' => [ + 'label' => 'Stare', + ], + 'step_two' => [ + 'label' => 'E-mailuri și notificări', + 'no_mailers' => 'Nu există e-mailuri disponibile pentru această stare.', + ], + 'step_three' => [ + 'label' => 'Previzualizare și salvare', + 'no_mailers' => 'Nu s-au ales e-mailuri pentru previzualizare.', + ], + ], + 'notification' => [ + 'label' => 'Starea comenzii a fost actualizată', + ], + 'billing_email' => [ + 'label' => 'E-mail facturare', + ], + 'shipping_email' => [ + 'label' => 'E-mail livrare', + ], + ], + + ], +]; diff --git a/packages/admin/resources/lang/ro/activity.php b/packages/admin/resources/lang/ro/activity.php new file mode 100644 index 0000000000..140f76b6ba --- /dev/null +++ b/packages/admin/resources/lang/ro/activity.php @@ -0,0 +1,29 @@ + 'Activitate', + + 'plural_label' => 'Activități', + + 'table' => [ + 'subject' => 'Subiect', + 'description' => 'Descriere', + 'log' => 'Jurnal', + 'logged_at' => 'Înregistrat la', + 'event' => 'Eveniment', + 'logged_from' => 'Înregistrat de la', + 'logged_until' => 'Înregistrat până la', + ], + + 'form' => [ + 'causer_type' => 'Tipul cauzatorului', + 'causer_id' => 'ID autor', + 'subject_type' => 'Tip subiect', + 'subject_id' => 'ID subiect', + 'description' => 'Descriere', + 'attributes' => 'Atribute', + 'old' => 'Vechi', + ], + +]; diff --git a/packages/admin/resources/lang/ro/address.php b/packages/admin/resources/lang/ro/address.php new file mode 100644 index 0000000000..dd968dfdc1 --- /dev/null +++ b/packages/admin/resources/lang/ro/address.php @@ -0,0 +1,99 @@ + 'Adresă', + + 'plural_label' => 'Adrese', + + 'table' => [ + 'title' => [ + 'label' => 'Titlu', + ], + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'company_name' => [ + 'label' => 'Nume companie', + ], + 'tax_identifier' => [ + 'label' => 'Cod fiscal', + ], + 'line_one' => [ + 'label' => 'Adresă', + ], + 'line_two' => [ + 'label' => 'Linia doi', + ], + 'line_three' => [ + 'label' => 'Linia trei', + ], + 'city' => [ + 'label' => 'Oraș', + ], + 'country_id' => [ + 'label' => 'Țară', + ], + 'state' => [ + 'label' => 'Județ', + ], + 'postcode' => [ + 'label' => 'Cod poștal', + ], + 'contact_email' => [ + 'label' => 'E-mail contact', + ], + 'contact_phone' => [ + 'label' => 'Telefon contact', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Titlu', + ], + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'company_name' => [ + 'label' => 'Nume companie', + ], + 'tax_identifier' => [ + 'label' => 'Cod fiscal', + ], + 'line_one' => [ + 'label' => 'Linia unu', + ], + 'line_two' => [ + 'label' => 'Linia doi', + ], + 'line_three' => [ + 'label' => 'Linia trei', + ], + 'city' => [ + 'label' => 'Oraș', + ], + 'country_id' => [ + 'label' => 'Țară', + ], + 'state' => [ + 'label' => 'Județ', + ], + 'postcode' => [ + 'label' => 'Cod poștal', + ], + 'contact_email' => [ + 'label' => 'E-mail contact', + ], + 'contact_phone' => [ + 'label' => 'Telefon contact', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/attribute.php b/packages/admin/resources/lang/ro/attribute.php new file mode 100644 index 0000000000..2ed68dd12d --- /dev/null +++ b/packages/admin/resources/lang/ro/attribute.php @@ -0,0 +1,55 @@ + 'Atribut', + + 'plural_label' => 'Atribute', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'description' => [ + 'label' => 'Descriere', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'type' => [ + 'label' => 'Tip', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tip', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'description' => [ + 'label' => 'Descriere', + 'helper' => 'Folosit pentru a afișa textul de ajutor sub câmp', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'searchable' => [ + 'label' => 'Căutabil', + ], + 'filterable' => [ + 'label' => 'Filtrabil', + ], + 'required' => [ + 'label' => 'Obligatoriu', + ], + 'type' => [ + 'label' => 'Tip', + ], + 'validation_rules' => [ + 'label' => 'Reguli de validare', + 'helper' => 'Reguli pentru câmpul atribut, de ex.: min:1|max:10|...', + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/attributegroup.php b/packages/admin/resources/lang/ro/attributegroup.php new file mode 100644 index 0000000000..b913126cde --- /dev/null +++ b/packages/admin/resources/lang/ro/attributegroup.php @@ -0,0 +1,46 @@ + 'Grup de atribute', + + 'plural_label' => 'Grupe de atribute', + + 'table' => [ + 'attributable_type' => [ + 'label' => 'Tip', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'position' => [ + 'label' => 'Poziție', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tip', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'position' => [ + 'label' => 'Poziție', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Acest grup de atribute nu poate fi șters deoarece are atribute asociate.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/auth.php b/packages/admin/resources/lang/ro/auth.php new file mode 100644 index 0000000000..69ecef876a --- /dev/null +++ b/packages/admin/resources/lang/ro/auth.php @@ -0,0 +1,32 @@ + 'Administrator', + 'roles.admin.description' => 'Administrator cu acces complet', + 'roles.staff.label' => 'Personal', + 'roles.staff.description' => 'Personal cu acces de bază', + /** + * Permissions. + */ + 'permissions.settings.label' => 'Setări', + 'permissions.settings.description' => 'Oferă acces la zona de setări a hub-ului', + 'permissions.settings:core.label' => 'Setări de bază', + 'permissions.settings:core.description' => 'Acces la setările de bază ale magazinului, precum canale, limbi, monede etc.', + 'permissions.settings:manage-staff.label' => 'Gestionare personal', + 'permissions.settings:manage-staff.description' => 'Permite membrului de personal să editeze alți membri', + 'permissions.settings:manage-attributes.label' => 'Gestionare atribute', + 'permissions.settings:manage-attributes.description' => 'Permite personalului să editeze și să creeze atribute suplimentare', + 'permissions.catalog:manage-products.label' => 'Gestionare produse', + 'permissions.catalog:manage-products.description' => 'Permite personalului să editeze produse, tipuri de produse și mărci', + 'permissions.catalog:manage-collections.label' => 'Gestionare colecții', + 'permissions.catalog:manage-collections.description' => 'Permite personalului să editeze colecții și grupurile acestora', + 'permissions.sales:manage-orders.label' => 'Gestionare comenzi', + 'permissions.sales:manage-orders.description' => 'Permite personalului să gestioneze comenzile', + 'permissions.sales:manage-customers.label' => 'Gestionare clienți', + 'permissions.sales:manage-customers.description' => 'Permite personalului să gestioneze clienții', + 'permissions.sales:manage-discounts.label' => 'Gestionare reduceri', + 'permissions.sales:manage-discounts.description' => 'Permite personalului să gestioneze reducerile', +]; diff --git a/packages/admin/resources/lang/ro/brand.php b/packages/admin/resources/lang/ro/brand.php new file mode 100644 index 0000000000..060b203515 --- /dev/null +++ b/packages/admin/resources/lang/ro/brand.php @@ -0,0 +1,75 @@ + 'Marcă', + + 'plural_label' => 'Mărci', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'products_count' => [ + 'label' => 'Nr. produse', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Această marcă nu poate fi ștearsă deoarece are produse asociate.', + ], + ], + ], + 'pages' => [ + 'edit' => [ + 'title' => 'Informații de bază', + ], + 'products' => [ + 'label' => 'Produse', + 'actions' => [ + 'attach' => [ + 'label' => 'Asociază un produs', + 'form' => [ + 'record_id' => [ + 'label' => 'Produs', + ], + ], + 'notification' => [ + 'success' => 'Produs asociat cu marca', + ], + ], + 'detach' => [ + 'notification' => [ + 'success' => 'Produs detașat.', + ], + ], + ], + ], + 'collections' => [ + 'label' => 'Colecții', + 'table' => [ + 'header_actions' => [ + 'attach' => [ + 'record_select' => [ + 'placeholder' => 'Selectează o colecție', + ], + ], + ], + ], + 'actions' => [ + 'attach' => [ + 'label' => 'Asociază o colecție', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/channel.php b/packages/admin/resources/lang/ro/channel.php new file mode 100644 index 0000000000..e4a15ee6c2 --- /dev/null +++ b/packages/admin/resources/lang/ro/channel.php @@ -0,0 +1,39 @@ + 'Canal', + + 'plural_label' => 'Canale', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Implicit', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Implicit', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/collection.php b/packages/admin/resources/lang/ro/collection.php new file mode 100644 index 0000000000..623146ba26 --- /dev/null +++ b/packages/admin/resources/lang/ro/collection.php @@ -0,0 +1,45 @@ + 'Colecție', + + 'plural_label' => 'Colecții', + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + ], + + 'pages' => [ + 'children' => [ + 'label' => 'Colecții copil', + 'actions' => [ + 'create_child' => [ + 'label' => 'Creează colecție copil', + ], + ], + 'table' => [ + 'children_count' => [ + 'label' => 'Nr. copii', + ], + 'name' => [ + 'label' => 'Nume', + ], + ], + ], + 'edit' => [ + 'label' => 'Informații de bază', + ], + 'products' => [ + 'label' => 'Produse', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează produs', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/collectiongroup.php b/packages/admin/resources/lang/ro/collectiongroup.php new file mode 100644 index 0000000000..59a4ce017a --- /dev/null +++ b/packages/admin/resources/lang/ro/collectiongroup.php @@ -0,0 +1,37 @@ + 'Grup de colecții', + + 'plural_label' => 'Grupe de colecții', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'collections_count' => [ + 'label' => 'Nr. colecții', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Acest grup de colecții nu poate fi șters deoarece are colecții asociate.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/components.php b/packages/admin/resources/lang/ro/components.php new file mode 100644 index 0000000000..f9c5232ea0 --- /dev/null +++ b/packages/admin/resources/lang/ro/components.php @@ -0,0 +1,117 @@ + [ + 'notification' => [ + + 'updated' => 'Etichetele au fost actualizate', + + ], + ], + + 'activity-log' => [ + + 'input' => [ + + 'placeholder' => 'Adaugă un comentariu', + + ], + + 'action' => [ + + 'add-comment' => 'Adaugă comentariu', + + ], + + 'system' => 'Sistem', + + 'partials' => [ + 'orders' => [ + 'order_created' => 'Comandă creată', + + 'status_change' => 'Stare actualizată', + + 'capture' => 'Plată de :amount pe cardul care se termină cu :last_four', + + 'authorized' => 'Autorizare de :amount pe cardul care se termină cu :last_four', + + 'refund' => 'Rambursare de :amount pe cardul care se termină cu :last_four', + + 'address' => ':type actualizată', + + 'billingAddress' => 'Adresă de facturare', + + 'shippingAddress' => 'Adresă de livrare', + ], + + 'update' => [ + 'updated' => ':model actualizat', + ], + + 'create' => [ + 'created' => ':model creat', + ], + + 'tags' => [ + 'updated' => 'Etichetele au fost actualizate', + 'added' => 'Adăugat', + 'removed' => 'Eliminat', + ], + ], + + 'notification' => [ + 'comment_added' => 'Comentariu adăugat', + ], + + ], + + 'forms' => [ + 'youtube' => [ + 'helperText' => 'Introduceți ID-ul videoclipului YouTube, ex.: dQw4w9WgXcQ', + ], + ], + + 'collection-tree-view' => [ + 'actions' => [ + 'move' => [ + 'form' => [ + 'target_id' => [ + 'label' => 'Colecție părinte', + ], + ], + ], + ], + 'notifications' => [ + 'collections-reordered' => [ + 'success' => 'Colecțiile au fost reordonate', + ], + 'node-expanded' => [ + 'danger' => 'Nu s-au putut încărca colecțiile', + ], + 'delete' => [ + 'danger' => 'Nu se poate șterge colecția', + ], + ], + ], + + 'product-options-list' => [ + 'add-option' => [ + 'label' => 'Adaugă opțiune', + ], + 'delete-option' => [ + 'label' => 'Șterge opțiunea', + ], + 'remove-shared-option' => [ + 'label' => 'Elimină opțiunea partajată', + ], + 'add-value' => [ + 'label' => 'Adaugă altă valoare', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'values' => [ + 'label' => 'Valori', + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/currency.php b/packages/admin/resources/lang/ro/currency.php new file mode 100644 index 0000000000..80692f0aff --- /dev/null +++ b/packages/admin/resources/lang/ro/currency.php @@ -0,0 +1,58 @@ + 'Monedă', + + 'plural_label' => 'Monede', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'exchange_rate' => [ + 'label' => 'Rată de schimb', + ], + 'decimal_places' => [ + 'label' => 'Zecimale', + ], + 'enabled' => [ + 'label' => 'Activată', + ], + 'sync_prices' => [ + 'label' => 'Sincronizează prețurile', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'exchange_rate' => [ + 'label' => 'Rată de schimb', + ], + 'decimal_places' => [ + 'label' => 'Zecimale', + ], + 'enabled' => [ + 'label' => 'Activată', + ], + 'default' => [ + 'label' => 'Implicită', + ], + 'sync_prices' => [ + 'label' => 'Sincronizează prețurile', + 'helper_text' => 'Păstrează prețurile în această monedă sincronizate cu moneda implicită.', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/customer.php b/packages/admin/resources/lang/ro/customer.php new file mode 100644 index 0000000000..7ae1932f4f --- /dev/null +++ b/packages/admin/resources/lang/ro/customer.php @@ -0,0 +1,63 @@ + 'Client', + + 'plural_label' => 'Clienți', + + 'table' => [ + 'full_name' => [ + 'label' => 'Nume', + ], + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'title' => [ + 'label' => 'Titlu', + ], + 'company_name' => [ + 'label' => 'Nume companie', + ], + 'tax_identifier' => [ + 'label' => 'Cod fiscal', + ], + 'account_reference' => [ + 'label' => 'Referință cont', + ], + 'new' => [ + 'label' => 'Nou', + ], + 'returning' => [ + 'label' => 'Revenit', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Titlu', + ], + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'company_name' => [ + 'label' => 'Nume companie', + ], + 'account_ref' => [ + 'label' => 'Referință cont', + ], + 'tax_identifier' => [ + 'label' => 'Cod fiscal', + ], + 'customer_groups' => [ + 'label' => 'Grupuri de clienți', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/customergroup.php b/packages/admin/resources/lang/ro/customergroup.php new file mode 100644 index 0000000000..9f71baf618 --- /dev/null +++ b/packages/admin/resources/lang/ro/customergroup.php @@ -0,0 +1,40 @@ + 'Grup de clienți', + + 'plural_label' => 'Grupe de clienți', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'default' => [ + 'label' => 'Implicit', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'default' => [ + 'label' => 'Implicit', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Acest grup de clienți nu poate fi șters deoarece are clienți asociați.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/discount.php b/packages/admin/resources/lang/ro/discount.php new file mode 100644 index 0000000000..1429cbc48d --- /dev/null +++ b/packages/admin/resources/lang/ro/discount.php @@ -0,0 +1,353 @@ + 'Reduceri', + 'label' => 'Reducere', + 'form' => [ + 'conditions' => [ + 'heading' => 'Condiții', + ], + 'buy_x_get_y' => [ + 'heading' => 'Cumperi X, primești Y', + ], + 'amount_off' => [ + 'heading' => 'Reducere sumă', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'starts_at' => [ + 'label' => 'Data de început', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + ], + 'priority' => [ + 'label' => 'Prioritate', + 'helper_text' => 'Reducerile cu prioritate mai mare vor fi aplicate primele.', + 'options' => [ + 'low' => [ + 'label' => 'Scăzută', + ], + 'medium' => [ + 'label' => 'Mediu', + ], + 'high' => [ + 'label' => 'Ridicată', + ], + ], + ], + 'stop' => [ + 'label' => 'Oprește aplicarea altor reduceri după aceasta', + ], + 'coupon' => [ + 'label' => 'Cupon', + 'helper_text' => 'Introduceți cuponul necesar pentru aplicarea reducerii; dacă este lăsat gol, se aplică automat.', + ], + 'max_uses' => [ + 'label' => 'Utilizări maxime', + 'helper_text' => 'Lăsați gol pentru utilizări nelimitate.', + ], + 'max_uses_per_user' => [ + 'label' => 'Utilizări maxime per utilizator', + 'helper_text' => 'Lăsați gol pentru utilizări nelimitate.', + ], + 'minimum_cart_amount' => [ + 'label' => 'Valoare minimă coș', + ], + 'min_qty' => [ + 'label' => 'Cantitate produs', + 'helper_text' => 'Stabiliți câte produse eligibile sunt necesare pentru aplicarea reducerii.', + ], + 'reward_qty' => [ + 'label' => 'Nr. articole gratuite', + 'helper_text' => 'Câte unități din fiecare articol sunt reduse.', + ], + 'max_reward_qty' => [ + 'label' => 'Cantitate maximă recompensă', + 'helper_text' => 'Numărul maxim de produse ce pot fi reduse, indiferent de criterii.', + ], + 'automatic_rewards' => [ + 'label' => 'Adaugă automat recompense', + 'helper_text' => 'Activați pentru a adăuga produse-recompensă când nu sunt în coș.', + ], + 'fixed_value' => [ + 'label' => 'Valoare fixă', + ], + 'percentage' => [ + 'label' => 'Procent', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'status' => [ + 'label' => 'Stare', + \Lunar\Models\Discount::ACTIVE => [ + 'label' => 'Activă', + ], + \Lunar\Models\Discount::PENDING => [ + 'label' => 'În așteptare', + ], + \Lunar\Models\Discount::EXPIRED => [ + 'label' => 'Expirată', + ], + \Lunar\Models\Discount::SCHEDULED => [ + 'label' => 'Programată', + ], + ], + 'type' => [ + 'label' => 'Tip', + ], + 'starts_at' => [ + 'label' => 'Data de început', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + ], + 'created_at' => [ + 'label' => 'Creat la', + ], + 'coupon' => [ + 'label' => 'Cupon', + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilitate', + ], + 'edit' => [ + 'title' => 'Informații de bază', + ], + 'limitations' => [ + 'label' => 'Limitări', + ], + ], + 'relationmanagers' => [ + 'collections' => [ + 'title' => 'Colecții', + 'description' => 'Selectați colecțiile la care se limitează această reducere.', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează colecție', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + 'customers' => [ + 'title' => 'Clienți', + 'description' => 'Selectați clienții la care se limitează această reducere.', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează client', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + ], + ], + 'brands' => [ + 'title' => 'Mărci', + 'description' => 'Selectați mărcile la care se limitează această reducere.', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează marcă', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + 'products' => [ + 'title' => 'Produse', + 'description' => 'Selectați produsele la care se limitează această reducere.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adaugă produs', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + 'rewards' => [ + 'title' => 'Recompense', + 'description' => 'Selectați produsele care vor fi reduse dacă sunt în coș și sunt îndeplinite condițiile de mai sus.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adaugă recompensă', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + 'conditions' => [ + 'title' => 'Condiții', + 'description' => 'Selectați condițiile necesare pentru aplicarea reducerii.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adaugă condiție', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + 'productvariants' => [ + 'title' => 'Variante de produs', + 'description' => 'Selectați variantele de produs la care se limitează această reducere.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adaugă variantă de produs', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'values' => [ + 'label' => 'Opțiune(i)', + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitare', + ], + 'exclusion' => [ + 'label' => 'Excludere', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/fieldtypes.php b/packages/admin/resources/lang/ro/fieldtypes.php new file mode 100644 index 0000000000..45d32b2ead --- /dev/null +++ b/packages/admin/resources/lang/ro/fieldtypes.php @@ -0,0 +1,72 @@ + [ + 'label' => 'Listă derulantă', + 'form' => [ + 'lookups' => [ + 'label' => 'Valori', + 'key_label' => 'Etichetă', + 'value_label' => 'Valoare', + ], + ], + ], + 'listfield' => [ + 'label' => 'Câmp listă', + ], + 'text' => [ + 'label' => 'Text', + 'form' => [ + 'richtext' => [ + 'label' => 'Text îmbogățit', + ], + ], + ], + 'translatedtext' => [ + 'label' => 'Text tradus', + 'form' => [ + 'richtext' => [ + 'label' => 'Text îmbogățit', + ], + 'locales' => 'Locale', + ], + ], + 'toggle' => [ + 'label' => 'Comutator', + ], + 'youtube' => [ + 'label' => 'YouTube', + ], + 'vimeo' => [ + 'label' => 'Vimeo', + ], + 'number' => [ + 'label' => 'Număr', + 'form' => [ + 'min' => [ + 'label' => 'Min.', + ], + 'max' => [ + 'label' => 'Max.', + ], + ], + ], + 'file' => [ + 'label' => 'Fișier', + 'form' => [ + 'file_types' => [ + 'label' => 'Tipuri de fișiere permise', + 'placeholder' => 'MIME nou', + ], + 'multiple' => [ + 'label' => 'Permite fișiere multiple', + ], + 'min_files' => [ + 'label' => 'Min. fișiere', + ], + 'max_files' => [ + 'label' => 'Max. fișiere', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/global.php b/packages/admin/resources/lang/ro/global.php new file mode 100644 index 0000000000..3bad6cff32 --- /dev/null +++ b/packages/admin/resources/lang/ro/global.php @@ -0,0 +1,12 @@ + [ + 'catalog' => 'Catalog', + 'sales' => 'Vânzări', + 'reports' => 'Rapoarte', + 'settings' => 'Setări', + ], + +]; diff --git a/packages/admin/resources/lang/ro/language.php b/packages/admin/resources/lang/ro/language.php new file mode 100644 index 0000000000..bcb60dbc68 --- /dev/null +++ b/packages/admin/resources/lang/ro/language.php @@ -0,0 +1,33 @@ + 'Limbă', + + 'plural_label' => 'Limbi', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/order.php b/packages/admin/resources/lang/ro/order.php new file mode 100644 index 0000000000..8e77534201 --- /dev/null +++ b/packages/admin/resources/lang/ro/order.php @@ -0,0 +1,305 @@ + 'Comandă', + + 'plural_label' => 'Comenzi', + + 'breadcrumb' => [ + 'manage' => 'Gestionează', + ], + + 'tabs' => [ + 'all' => 'Toate', + ], + + 'transactions' => [ + 'capture' => 'Capturat', + 'intent' => 'Intenție', + 'refund' => 'Rambursat', + 'failed' => 'Eșuat', + ], + + 'table' => [ + 'status' => [ + 'label' => 'Stare', + ], + 'reference' => [ + 'label' => 'Referință', + ], + 'customer_reference' => [ + 'label' => 'Referință client', + ], + 'customer' => [ + 'label' => 'Client', + ], + 'tags' => [ + 'label' => 'Etichete', + ], + 'postcode' => [ + 'label' => 'Cod poștal', + ], + 'email' => [ + 'label' => 'E-mail', + 'copy_message' => 'Adresă e-mail copiată', + ], + 'phone' => [ + 'label' => 'Telefon', + ], + 'total' => [ + 'label' => 'Total', + ], + 'date' => [ + 'label' => 'Data', + ], + 'new_customer' => [ + 'label' => 'Tip client', + ], + 'placed_after' => [ + 'label' => 'Plasată după', + ], + 'placed_before' => [ + 'label' => 'Plasată înainte de', + ], + ], + + 'form' => [ + 'address' => [ + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'line_one' => [ + 'label' => 'Adresă linia 1', + ], + 'line_two' => [ + 'label' => 'Adresă linia 2', + ], + 'line_three' => [ + 'label' => 'Adresă linia 3', + ], + 'company_name' => [ + 'label' => 'Nume companie', + ], + 'tax_identifier' => [ + 'label' => 'Cod fiscal', + ], + 'contact_phone' => [ + 'label' => 'Telefon', + ], + 'contact_email' => [ + 'label' => 'Adresă e-mail', + ], + 'city' => [ + 'label' => 'Oraș', + ], + 'state' => [ + 'label' => 'Județ / Provincie', + ], + 'postcode' => [ + 'label' => 'Cod poștal', + ], + 'country_id' => [ + 'label' => 'Țară', + ], + ], + + 'reference' => [ + 'label' => 'Referință', + ], + 'status' => [ + 'label' => 'Stare', + ], + 'transaction' => [ + 'label' => 'Tranzacție', + ], + 'amount' => [ + 'label' => 'Sumă', + + 'hint' => [ + 'less_than_total' => 'Urmează să capturezi o sumă mai mică decât valoarea totală a tranzacției', + ], + ], + + 'notes' => [ + 'label' => 'Note', + ], + 'confirm' => [ + 'label' => 'Confirmă', + + 'alert' => 'Este necesară confirmarea', + + 'hint' => [ + 'capture' => 'Confirmă că dorești să capturezi această plată', + 'refund' => 'Confirmă că dorești să rambursezi această sumă.', + ], + ], + ], + + 'infolist' => [ + 'notes' => [ + 'label' => 'Note', + 'placeholder' => 'Nu există note pentru această comandă', + ], + 'delivery_instructions' => [ + 'label' => 'Instrucțiuni de livrare', + ], + 'shipping_total' => [ + 'label' => 'Total livrare', + ], + 'paid' => [ + 'label' => 'Plătit', + ], + 'refund' => [ + 'label' => 'Rambursare', + ], + 'unit_price' => [ + 'label' => 'Preț unitar', + ], + 'quantity' => [ + 'label' => 'Cantitate', + ], + 'sub_total' => [ + 'label' => 'Subtotal', + ], + 'discount_total' => [ + 'label' => 'Total reducere', + ], + 'total' => [ + 'label' => 'Total', + ], + 'current_stock_level' => [ + 'message' => 'Stoc curent: :count', + ], + 'purchase_stock_level' => [ + 'message' => 'la momentul comenzii: :count', + ], + 'status' => [ + 'label' => 'Stare', + ], + 'reference' => [ + 'label' => 'Referință', + ], + 'customer_reference' => [ + 'label' => 'Referință client', + ], + 'channel' => [ + 'label' => 'Canal', + ], + 'date_created' => [ + 'label' => 'Data creării', + ], + 'date_placed' => [ + 'label' => 'Data plasării', + ], + 'new_returning' => [ + 'label' => 'Nou / Revenit', + ], + 'new_customer' => [ + 'label' => 'Client nou', + ], + 'returning_customer' => [ + 'label' => 'Client revenit', + ], + 'shipping_address' => [ + 'label' => 'Adresă de livrare', + ], + 'billing_address' => [ + 'label' => 'Adresă de facturare', + ], + 'address_not_set' => [ + 'label' => 'Nicio adresă setată', + ], + 'billing_matches_shipping' => [ + 'label' => 'La fel ca adresa de livrare', + ], + 'additional_info' => [ + 'label' => 'Informații suplimentare', + ], + 'no_additional_info' => [ + 'label' => 'Nu există informații suplimentare', + ], + 'tags' => [ + 'label' => 'Etichete', + ], + 'timeline' => [ + 'label' => 'Cronologie', + ], + 'transactions' => [ + 'label' => 'Tranzacții', + 'placeholder' => 'Nicio tranzacție', + ], + 'alert' => [ + 'requires_capture' => 'Această comandă necesită în continuare capturarea plății.', + 'partially_refunded' => 'Această comandă a fost rambursată parțial.', + 'refunded' => 'Această comandă a fost rambursată.', + ], + ], + + 'action' => [ + 'bulk_update_status' => [ + 'label' => 'Actualizează starea', + 'notification' => 'Starea comenzilor a fost actualizată', + ], + 'update_status' => [ + 'new_status' => [ + 'label' => 'Stare nouă', + ], + 'additional_content' => [ + 'label' => 'Conținut suplimentar', + ], + 'additional_email_recipient' => [ + 'label' => 'Destinatar e-mail suplimentar', + 'placeholder' => 'opțional', + ], + ], + 'download_order_pdf' => [ + 'label' => 'Descarcă PDF', + 'notification' => 'Descărcarea PDF-ului comenzii', + ], + 'edit_address' => [ + 'label' => 'Editează', + + 'notification' => [ + 'error' => 'Eroare', + + 'billing_address' => [ + 'saved' => 'Adresa de facturare a fost salvată', + ], + + 'shipping_address' => [ + 'saved' => 'Adresa de livrare a fost salvată', + ], + ], + ], + 'edit_tags' => [ + 'label' => 'Editează', + 'form' => [ + 'tags' => [ + 'label' => 'Etichete', + 'helper_text' => 'Separați etichetele apăsând Enter, Tab sau virgulă (,)', + ], + ], + ], + 'capture_payment' => [ + 'label' => 'Capturează plata', + + 'notification' => [ + 'error' => 'A apărut o problemă la capturare', + 'success' => 'Capturare reușită', + ], + ], + 'refund_payment' => [ + 'label' => 'Rambursare', + + 'notification' => [ + 'error' => 'A apărut o problemă la rambursare', + 'success' => 'Rambursare reușită', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/product.php b/packages/admin/resources/lang/ro/product.php new file mode 100644 index 0000000000..c3c423ad50 --- /dev/null +++ b/packages/admin/resources/lang/ro/product.php @@ -0,0 +1,131 @@ + 'Produs', + + 'plural_label' => 'Produse', + + 'tabs' => [ + 'all' => 'Toate', + ], + + 'status' => [ + 'unpublished' => [ + 'content' => 'În prezent în stadiu de ciornă, acest produs este ascuns în toate canalele și grupurile de clienți.', + ], + 'availability' => [ + 'customer_groups' => 'Acest produs nu este disponibil momentan pentru niciun grup de clienți.', + 'channels' => 'Acest produs nu este disponibil momentan în niciun canal.', + ], + ], + + 'table' => [ + 'status' => [ + 'label' => 'Stare', + 'states' => [ + 'deleted' => 'Șters', + 'draft' => 'Ciornă', + 'published' => 'Publicat', + ], + ], + 'name' => [ + 'label' => 'Nume', + ], + 'brand' => [ + 'label' => 'Marcă', + ], + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'stock' => [ + 'label' => 'Stoc', + ], + 'producttype' => [ + 'label' => 'Tip produs', + ], + ], + + 'actions' => [ + 'edit_status' => [ + 'label' => 'Actualizează starea', + 'heading' => 'Actualizează starea', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'brand' => [ + 'label' => 'Marcă', + ], + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'producttype' => [ + 'label' => 'Tip produs', + ], + 'status' => [ + 'label' => 'Stare', + 'options' => [ + 'published' => [ + 'label' => 'Publicat', + 'description' => 'Acest produs va fi disponibil în toate grupurile de clienți și canalele activate', + ], + 'draft' => [ + 'label' => 'Ciornă', + 'description' => 'Acest produs va fi ascuns în toate canalele și grupurile de clienți', + ], + ], + ], + 'tags' => [ + 'label' => 'Etichete', + 'helper_text' => 'Separați etichetele apăsând Enter, Tab sau virgulă (,)', + ], + 'collections' => [ + 'label' => 'Colecții', + 'select_collection' => 'Selectează o colecție', + ], + ], + + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilitate', + ], + 'edit' => [ + 'title' => 'Informații de bază', + ], + 'identifiers' => [ + 'label' => 'Identificatori produs', + ], + 'inventory' => [ + 'label' => 'Stoc', + ], + 'pricing' => [ + 'form' => [ + 'tax_class_id' => [ + 'label' => 'Clasă de taxe', + ], + 'tax_ref' => [ + 'label' => 'Referință taxe', + 'helper_text' => 'Opțional, pentru integrare cu sisteme terțe.', + ], + ], + ], + 'shipping' => [ + 'label' => 'Livrare', + ], + 'variants' => [ + 'label' => 'Variante', + ], + 'collections' => [ + 'label' => 'Colecții', + 'select_collection' => 'Selectează o colecție', + ], + 'associations' => [ + 'label' => 'Asocieri produs', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/productoption.php b/packages/admin/resources/lang/ro/productoption.php new file mode 100644 index 0000000000..ade933e58b --- /dev/null +++ b/packages/admin/resources/lang/ro/productoption.php @@ -0,0 +1,127 @@ + 'Opțiune produs', + + 'plural_label' => 'Opțiuni produs', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'label' => [ + 'label' => 'Etichetă', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + 'shared' => [ + 'label' => 'Partajată', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'label' => [ + 'label' => 'Etichetă', + ], + 'handle' => [ + 'label' => 'Identificator', + ], + ], + + 'widgets' => [ + 'product-options' => [ + 'notifications' => [ + 'save-variants' => [ + 'success' => [ + 'title' => 'Variantele de produs au fost salvate', + ], + ], + ], + 'actions' => [ + 'cancel' => [ + 'label' => 'Anulează', + ], + 'save-options' => [ + 'label' => 'Salvează opțiunile', + ], + 'add-shared-option' => [ + 'label' => 'Adaugă opțiune partajată', + 'form' => [ + 'product_option' => [ + 'label' => 'Opțiune produs', + ], + 'no_shared_components' => [ + 'label' => 'Nu există opțiuni partajate disponibile.', + ], + 'preselect' => [ + 'label' => 'Preselectează implicit toate valorile.', + ], + ], + ], + 'add-restricted-option' => [ + 'label' => 'Adaugă opțiune', + ], + ], + 'options-list' => [ + 'empty' => [ + 'heading' => 'Nu există opțiuni de produs configurate', + 'description' => 'Adaugă o opțiune partajată sau restricționată pentru a începe generarea variantelor.', + ], + ], + 'options-table' => [ + 'title' => 'Opțiuni produs', + 'configure-options' => [ + 'label' => 'Configurează opțiunile', + ], + 'table' => [ + 'option' => [ + 'label' => 'Opțiune', + ], + 'values' => [ + 'label' => 'Valori', + ], + ], + ], + 'variants-table' => [ + 'title' => 'Variante de produs', + 'actions' => [ + 'create' => [ + 'label' => 'Creează variantă', + ], + 'edit' => [ + 'label' => 'Editează', + ], + 'delete' => [ + 'label' => 'Șterge', + ], + ], + 'empty' => [ + 'heading' => 'Nu există variante configurate', + ], + 'table' => [ + 'new' => [ + 'label' => 'NOU', + ], + 'option' => [ + 'label' => 'Opțiune', + ], + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'price' => [ + 'label' => 'Preț', + ], + 'stock' => [ + 'label' => 'Stoc', + ], + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/producttype.php b/packages/admin/resources/lang/ro/producttype.php new file mode 100644 index 0000000000..8fcfe81306 --- /dev/null +++ b/packages/admin/resources/lang/ro/producttype.php @@ -0,0 +1,52 @@ + 'Tip produs', + + 'plural_label' => 'Tipuri de produse', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'products_count' => [ + 'label' => 'Număr produse', + ], + 'product_attributes_count' => [ + 'label' => 'Atribute produs', + ], + 'variant_attributes_count' => [ + 'label' => 'Atribute variantă', + ], + ], + + 'tabs' => [ + 'product_attributes' => [ + 'label' => 'Atribute produs', + ], + 'variant_attributes' => [ + 'label' => 'Atribute variantă', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + ], + + 'attributes' => [ + 'no_groups' => 'Nu există grupe de atribute disponibile.', + 'no_attributes' => 'Nu există atribute disponibile.', + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Acest tip de produs nu poate fi șters deoarece are produse asociate.', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/productvariant.php b/packages/admin/resources/lang/ro/productvariant.php new file mode 100644 index 0000000000..a3d933e909 --- /dev/null +++ b/packages/admin/resources/lang/ro/productvariant.php @@ -0,0 +1,105 @@ + 'Variantă produs', + 'plural_label' => 'Variante de produs', + 'pages' => [ + 'edit' => [ + 'title' => 'Informații de bază', + ], + 'media' => [ + 'title' => 'Media', + 'form' => [ + 'no_selection' => [ + 'label' => 'Nu aveți selectată nicio imagine pentru această variantă.', + ], + 'no_media_available' => [ + 'label' => 'Momentan nu există media disponibilă pentru acest produs.', + ], + 'images' => [ + 'label' => 'Imagine principală', + 'helper_text' => 'Selectați imaginea produsului care reprezintă această variantă.', + ], + ], + ], + 'identifiers' => [ + 'title' => 'Identificatori', + ], + 'inventory' => [ + 'title' => 'Stoc', + ], + 'shipping' => [ + 'title' => 'Livrare', + ], + ], + 'form' => [ + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'gtin' => [ + 'label' => 'Număr global de articol comercial (GTIN)', + ], + 'mpn' => [ + 'label' => 'Număr de parte al producătorului (MPN)', + ], + 'ean' => [ + 'label' => 'Cod de bare (UPC/EAN)', + ], + 'stock' => [ + 'label' => 'În stoc', + ], + 'backorder' => [ + 'label' => 'La precomandă', + ], + 'purchasable' => [ + 'label' => 'Disponibilitate la achiziție', + 'options' => [ + 'always' => 'Întotdeauna', + 'in_stock' => 'În stoc', + 'in_stock_or_on_backorder' => 'În stoc sau la precomandă', + ], + ], + 'unit_quantity' => [ + 'label' => 'Cantitate unitate', + 'helper_text' => 'Câte articole individuale compun 1 unitate.', + ], + 'min_quantity' => [ + 'label' => 'Cantitate minimă', + 'helper_text' => 'Cantitatea minimă dintr-o variantă de produs care poate fi cumpărată într-o singură achiziție.', + ], + 'quantity_increment' => [ + 'label' => 'Increment cantitate', + 'helper_text' => 'Varianta de produs trebuie cumpărată în multipli ai acestei cantități.', + ], + 'tax_class_id' => [ + 'label' => 'Clasă de taxe', + ], + 'shippable' => [ + 'label' => 'Expediabil', + ], + 'length_value' => [ + 'label' => 'Lungime', + ], + 'length_unit' => [ + 'label' => 'Unitate lungime', + ], + 'width_value' => [ + 'label' => 'Lățime', + ], + 'width_unit' => [ + 'label' => 'Unitate lățime', + ], + 'height_value' => [ + 'label' => 'Înălțime', + ], + 'height_unit' => [ + 'label' => 'Unitate înălțime', + ], + 'weight_value' => [ + 'label' => 'Greutate', + ], + 'weight_unit' => [ + 'label' => 'Unitate greutate', + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/relationmanagers.php b/packages/admin/resources/lang/ro/relationmanagers.php new file mode 100644 index 0000000000..100bdb5b0f --- /dev/null +++ b/packages/admin/resources/lang/ro/relationmanagers.php @@ -0,0 +1,285 @@ + [ + 'title' => 'Grupuri de clienți', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează grup de clienți', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'enabled' => [ + 'label' => 'Activat', + ], + 'starts_at' => [ + 'label' => 'Data de început', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + ], + 'visible' => [ + 'label' => 'Vizibil', + ], + 'purchasable' => [ + 'label' => 'Achiziționabil', + ], + ], + 'table' => [ + 'description' => 'Asociați grupuri de clienți la acest :type pentru a determina disponibilitatea.', + 'name' => [ + 'label' => 'Nume', + ], + 'enabled' => [ + 'label' => 'Activat', + ], + 'starts_at' => [ + 'label' => 'Data de început', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + ], + 'visible' => [ + 'label' => 'Vizibil', + ], + 'purchasable' => [ + 'label' => 'Achiziționabil', + ], + ], + ], + 'channels' => [ + 'title' => 'Canale', + 'actions' => [ + 'attach' => [ + 'label' => 'Programează alt canal', + ], + ], + 'form' => [ + 'enabled' => [ + 'label' => 'Activat', + 'helper_text_false' => 'Acest canal nu va fi activat chiar dacă există o dată de început.', + ], + 'starts_at' => [ + 'label' => 'Data de început', + 'helper_text' => 'Lăsați gol pentru a fi disponibil de la orice dată.', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + 'helper_text' => 'Lăsați gol pentru a fi disponibil pe termen nelimitat.', + ], + ], + 'table' => [ + 'description' => 'Determinați ce canale sunt activate și programați disponibilitatea.', + 'name' => [ + 'label' => 'Nume', + ], + 'enabled' => [ + 'label' => 'Activat', + ], + 'starts_at' => [ + 'label' => 'Data de început', + ], + 'ends_at' => [ + 'label' => 'Data de sfârșit', + ], + ], + ], + 'medias' => [ + 'title' => 'Media', + 'title_plural' => 'Media', + 'actions' => [ + 'attach' => [ + 'label' => 'Atașează media', + ], + 'create' => [ + 'label' => 'Creează media', + ], + 'detach' => [ + 'label' => 'Detașează', + ], + 'view' => [ + 'label' => 'Vezi', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'media' => [ + 'label' => 'Imagine', + ], + 'primary' => [ + 'label' => 'Principală', + ], + ], + 'table' => [ + 'image' => [ + 'label' => 'Imagine', + ], + 'file' => [ + 'label' => 'Fișier', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'primary' => [ + 'label' => 'Principală', + ], + ], + 'all_media_attached' => 'Nu există imagini de produs disponibile pentru atașare', + 'variant_description' => 'Atașați imaginile produsului la această variantă', + ], + 'urls' => [ + 'title' => 'URL', + 'title_plural' => 'URL-uri', + 'actions' => [ + 'create' => [ + 'label' => 'Creează URL', + ], + ], + 'filters' => [ + 'language_id' => [ + 'label' => 'Limbă', + ], + ], + 'form' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Implicit', + ], + 'language' => [ + 'label' => 'Limbă', + ], + ], + 'table' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Implicit', + ], + 'language' => [ + 'label' => 'Limbă', + ], + ], + ], + 'customer_group_pricing' => [ + 'title' => 'Prețuri pe grupuri de clienți', + 'title_plural' => 'Prețuri pe grupuri de clienți', + 'table' => [ + 'heading' => 'Prețuri pe grupuri de clienți', + 'description' => 'Asociați prețuri grupurilor de clienți pentru a determina prețul produsului.', + 'empty_state' => [ + 'label' => 'Nu există prețuri pe grupuri de clienți.', + 'description' => 'Creați un preț pentru un grup de clienți pentru a începe.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adaugă preț pentru grup de clienți', + 'modal' => [ + 'heading' => 'Creează preț pentru grup de clienți', + ], + ], + ], + ], + ], + 'pricing' => [ + 'title' => 'Prețuri', + 'title_plural' => 'Prețuri', + 'tab_name' => 'Reduceri cantitative', + 'table' => [ + 'heading' => 'Reduceri cantitative', + 'description' => 'Reduceți prețul când un client cumpără cantități mai mari.', + 'empty_state' => [ + 'label' => 'Nu există reduceri cantitative.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adaugă reducere cantitativă', + ], + ], + 'price' => [ + 'label' => 'Preț', + ], + 'customer_group' => [ + 'label' => 'Grup de clienți', + 'placeholder' => 'Toate grupurile de clienți', + ], + 'min_quantity' => [ + 'label' => 'Cantitate minimă', + ], + 'currency' => [ + 'label' => 'Monedă', + ], + ], + 'form' => [ + 'price' => [ + 'label' => 'Preț', + 'helper_text' => 'Prețul de achiziție, înainte de reduceri.', + ], + 'customer_group_id' => [ + 'label' => 'Grup de clienți', + 'placeholder' => 'Toate grupurile de clienți', + 'helper_text' => 'Selectați grupul de clienți pentru care se aplică acest preț.', + ], + 'min_quantity' => [ + 'label' => 'Cantitate minimă', + 'helper_text' => 'Selectați cantitatea minimă pentru care va fi disponibil acest preț.', + 'validation' => [ + 'unique' => 'Grupul de clienți și cantitatea minimă trebuie să fie unice.', + ], + ], + 'currency_id' => [ + 'label' => 'Monedă', + 'helper_text' => 'Selectați moneda pentru acest preț.', + ], + 'compare_price' => [ + 'label' => 'Preț de comparație', + 'helper_text' => 'Prețul original sau RRP, pentru comparație cu prețul de achiziție.', + ], + 'basePrices' => [ + 'title' => 'Prețuri', + 'form' => [ + 'price' => [ + 'label' => 'Preț', + 'helper_text' => 'Prețul de achiziție, înainte de reduceri.', + 'sync_price' => 'Prețul este sincronizat cu moneda implicită.', + ], + 'compare_price' => [ + 'label' => 'Preț de comparație', + 'helper_text' => 'Prețul original sau RRP, pentru comparație cu prețul de achiziție.', + ], + ], + 'tooltip' => 'Generat automat pe baza cursurilor de schimb valutar.', + ], + ], + ], + 'tax_rate_amounts' => [ + 'table' => [ + 'description' => '', + 'percentage' => [ + 'label' => 'Procent', + ], + 'tax_class' => [ + 'label' => 'Clasă de taxe', + ], + ], + ], + 'values' => [ + 'title' => 'Valori', + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'position' => [ + 'label' => 'Poziție', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/staff.php b/packages/admin/resources/lang/ro/staff.php new file mode 100644 index 0000000000..706be1c595 --- /dev/null +++ b/packages/admin/resources/lang/ro/staff.php @@ -0,0 +1,81 @@ + 'Personal', + + 'plural_label' => 'Personal', + + 'table' => [ + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'admin' => [ + 'badge' => 'Super administrator', + ], + ], + + 'form' => [ + 'first_name' => [ + 'label' => 'Prenume', + ], + 'last_name' => [ + 'label' => 'Nume', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Parolă', + 'hint' => 'Resetează parola', + ], + 'admin' => [ + 'label' => 'Super administrator', + 'helper' => 'Rolurile de super administrator nu pot fi schimbate în hub.', + ], + 'roles' => [ + 'label' => 'Roluri', + 'helper' => ':roles au acces complet', + ], + 'permissions' => [ + 'label' => 'Permisiuni', + ], + 'role' => [ + 'label' => 'Nume rol', + ], + ], + + 'action' => [ + 'acl' => [ + 'label' => 'Control acces', + ], + 'add-role' => [ + 'label' => 'Adaugă rol', + ], + 'delete-role' => [ + 'label' => 'Șterge rol', + 'heading' => 'Șterge rolul: :role', + ], + ], + + 'acl' => [ + 'title' => 'Control acces', + 'tooltip' => [ + 'roles-included' => 'Permisiunea este inclusă în următoarele roluri', + ], + 'notification' => [ + 'updated' => 'Actualizat', + 'error' => 'Eroare', + 'no-role' => 'Rolul nu este înregistrat în Lunar', + 'no-permission' => 'Permisiunea nu este înregistrată în Lunar', + 'no-role-permission' => 'Rolul și permisiunea nu sunt înregistrate în Lunar', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/tag.php b/packages/admin/resources/lang/ro/tag.php new file mode 100644 index 0000000000..a80ff40058 --- /dev/null +++ b/packages/admin/resources/lang/ro/tag.php @@ -0,0 +1,21 @@ + 'Etichetă', + + 'plural_label' => 'Etichete', + + 'table' => [ + 'value' => [ + 'label' => 'Valoare', + ], + ], + + 'form' => [ + 'value' => [ + 'label' => 'Valoare', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/tax.php b/packages/admin/resources/lang/ro/tax.php new file mode 100644 index 0000000000..fda4604923 --- /dev/null +++ b/packages/admin/resources/lang/ro/tax.php @@ -0,0 +1,9 @@ + 'Taxă', + + 'plural_label' => 'Taxe', + +]; diff --git a/packages/admin/resources/lang/ro/taxclass.php b/packages/admin/resources/lang/ro/taxclass.php new file mode 100644 index 0000000000..1afc3e5070 --- /dev/null +++ b/packages/admin/resources/lang/ro/taxclass.php @@ -0,0 +1,32 @@ + 'Clasă de taxe', + + 'plural_label' => 'Clase de taxe', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + 'delete' => [ + 'error' => [ + 'title' => 'Nu se poate șterge clasa de taxe', + 'body' => 'Această clasă de taxe are variante de produs asociate și nu poate fi ștearsă.', + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/taxrate.php b/packages/admin/resources/lang/ro/taxrate.php new file mode 100644 index 0000000000..946da62fa3 --- /dev/null +++ b/packages/admin/resources/lang/ro/taxrate.php @@ -0,0 +1,33 @@ + 'Rată de taxă', + + 'plural_label' => 'Rate de taxă', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'tax_zone' => [ + 'label' => 'Zonă de taxe', + ], + 'priority' => [ + 'label' => 'Prioritate', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'priority' => [ + 'label' => 'Prioritate', + ], + 'tax_zone_id' => [ + 'label' => 'Zonă de taxe', + ], + ], + +]; diff --git a/packages/admin/resources/lang/ro/taxzone.php b/packages/admin/resources/lang/ro/taxzone.php new file mode 100644 index 0000000000..32663db7b7 --- /dev/null +++ b/packages/admin/resources/lang/ro/taxzone.php @@ -0,0 +1,69 @@ + 'Zonă de taxe', + + 'plural_label' => 'Zone de taxe', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'zone_type' => [ + 'label' => 'Tip zonă', + ], + 'active' => [ + 'label' => 'Activă', + ], + 'default' => [ + 'label' => 'Implicită', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'zone_type' => [ + 'label' => 'Tip zonă', + 'options' => [ + 'country' => 'Limitează la țări', + 'states' => 'Limitează la județe', + 'postcodes' => 'Limitează la coduri poștale', + ], + ], + 'price_display' => [ + 'label' => 'Afișare preț', + 'options' => [ + 'include_tax' => 'Include taxe', + 'exclude_tax' => 'Exclude taxe', + ], + ], + 'active' => [ + 'label' => 'Activă', + ], + 'default' => [ + 'label' => 'Implicită', + ], + + 'zone_countries' => [ + 'label' => 'Țări', + ], + + 'zone_country' => [ + 'label' => 'Țară', + ], + + 'zone_states' => [ + 'label' => 'Județe', + ], + + 'zone_postcodes' => [ + 'label' => 'Coduri poștale', + 'helper' => 'Listați fiecare cod poștal pe o linie nouă. Suportă wildcard-uri precum NW*', + ], + + ], + +]; diff --git a/packages/admin/resources/lang/ro/user.php b/packages/admin/resources/lang/ro/user.php new file mode 100644 index 0000000000..74dae6252b --- /dev/null +++ b/packages/admin/resources/lang/ro/user.php @@ -0,0 +1,29 @@ + 'Utilizator', + + 'plural_label' => 'Utilizatori', + + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'email' => [ + 'label' => 'E-mail', + ], + ], + + 'form' => [ + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Parolă nouă', + ], + 'password_confirmation' => [ + 'label' => 'Confirmă parola nouă', + ], + ], +]; diff --git a/packages/admin/resources/lang/ro/widgets.php b/packages/admin/resources/lang/ro/widgets.php new file mode 100644 index 0000000000..bcc167190d --- /dev/null +++ b/packages/admin/resources/lang/ro/widgets.php @@ -0,0 +1,118 @@ + [ + 'orders' => [ + 'order_stats_overview' => [ + 'stat_one' => [ + 'label' => 'Comenzi astăzi', + 'increase' => 'Creștere de :percentage% față de :count ieri', + 'decrease' => 'Scădere de :percentage% față de :count ieri', + 'neutral' => 'Fără schimbare față de ieri', + ], + 'stat_two' => [ + 'label' => 'Comenzi în ultimele 7 zile', + 'increase' => 'Creștere de :percentage% față de :count în perioada anterioară', + 'decrease' => 'Scădere de :percentage% față de :count în perioada anterioară', + 'neutral' => 'Fără schimbare față de perioada anterioară', + ], + 'stat_three' => [ + 'label' => 'Comenzi în ultimele 30 de zile', + 'increase' => 'Creștere de :percentage% față de :count în perioada anterioară', + 'decrease' => 'Scădere de :percentage% față de :count în perioada anterioară', + 'neutral' => 'Fără schimbare față de perioada anterioară', + ], + 'stat_four' => [ + 'label' => 'Vânzări astăzi', + 'increase' => 'Creștere de :percentage% față de :total ieri', + 'decrease' => 'Scădere de :percentage% față de :total ieri', + 'neutral' => 'Fără schimbare față de ieri', + ], + 'stat_five' => [ + 'label' => 'Vânzări în ultimele 7 zile', + 'increase' => 'Creștere de :percentage% față de :total în perioada anterioară', + 'decrease' => 'Scădere de :percentage% față de :total în perioada anterioară', + 'neutral' => 'Fără schimbare față de perioada anterioară', + ], + 'stat_six' => [ + 'label' => 'Vânzări în ultimele 30 de zile', + 'increase' => 'Creștere de :percentage% față de :total în perioada anterioară', + 'decrease' => 'Scădere de :percentage% față de :total în perioada anterioară', + 'neutral' => 'Fără schimbare față de perioada anterioară', + ], + ], + 'order_totals_chart' => [ + 'heading' => 'Total comenzi în ultimul an', + 'series_one' => [ + 'label' => 'Această perioadă', + ], + 'series_two' => [ + 'label' => 'Perioada anterioară', + ], + 'yaxis' => [ + 'label' => 'Cifra de afaceri (:currency)', + ], + ], + 'order_sales_chart' => [ + 'heading' => 'Raport comenzi / vânzări', + 'series_one' => [ + 'label' => 'Comenzi', + ], + 'series_two' => [ + 'label' => 'Venit', + ], + 'yaxis' => [ + 'series_one' => [ + 'label' => 'Nr. comenzi', + ], + 'series_two' => [ + 'label' => 'Valoare totală', + ], + ], + ], + 'average_order_value' => [ + 'heading' => 'Valoarea medie a comenzii', + ], + 'new_returning_customers' => [ + 'heading' => 'Clienți noi vs. recurenți', + 'series_one' => [ + 'label' => 'Clienți noi', + ], + 'series_two' => [ + 'label' => 'Clienți recurenți', + ], + ], + 'popular_products' => [ + 'heading' => 'Cele mai vândute (ultimele 12 luni)', + 'description' => 'Aceste cifre se bazează pe numărul de apariții ale unui produs într-o comandă, nu pe cantitatea comandată.', + ], + 'latest_orders' => [ + 'heading' => 'Cele mai recente comenzi', + ], + ], + ], + 'customer' => [ + 'stats_overview' => [ + 'total_orders' => [ + 'label' => 'Total comenzi', + ], + 'avg_spend' => [ + 'label' => 'Cheltuială medie', + ], + 'total_spend' => [ + 'label' => 'Cheltuială totală', + ], + ], + ], + 'variant_switcher' => [ + 'label' => 'Schimbă varianta', + 'table' => [ + 'sku' => [ + 'label' => 'Cod stoc intern (SKU)', + ], + 'values' => [ + 'label' => 'Valori', + ], + ], + ], +]; diff --git a/packages/core/resources/lang/ro/base.php b/packages/core/resources/lang/ro/base.php new file mode 100644 index 0000000000..9b027dd150 --- /dev/null +++ b/packages/core/resources/lang/ro/base.php @@ -0,0 +1,9 @@ + [ + 'collection-titles' => [ + 'images' => 'Imagini', + ], + ], +]; diff --git a/packages/core/resources/lang/ro/exceptions.php b/packages/core/resources/lang/ro/exceptions.php new file mode 100644 index 0000000000..fcfe0427f8 --- /dev/null +++ b/packages/core/resources/lang/ro/exceptions.php @@ -0,0 +1,21 @@ + 'Modelul ":class" nu implementează interfața de achiziționare.', + 'cart_line_id_mismatch' => 'Această linie din coș nu aparține acestui coș', + 'invalid_cart_line_quantity' => 'Cantitatea așteptată trebuie să fie cel puțin 1, s-a găsit: :quantity.', + 'maximum_cart_line_quantity' => 'Cantitatea nu poate depăși :quantity.', + 'carts.invalid_action' => 'Acțiunea pentru coș este invalidă', + 'carts.shipping_missing' => 'Este necesară o adresă de livrare', + 'carts.billing_missing' => 'Este necesară o adresă de facturare', + 'carts.billing_incomplete' => 'Adresa de facturare este incompletă', + 'carts.order_exists' => 'Există deja o comandă pentru acest coș', + 'carts.shipping_option_missing' => 'Opțiune de livrare lipsă', + 'missing_currency_price' => 'Nu există un preț pentru moneda ":currency"', + 'minimum_quantity' => 'Trebuie să adăugați cel puțin :quantity articole.', + 'quantity_increment' => 'Cantitatea :quantity trebuie să fie în multipli de :increment', + 'fieldtype_missing' => 'FieldType ":class" nu există', + 'invalid_fieldtype' => 'Clasa ":class" nu implementează interfața FieldType.', + 'discounts.invalid_type' => 'Colecția trebuie să conțină doar ":expected", s-a găsit ":actual"', + 'disallow_multiple_cart_orders' => 'Un coș poate avea asociată doar o singură comandă.', +]; diff --git a/packages/table-rate-shipping/resources/lang/ro/plugin.php b/packages/table-rate-shipping/resources/lang/ro/plugin.php new file mode 100644 index 0000000000..4e0e298b96 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/ro/plugin.php @@ -0,0 +1,7 @@ + [ + 'group' => 'Livrare', + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/ro/relationmanagers.php b/packages/table-rate-shipping/resources/lang/ro/relationmanagers.php new file mode 100644 index 0000000000..d80c48d523 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/ro/relationmanagers.php @@ -0,0 +1,80 @@ + [ + 'customer_groups' => [ + 'description' => 'Asociați grupuri de clienți acestei metode de livrare pentru a-i stabili disponibilitatea.', + ], + ], + 'shipping_rates' => [ + 'title_plural' => 'Tarife de livrare', + 'actions' => [ + 'create' => [ + 'label' => 'Creează tarif de livrare', + ], + ], + 'notices' => [ + 'prices_incl_tax' => 'Toate prețurile includ taxa, care va fi luată în calcul la determinarea cheltuielii minime.', + 'prices_excl_tax' => 'Toate prețurile exclud taxa; cheltuiala minimă va fi calculată pe baza subtotalului coșului.', + ], + 'form' => [ + 'shipping_method_id' => [ + 'label' => 'Metodă de livrare', + ], + 'price' => [ + 'label' => 'Preț', + ], + 'prices' => [ + 'label' => 'Transe de preț', + 'repeater' => [ + 'customer_group_id' => [ + 'label' => 'Grup de clienți', + 'placeholder' => 'Oricare', + ], + 'currency_id' => [ + 'label' => 'Monedă', + ], + 'min_spend' => [ + 'label' => 'Cheltuială min.', + ], + 'min_weight' => [ + 'label' => 'Greutate min.', + ], + 'price' => [ + 'label' => 'Preț', + ], + ], + ], + ], + 'table' => [ + 'shipping_method' => [ + 'label' => 'Metodă de livrare', + ], + 'price' => [ + 'label' => 'Preț', + ], + 'price_breaks_count' => [ + 'label' => 'Transe de preț', + ], + ], + ], + 'exclusions' => [ + 'title_plural' => 'Excluderi de livrare', + 'form' => [ + 'purchasable' => [ + 'label' => 'Produs', + ], + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adaugă listă de excluderi pentru livrare', + ], + 'attach' => [ + 'label' => 'Adaugă listă de excluderi', + ], + 'detach' => [ + 'label' => 'Elimină', + ], + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/ro/shippingexclusionlist.php b/packages/table-rate-shipping/resources/lang/ro/shippingexclusionlist.php new file mode 100644 index 0000000000..2b082e3c7f --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/ro/shippingexclusionlist.php @@ -0,0 +1,19 @@ + 'Listă de excluderi pentru livrare', + 'label_plural' => 'Liste de excluderi pentru livrare', + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'exclusions_count' => [ + 'label' => 'Nr. produse', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/ro/shippingmethod.php b/packages/table-rate-shipping/resources/lang/ro/shippingmethod.php new file mode 100644 index 0000000000..be456ced2a --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/ro/shippingmethod.php @@ -0,0 +1,58 @@ + 'Metode de livrare', + 'label' => 'Metodă de livrare', + 'form' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'description' => [ + 'label' => 'Descriere', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'cutoff' => [ + 'label' => 'Termen limită', + ], + 'charge_by' => [ + 'label' => 'Taxare după', + 'options' => [ + 'cart_total' => 'Total coș', + 'weight' => 'Greutate', + ], + ], + 'driver' => [ + 'label' => 'Tip', + 'options' => [ + 'ship-by' => 'Standard', + 'collection' => 'Ridicare', + ], + ], + 'stock_available' => [ + 'label' => 'Stocul tuturor articolelor din coș trebuie să fie disponibil', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'code' => [ + 'label' => 'Cod', + ], + 'driver' => [ + 'label' => 'Tip', + 'options' => [ + 'ship-by' => 'Standard', + 'collection' => 'Ridicare', + ], + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilitate', + 'customer_groups' => 'Această metodă de livrare este momentan indisponibilă pentru toate grupurile de clienți.', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/ro/shippingzone.php b/packages/table-rate-shipping/resources/lang/ro/shippingzone.php new file mode 100644 index 0000000000..15adb01a7c --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/ro/shippingzone.php @@ -0,0 +1,50 @@ + 'Zonă de livrare', + 'label_plural' => 'Zone de livrare', + 'form' => [ + 'unrestricted' => [ + 'content' => 'Această zonă de livrare nu are restricții și va fi disponibilă pentru toți clienții la finalizarea comenzii.', + ], + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'options' => [ + 'unrestricted' => 'Fără restricții', + 'countries' => 'Limitat la țări', + 'states' => 'Limitat la state / provincii', + 'postcodes' => 'Limitat la coduri poștale', + ], + ], + 'country' => [ + 'label' => 'Țară', + ], + 'states' => [ + 'label' => 'Județe', + ], + 'countries' => [ + 'label' => 'Țări', + ], + 'postcodes' => [ + 'label' => 'Coduri poștale', + 'helper' => 'Listați fiecare cod poștal pe o linie separată. Suportă wildcard-uri precum NW*', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nume', + ], + 'type' => [ + 'label' => 'Tip', + 'options' => [ + 'unrestricted' => 'Fără restricții', + 'countries' => 'Limitat la țări', + 'states' => 'Limitat la state / provincii', + 'postcodes' => 'Limitat la coduri poștale', + ], + ], + ], +]; From 8cb5621f9b52de12a9c3f94b2869c9f0f8847950 Mon Sep 17 00:00:00 2001 From: Huncsuga <91792987+Huncsuga@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:50:34 +0200 Subject: [PATCH 11/50] Add Hungarian translations (#2308) I added Hungarian translations to the package. --------- Co-authored-by: Glenn Jacobs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/admin/resources/lang/hu/actions.php | 52 +++ packages/admin/resources/lang/hu/activity.php | 29 ++ packages/admin/resources/lang/hu/address.php | 99 +++++ .../admin/resources/lang/hu/attribute.php | 55 +++ .../resources/lang/hu/attributegroup.php | 46 +++ packages/admin/resources/lang/hu/auth.php | 32 ++ packages/admin/resources/lang/hu/brand.php | 75 ++++ packages/admin/resources/lang/hu/channel.php | 39 ++ .../admin/resources/lang/hu/collection.php | 45 +++ .../resources/lang/hu/collectiongroup.php | 37 ++ .../admin/resources/lang/hu/components.php | 111 ++++++ packages/admin/resources/lang/hu/currency.php | 58 +++ packages/admin/resources/lang/hu/customer.php | 63 ++++ .../admin/resources/lang/hu/customergroup.php | 40 ++ packages/admin/resources/lang/hu/discount.php | 353 ++++++++++++++++++ .../admin/resources/lang/hu/fieldtypes.php | 72 ++++ packages/admin/resources/lang/hu/global.php | 12 + packages/admin/resources/lang/hu/language.php | 33 ++ packages/admin/resources/lang/hu/order.php | 305 +++++++++++++++ packages/admin/resources/lang/hu/product.php | 131 +++++++ .../admin/resources/lang/hu/productoption.php | 127 +++++++ .../admin/resources/lang/hu/producttype.php | 52 +++ .../resources/lang/hu/productvariant.php | 105 ++++++ .../resources/lang/hu/relationmanagers.php | 285 ++++++++++++++ packages/admin/resources/lang/hu/staff.php | 81 ++++ packages/admin/resources/lang/hu/tag.php | 21 ++ packages/admin/resources/lang/hu/tax.php | 9 + packages/admin/resources/lang/hu/taxclass.php | 32 ++ packages/admin/resources/lang/hu/taxrate.php | 33 ++ packages/admin/resources/lang/hu/taxzone.php | 69 ++++ packages/admin/resources/lang/hu/user.php | 29 ++ packages/admin/resources/lang/hu/widgets.php | 118 ++++++ packages/core/resources/lang/hu/base.php | 9 + .../core/resources/lang/hu/exceptions.php | 21 ++ .../resources/lang/hu/plugin.php | 7 + .../resources/lang/hu/relationmanagers.php | 80 ++++ .../lang/hu/shippingexclusionlist.php | 19 + .../resources/lang/hu/shippingmethod.php | 58 +++ .../resources/lang/hu/shippingzone.php | 50 +++ 39 files changed, 2892 insertions(+) create mode 100644 packages/admin/resources/lang/hu/actions.php create mode 100644 packages/admin/resources/lang/hu/activity.php create mode 100644 packages/admin/resources/lang/hu/address.php create mode 100644 packages/admin/resources/lang/hu/attribute.php create mode 100644 packages/admin/resources/lang/hu/attributegroup.php create mode 100644 packages/admin/resources/lang/hu/auth.php create mode 100644 packages/admin/resources/lang/hu/brand.php create mode 100644 packages/admin/resources/lang/hu/channel.php create mode 100644 packages/admin/resources/lang/hu/collection.php create mode 100644 packages/admin/resources/lang/hu/collectiongroup.php create mode 100644 packages/admin/resources/lang/hu/components.php create mode 100644 packages/admin/resources/lang/hu/currency.php create mode 100644 packages/admin/resources/lang/hu/customer.php create mode 100644 packages/admin/resources/lang/hu/customergroup.php create mode 100644 packages/admin/resources/lang/hu/discount.php create mode 100644 packages/admin/resources/lang/hu/fieldtypes.php create mode 100644 packages/admin/resources/lang/hu/global.php create mode 100644 packages/admin/resources/lang/hu/language.php create mode 100644 packages/admin/resources/lang/hu/order.php create mode 100644 packages/admin/resources/lang/hu/product.php create mode 100644 packages/admin/resources/lang/hu/productoption.php create mode 100644 packages/admin/resources/lang/hu/producttype.php create mode 100644 packages/admin/resources/lang/hu/productvariant.php create mode 100644 packages/admin/resources/lang/hu/relationmanagers.php create mode 100644 packages/admin/resources/lang/hu/staff.php create mode 100644 packages/admin/resources/lang/hu/tag.php create mode 100644 packages/admin/resources/lang/hu/tax.php create mode 100644 packages/admin/resources/lang/hu/taxclass.php create mode 100644 packages/admin/resources/lang/hu/taxrate.php create mode 100644 packages/admin/resources/lang/hu/taxzone.php create mode 100644 packages/admin/resources/lang/hu/user.php create mode 100644 packages/admin/resources/lang/hu/widgets.php create mode 100644 packages/core/resources/lang/hu/base.php create mode 100644 packages/core/resources/lang/hu/exceptions.php create mode 100644 packages/table-rate-shipping/resources/lang/hu/plugin.php create mode 100644 packages/table-rate-shipping/resources/lang/hu/relationmanagers.php create mode 100644 packages/table-rate-shipping/resources/lang/hu/shippingexclusionlist.php create mode 100644 packages/table-rate-shipping/resources/lang/hu/shippingmethod.php create mode 100644 packages/table-rate-shipping/resources/lang/hu/shippingzone.php diff --git a/packages/admin/resources/lang/hu/actions.php b/packages/admin/resources/lang/hu/actions.php new file mode 100644 index 0000000000..167c67bc18 --- /dev/null +++ b/packages/admin/resources/lang/hu/actions.php @@ -0,0 +1,52 @@ + [ + 'create_root' => [ + 'label' => 'Gyökérgyűjtemény létrehozása', + ], + 'create_child' => [ + 'label' => 'Algyűjtemény létrehozása', + ], + 'move' => [ + 'label' => 'Gyűjtemény áthelyezése', + ], + 'delete' => [ + 'label' => 'Törlés', + 'notifications' => [ + 'cannot_delete' => [ + 'title' => 'Nem törölhető', + 'body' => 'Ennek a gyűjteménynek vannak algyűjteményei, ezért nem törölhető.', + ], + ], + ], + ], + 'orders' => [ + 'update_status' => [ + 'label' => 'Állapot frissítése', + 'wizard' => [ + 'step_one' => [ + 'label' => 'Állapot', + ], + 'step_two' => [ + 'label' => 'E-mailek és értesítések', + 'no_mailers' => 'Ehhez az állapothoz nincs elérhető e-mail sablon.', + ], + 'step_three' => [ + 'label' => 'Előnézet és mentés', + 'no_mailers' => 'Nincs kiválasztva e-mail előnézethez.', + ], + ], + 'notification' => [ + 'label' => 'A rendelés állapota frissítve', + ], + 'billing_email' => [ + 'label' => 'Számlázási e-mail', + ], + 'shipping_email' => [ + 'label' => 'Szállítási e-mail', + ], + ], + + ], +]; diff --git a/packages/admin/resources/lang/hu/activity.php b/packages/admin/resources/lang/hu/activity.php new file mode 100644 index 0000000000..d05ee82432 --- /dev/null +++ b/packages/admin/resources/lang/hu/activity.php @@ -0,0 +1,29 @@ + 'Tevékenység', + + 'plural_label' => 'Tevékenységek', + + 'table' => [ + 'subject' => 'Tárgy', + 'description' => 'Leírás', + 'log' => 'Napló', + 'logged_at' => 'Rögzítés ideje', + 'event' => 'Esemény', + 'logged_from' => 'Naplózva innen', + 'logged_until' => 'Naplózva eddig', + ], + + 'form' => [ + 'causer_type' => 'Okozó típusa', + 'causer_id' => 'Okozó azonosító', + 'subject_type' => 'Tárgy típusa', + 'subject_id' => 'Tárgy azonosító', + 'description' => 'Leírás', + 'attributes' => 'Attribútumok', + 'old' => 'Régi', + ], + +]; diff --git a/packages/admin/resources/lang/hu/address.php b/packages/admin/resources/lang/hu/address.php new file mode 100644 index 0000000000..a6b2814730 --- /dev/null +++ b/packages/admin/resources/lang/hu/address.php @@ -0,0 +1,99 @@ + 'Cím', + + 'plural_label' => 'Címek', + + 'table' => [ + 'title' => [ + 'label' => 'Cím', + ], + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'company_name' => [ + 'label' => 'Cégnév', + ], + 'tax_identifier' => [ + 'label' => 'Adóazonosító', + ], + 'line_one' => [ + 'label' => 'Utca, házszám', + ], + 'line_two' => [ + 'label' => 'Emelet, ajtó', + ], + 'line_three' => [ + 'label' => 'Egyéb címadat', + ], + 'city' => [ + 'label' => 'Város', + ], + 'country_id' => [ + 'label' => 'Ország', + ], + 'state' => [ + 'label' => 'Megye', + ], + 'postcode' => [ + 'label' => 'Irányítószám', + ], + 'contact_email' => [ + 'label' => 'Kapcsolattartó e-mail', + ], + 'contact_phone' => [ + 'label' => 'Kapcsolattartó telefonszám', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Cím', + ], + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'company_name' => [ + 'label' => 'Cégnév', + ], + 'tax_identifier' => [ + 'label' => 'Adóazonosító', + ], + 'line_one' => [ + 'label' => 'Utca, házszám', + ], + 'line_two' => [ + 'label' => 'Emelet, ajtó', + ], + 'line_three' => [ + 'label' => 'Egyéb címadat', + ], + 'city' => [ + 'label' => 'Város', + ], + 'country_id' => [ + 'label' => 'Ország', + ], + 'state' => [ + 'label' => 'Megye', + ], + 'postcode' => [ + 'label' => 'Irányítószám', + ], + 'contact_email' => [ + 'label' => 'Kapcsolattartó e-mail', + ], + 'contact_phone' => [ + 'label' => 'Kapcsolattartó telefonszám', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/attribute.php b/packages/admin/resources/lang/hu/attribute.php new file mode 100644 index 0000000000..7d9c496f2a --- /dev/null +++ b/packages/admin/resources/lang/hu/attribute.php @@ -0,0 +1,55 @@ + 'Attribútum', + + 'plural_label' => 'Attribútumok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'description' => [ + 'label' => 'Leírás', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'type' => [ + 'label' => 'Típus', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Típus', + ], + 'name' => [ + 'label' => 'Név', + ], + 'description' => [ + 'label' => 'Leírás', + 'helper' => 'Használja a bejegyzés alatt a súgó szöveg megjelenítéséhez.', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'searchable' => [ + 'label' => 'Kereshető', + ], + 'filterable' => [ + 'label' => 'Szűrhető', + ], + 'required' => [ + 'label' => 'Kötelező', + ], + 'type' => [ + 'label' => 'Típus', + ], + 'validation_rules' => [ + 'label' => 'Érvényesítési szabályok', + 'helper' => 'Szabályok az attribútum mezőhöz, pl.: min:1|max:10|...', + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/attributegroup.php b/packages/admin/resources/lang/hu/attributegroup.php new file mode 100644 index 0000000000..110d543b6c --- /dev/null +++ b/packages/admin/resources/lang/hu/attributegroup.php @@ -0,0 +1,46 @@ + 'Attribútumcsoport', + + 'plural_label' => 'Attribútumcsoportok', + + 'table' => [ + 'attributable_type' => [ + 'label' => 'Típus', + ], + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'position' => [ + 'label' => 'Pozíció', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Típus', + ], + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'position' => [ + 'label' => 'Pozíció', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Ez az attribútumcsoport nem törölhető, mivel vannak hozzá kapcsolódó attribútumok.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/auth.php b/packages/admin/resources/lang/hu/auth.php new file mode 100644 index 0000000000..47a96206bb --- /dev/null +++ b/packages/admin/resources/lang/hu/auth.php @@ -0,0 +1,32 @@ + 'Admin', + 'roles.admin.description' => 'Teljes hozzáféréssel rendelkező admin', + 'roles.staff.label' => 'Munkatárs', + 'roles.staff.description' => 'Alapvető jogosultságokkal rendelkező munkatárs', + /** + * Permissions. + */ + 'permissions.settings.label' => 'Beállítások', + 'permissions.settings.description' => 'Hozzáférést ad a Hub beállítási területéhez', + 'permissions.settings:core.label' => 'Alapbeállítások', + 'permissions.settings:core.description' => 'Hozzáférés az alapvető áruházbeállításokhoz (csatornák, nyelvek, pénznemek stb.)', + 'permissions.settings:manage-staff.label' => 'Munkatársak kezelése', + 'permissions.settings:manage-staff.description' => 'Engedélyezi más munkatársak adatainak szerkesztését', + 'permissions.settings:manage-attributes.label' => 'Attribútumok kezelése', + 'permissions.settings:manage-attributes.description' => 'Engedélyezi további attribútumok létrehozását és szerkesztését', + 'permissions.catalog:manage-products.label' => 'Termékek kezelése', + 'permissions.catalog:manage-products.description' => 'Engedélyezi a termékek, terméktípusok és márkák szerkesztését', + 'permissions.catalog:manage-collections.label' => 'Kollekciók kezelése', + 'permissions.catalog:manage-collections.description' => 'Engedélyezi a kollekciók és azok csoportjainak szerkesztését', + 'permissions.sales:manage-orders.label' => 'Rendelések kezelése', + 'permissions.sales:manage-orders.description' => 'Engedélyezi a rendelések kezelését', + 'permissions.sales:manage-customers.label' => 'Vásárlók kezelése', + 'permissions.sales:manage-customers.description' => 'Engedélyezi a vásárlók kezelését', + 'permissions.sales:manage-discounts.label' => 'Kedvezmények kezelése', + 'permissions.sales:manage-discounts.description' => 'Engedélyezi a kedvezmények kezelését', +]; diff --git a/packages/admin/resources/lang/hu/brand.php b/packages/admin/resources/lang/hu/brand.php new file mode 100644 index 0000000000..529f213ce6 --- /dev/null +++ b/packages/admin/resources/lang/hu/brand.php @@ -0,0 +1,75 @@ + 'Márka', + + 'plural_label' => 'Márkák', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'products_count' => [ + 'label' => 'Termékek száma', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'A márka nem törölhető, mert termékek vannak hozzárendelve.', + ], + ], + ], + 'pages' => [ + 'edit' => [ + 'title' => 'Alapvető információk', + ], + 'products' => [ + 'label' => 'Termékek', + 'actions' => [ + 'attach' => [ + 'label' => 'Termék hozzáadása', + 'form' => [ + 'record_id' => [ + 'label' => 'Termék', + ], + ], + 'notification' => [ + 'success' => 'Termék hozzárendelve a márkához', + ], + ], + 'detach' => [ + 'notification' => [ + 'success' => 'Termék leválasztva.', + ], + ], + ], + ], + 'collections' => [ + 'label' => 'Gyűjtemények', + 'table' => [ + 'header_actions' => [ + 'attach' => [ + 'record_select' => [ + 'placeholder' => 'Válassz gyűjteményt', + ], + ], + ], + ], + 'actions' => [ + 'attach' => [ + 'label' => 'Gyűjtemény hozzárendelése', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/channel.php b/packages/admin/resources/lang/hu/channel.php new file mode 100644 index 0000000000..7d5ae05f65 --- /dev/null +++ b/packages/admin/resources/lang/hu/channel.php @@ -0,0 +1,39 @@ + 'Csatorna', + + 'plural_label' => 'Csatornák', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/collection.php b/packages/admin/resources/lang/hu/collection.php new file mode 100644 index 0000000000..05f845d7c0 --- /dev/null +++ b/packages/admin/resources/lang/hu/collection.php @@ -0,0 +1,45 @@ + 'Gyűjtemény', + + 'plural_label' => 'Gyűjtemények', + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + ], + + 'pages' => [ + 'children' => [ + 'label' => 'Algyűjtemények', + 'actions' => [ + 'create_child' => [ + 'label' => 'Algyűjtemény létrehozása', + ], + ], + 'table' => [ + 'children_count' => [ + 'label' => 'Gyermekek száma', + ], + 'name' => [ + 'label' => 'Név', + ], + ], + ], + 'edit' => [ + 'label' => 'Alapvető információk', + ], + 'products' => [ + 'label' => 'Termékek', + 'actions' => [ + 'attach' => [ + 'label' => 'Termék csatolása', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/collectiongroup.php b/packages/admin/resources/lang/hu/collectiongroup.php new file mode 100644 index 0000000000..3f9c625647 --- /dev/null +++ b/packages/admin/resources/lang/hu/collectiongroup.php @@ -0,0 +1,37 @@ + 'Gyűjteménycsoport', + + 'plural_label' => 'Gyűjteménycsoportok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'collections_count' => [ + 'label' => 'Gyűjtemények száma', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Ez a gyűjteménycsoport nem törölhető, mert gyűjtemények kapcsolódnak hozzá.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/components.php b/packages/admin/resources/lang/hu/components.php new file mode 100644 index 0000000000..cf52e142a2 --- /dev/null +++ b/packages/admin/resources/lang/hu/components.php @@ -0,0 +1,111 @@ + [ + 'notification' => [ + 'updated' => 'Címkék frissítve', + ], + ], + + 'activity-log' => [ + + 'input' => [ + 'placeholder' => 'Megjegyzés hozzáadása', + ], + + 'action' => [ + 'add-comment' => 'Megjegyzés hozzáadása', + ], + + 'system' => 'Rendszer', + + 'partials' => [ + 'orders' => [ + 'order_created' => 'Rendelés létrehozva', + + 'status_change' => 'Státusz frissítve', + + 'capture' => 'Fizetés: :amount, kártya utolsó négy számjegye: :last_four', + + 'authorized' => 'Engedélyezve: :amount, kártya utolsó négy számjegye: :last_four', + + 'refund' => 'Visszatérítés: :amount, kártya utolsó négy számjegye: :last_four', + + 'address' => ':type frissítve', + + 'billingAddress' => 'Számlázási cím', + + 'shippingAddress' => 'Szállítási cím', + ], + + 'update' => [ + 'updated' => ':model frissítve', + ], + + 'create' => [ + 'created' => ':model létrehozva', + ], + + 'tags' => [ + 'updated' => 'Címkék frissítve', + 'added' => 'Hozzáadva', + 'removed' => 'Eltávolítva', + ], + ], + + 'notification' => [ + 'comment_added' => 'Megjegyzés hozzáadva', + ], + + ], + + 'forms' => [ + 'youtube' => [ + 'helperText' => 'Add meg a YouTube videó azonosítóját. pl.: dQw4w9WgXcQ', + ], + ], + + 'collection-tree-view' => [ + 'actions' => [ + 'move' => [ + 'form' => [ + 'target_id' => [ + 'label' => 'Szülő gyűjtemény', + ], + ], + ], + ], + 'notifications' => [ + 'collections-reordered' => [ + 'success' => 'Gyűjtemények át-rendezve', + ], + 'node-expanded' => [ + 'danger' => 'Nem lehet betölteni a gyűjteményeket', + ], + 'delete' => [ + 'danger' => 'Nem lehet törölni a gyűjteményt', + ], + ], + ], + + 'product-options-list' => [ + 'add-option' => [ + 'label' => 'Opció hozzáadása', + ], + 'delete-option' => [ + 'label' => 'Opció törlése', + ], + 'remove-shared-option' => [ + 'label' => 'Megosztott opció eltávolítása', + ], + 'add-value' => [ + 'label' => 'Új érték hozzáadása', + ], + 'name' => [ + 'label' => 'Név', + ], + 'values' => [ + 'label' => 'Értékek', + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/currency.php b/packages/admin/resources/lang/hu/currency.php new file mode 100644 index 0000000000..757aa1b268 --- /dev/null +++ b/packages/admin/resources/lang/hu/currency.php @@ -0,0 +1,58 @@ + 'Pénznem', + + 'plural_label' => 'Pénznemek', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'exchange_rate' => [ + 'label' => 'Átváltási árfolyam', + ], + 'decimal_places' => [ + 'label' => 'Tizedesjegyek', + ], + 'enabled' => [ + 'label' => 'Engedélyezve', + ], + 'sync_prices' => [ + 'label' => 'Árak szinkronizálása', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'exchange_rate' => [ + 'label' => 'Átváltási árfolyam', + ], + 'decimal_places' => [ + 'label' => 'Tizedesjegyek', + ], + 'enabled' => [ + 'label' => 'Engedélyezve', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + 'sync_prices' => [ + 'label' => 'Árak szinkronizálása', + 'helper_text' => 'Tartsa ennek a pénznemnek az árait szinkronban az alapértelmezett pénznem áraival.', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/customer.php b/packages/admin/resources/lang/hu/customer.php new file mode 100644 index 0000000000..7577e8fa4a --- /dev/null +++ b/packages/admin/resources/lang/hu/customer.php @@ -0,0 +1,63 @@ + 'Vásárló', + + 'plural_label' => 'Vásárlók', + + 'table' => [ + 'full_name' => [ + 'label' => 'Név', + ], + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'title' => [ + 'label' => 'Cím', + ], + 'company_name' => [ + 'label' => 'Cégnév', + ], + 'tax_identifier' => [ + 'label' => 'Adóazonosító', + ], + 'account_reference' => [ + 'label' => 'Ügyfélszám', + ], + 'new' => [ + 'label' => 'Új', + ], + 'returning' => [ + 'label' => 'Visszatérő', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Cím', + ], + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'company_name' => [ + 'label' => 'Cégnév', + ], + 'account_ref' => [ + 'label' => 'Ügyfélszám', + ], + 'tax_identifier' => [ + 'label' => 'Adóazonosító', + ], + 'customer_groups' => [ + 'label' => 'Vásárlói csoportok', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/customergroup.php b/packages/admin/resources/lang/hu/customergroup.php new file mode 100644 index 0000000000..ea1d9c7999 --- /dev/null +++ b/packages/admin/resources/lang/hu/customergroup.php @@ -0,0 +1,40 @@ + 'Vásárlói csoport', + + 'plural_label' => 'Vásárlói csoportok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Ez a vásárlói csoport nem törölhető, mert vásárlók kapcsolódnak hozzá.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/discount.php b/packages/admin/resources/lang/hu/discount.php new file mode 100644 index 0000000000..8f3d606058 --- /dev/null +++ b/packages/admin/resources/lang/hu/discount.php @@ -0,0 +1,353 @@ + 'Kedvezmények', + 'label' => 'Kedvezmény', + 'form' => [ + 'conditions' => [ + 'heading' => 'Feltételek', + ], + 'buy_x_get_y' => [ + 'heading' => 'Vásárolj X-et, kapj Y-t', + ], + 'amount_off' => [ + 'heading' => 'Összeg alapú kedvezmény', + ], + 'name' => [ + 'label' => 'Név', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + ], + 'priority' => [ + 'label' => 'Prioritás', + 'helper_text' => 'A magasabb prioritású kedvezmények kerülnek először alkalmazásra.', + 'options' => [ + 'low' => [ + 'label' => 'Alacsony', + ], + 'medium' => [ + 'label' => 'Közepes', + ], + 'high' => [ + 'label' => 'Magas', + ], + ], + ], + 'stop' => [ + 'label' => 'Más kedvezmények alkalmazásának megakadályozása ez után', + ], + 'coupon' => [ + 'label' => 'Kupon', + 'helper_text' => 'Add meg a kuponkódot, amely szükséges a kedvezmény alkalmazásához; üresen hagyva automatikusan alkalmazódik.', + ], + 'max_uses' => [ + 'label' => 'Max felhasználás', + 'helper_text' => 'Hagyd üresen a korlátlan felhasználáshoz.', + ], + 'max_uses_per_user' => [ + 'label' => 'Max felhasználás felhasználónként', + 'helper_text' => 'Hagyd üresen a korlátlan felhasználáshoz.', + ], + 'minimum_cart_amount' => [ + 'label' => 'Minimum kosárérték', + ], + 'min_qty' => [ + 'label' => 'Termék mennyisége', + 'helper_text' => 'Add meg, hány jogosult termék szükséges a kedvezmény érvényesítéséhez.', + ], + 'reward_qty' => [ + 'label' => 'Ingyenes tételek száma', + 'helper_text' => 'Termékenként hány darab jár ingyen.', + ], + 'max_reward_qty' => [ + 'label' => 'Maximális jutalom mennyiség', + 'helper_text' => 'A maximális termékmennyiség, amely kedvezményben részesíthető, függetlenül a feltételektől.', + ], + 'automatic_rewards' => [ + 'label' => 'Jutalmak automatikus hozzáadása', + 'helper_text' => 'Kapcsolja be, hogy a jutalomtermékek automatikusan hozzáadódjanak, ha nincsenek a kosárban.', + ], + 'fixed_value' => [ + 'label' => 'Rögzített érték', + ], + 'percentage' => [ + 'label' => 'Százalék', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'status' => [ + 'label' => 'Státusz', + \Lunar\Models\Discount::ACTIVE => [ + 'label' => 'Aktív', + ], + \Lunar\Models\Discount::PENDING => [ + 'label' => 'Függőben', + ], + \Lunar\Models\Discount::EXPIRED => [ + 'label' => 'Lejárt', + ], + \Lunar\Models\Discount::SCHEDULED => [ + 'label' => 'Ütemezett', + ], + ], + 'type' => [ + 'label' => 'Típus', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + ], + 'created_at' => [ + 'label' => 'Létrehozva', + ], + 'coupon' => [ + 'label' => 'Kupon', + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Elérhetőség', + ], + 'edit' => [ + 'title' => 'Alapinformációk', + ], + 'limitations' => [ + 'label' => 'Korlátozások', + ], + ], + 'relationmanagers' => [ + 'collections' => [ + 'title' => 'Gyűjtemények', + 'description' => 'Válaszd ki, mely gyűjteményekre legyen érvényes ez a kedvezmény.', + 'actions' => [ + 'attach' => [ + 'label' => 'Gyűjtemény csatolása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + 'customers' => [ + 'title' => 'Vásárlók', + 'description' => 'Válaszd ki, mely vásárlókra legyen érvényes ez a kedvezmény.', + 'actions' => [ + 'attach' => [ + 'label' => 'Vásárló csatolása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + ], + ], + 'brands' => [ + 'title' => 'Márkák', + 'description' => 'Válaszd ki, mely márkákra legyen érvényes ez a kedvezmény.', + 'actions' => [ + 'attach' => [ + 'label' => 'Márka csatolása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + 'products' => [ + 'title' => 'Termékek', + 'description' => 'Válaszd ki, mely termékekre legyen érvényes ez a kedvezmény.', + 'actions' => [ + 'attach' => [ + 'label' => 'Termék hozzáadása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + 'rewards' => [ + 'title' => 'Jutalmak', + 'description' => 'Válaszd ki, mely termékek kapnak kedvezményt, ha a kosárban megtalálhatók és a feltételek teljesülnek.', + 'actions' => [ + 'attach' => [ + 'label' => 'Jutalom hozzáadása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + 'conditions' => [ + 'title' => 'Feltételek', + 'description' => 'Válaszd ki azokat a feltételeket, amelyek szükségesek a kedvezmény alkalmazásához.', + 'actions' => [ + 'attach' => [ + 'label' => 'Feltétel hozzáadása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + 'productvariants' => [ + 'title' => 'Termékváltozatok', + 'description' => 'Válaszd ki, mely termékváltozatokra legyen érvényes ez a kedvezmény.', + 'actions' => [ + 'attach' => [ + 'label' => 'Termékváltozat hozzáadása', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'values' => [ + 'label' => 'Opció(k)', + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Korlátozás', + ], + 'exclusion' => [ + 'label' => 'Kizárás', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/fieldtypes.php b/packages/admin/resources/lang/hu/fieldtypes.php new file mode 100644 index 0000000000..0d9fde633b --- /dev/null +++ b/packages/admin/resources/lang/hu/fieldtypes.php @@ -0,0 +1,72 @@ + [ + 'label' => 'Lenyíló lista', + 'form' => [ + 'lookups' => [ + 'label' => 'Értékkészlet', + 'key_label' => 'Címke', + 'value_label' => 'Érték', + ], + ], + ], + 'listfield' => [ + 'label' => 'Lista mező', + ], + 'text' => [ + 'label' => 'Szöveg', + 'form' => [ + 'richtext' => [ + 'label' => 'Formázott szöveg', + ], + ], + ], + 'translatedtext' => [ + 'label' => 'Többnyelvű szöveg', + 'form' => [ + 'richtext' => [ + 'label' => 'Formázott szöveg', + ], + 'locales' => 'Nyelvek', + ], + ], + 'toggle' => [ + 'label' => 'Kapcsoló', + ], + 'youtube' => [ + 'label' => 'YouTube', + ], + 'vimeo' => [ + 'label' => 'Vimeo', + ], + 'number' => [ + 'label' => 'Szám', + 'form' => [ + 'min' => [ + 'label' => 'Min.', + ], + 'max' => [ + 'label' => 'Max.', + ], + ], + ], + 'file' => [ + 'label' => 'Fájl', + 'form' => [ + 'file_types' => [ + 'label' => 'Engedélyezett fájltípusok', + 'placeholder' => 'Új MIME', + ], + 'multiple' => [ + 'label' => 'Több fájl engedélyezése', + ], + 'min_files' => [ + 'label' => 'Min. fájlok száma', + ], + 'max_files' => [ + 'label' => 'Max. fájlok száma', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/global.php b/packages/admin/resources/lang/hu/global.php new file mode 100644 index 0000000000..a0bd50bdd0 --- /dev/null +++ b/packages/admin/resources/lang/hu/global.php @@ -0,0 +1,12 @@ + [ + 'catalog' => 'Katalógus', + 'sales' => 'Eladások', + 'reports' => 'Jelentések', + 'settings' => 'Beállítások', + ], + +]; diff --git a/packages/admin/resources/lang/hu/language.php b/packages/admin/resources/lang/hu/language.php new file mode 100644 index 0000000000..857df00487 --- /dev/null +++ b/packages/admin/resources/lang/hu/language.php @@ -0,0 +1,33 @@ + 'Nyelv', + + 'plural_label' => 'Nyelvek', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/order.php b/packages/admin/resources/lang/hu/order.php new file mode 100644 index 0000000000..6a5eae9dfc --- /dev/null +++ b/packages/admin/resources/lang/hu/order.php @@ -0,0 +1,305 @@ + 'Rendelés', + + 'plural_label' => 'Rendelések', + + 'breadcrumb' => [ + 'manage' => 'Kezelés', + ], + + 'tabs' => [ + 'all' => 'Mind', + ], + + 'transactions' => [ + 'capture' => 'Lekönyvelve', + 'intent' => 'Fizetési szándék', + 'refund' => 'Visszatérítve', + 'failed' => 'Sikertelen', + ], + + 'table' => [ + 'status' => [ + 'label' => 'Státusz', + ], + 'reference' => [ + 'label' => 'Hivatkozás', + ], + 'customer_reference' => [ + 'label' => 'Vásárlói azonosító', + ], + 'customer' => [ + 'label' => 'Vásárló', + ], + 'tags' => [ + 'label' => 'Címkék', + ], + 'postcode' => [ + 'label' => 'Irányítószám', + ], + 'email' => [ + 'label' => 'E-mail', + 'copy_message' => 'E-mail cím másolva', + ], + 'phone' => [ + 'label' => 'Telefon', + ], + 'total' => [ + 'label' => 'Végösszeg', + ], + 'date' => [ + 'label' => 'Dátum', + ], + 'new_customer' => [ + 'label' => 'Vásárló típusa', + ], + 'placed_after' => [ + 'label' => 'Rendelés ideje után', + ], + 'placed_before' => [ + 'label' => 'Rendelés ideje előtt', + ], + ], + + 'form' => [ + 'address' => [ + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'line_one' => [ + 'label' => 'Utca, házszám', + ], + 'line_two' => [ + 'label' => 'Emelet, ajtó', + ], + 'line_three' => [ + 'label' => 'Egyéb címadat', + ], + 'company_name' => [ + 'label' => 'Cégnév', + ], + 'tax_identifier' => [ + 'label' => 'Adóazonosító', + ], + 'contact_phone' => [ + 'label' => 'Telefon', + ], + 'contact_email' => [ + 'label' => 'E-mail cím', + ], + 'city' => [ + 'label' => 'Város', + ], + 'state' => [ + 'label' => 'Megye / Tartomány', + ], + 'postcode' => [ + 'label' => 'Irányítószám', + ], + 'country_id' => [ + 'label' => 'Ország', + ], + ], + + 'reference' => [ + 'label' => 'Hivatkozás', + ], + 'status' => [ + 'label' => 'Státusz', + ], + 'transaction' => [ + 'label' => 'Tranzakció', + ], + 'amount' => [ + 'label' => 'Összeg', + + 'hint' => [ + 'less_than_total' => 'Ön kevesebb összeget készül lekönyvelni, mint a teljes tranzakció értéke', + ], + ], + + 'notes' => [ + 'label' => 'Megjegyzések', + ], + 'confirm' => [ + 'label' => 'Megerősítés', + + 'alert' => 'Megerősítés szükséges', + + 'hint' => [ + 'capture' => 'Kérjük, erősítse meg, hogy le akarja könyvelni ezt a fizetést', + 'refund' => 'Kérjük, erősítse meg, hogy vissza kívánja téríteni ezt az összeget.', + ], + ], + ], + + 'infolist' => [ + 'notes' => [ + 'label' => 'Megjegyzések', + 'placeholder' => 'Nincsenek megjegyzések ehhez a rendeléshez', + ], + 'delivery_instructions' => [ + 'label' => 'Szállítási utasítások', + ], + 'shipping_total' => [ + 'label' => 'Szállítási díj', + ], + 'paid' => [ + 'label' => 'Kifizetve', + ], + 'refund' => [ + 'label' => 'Visszatérítés', + ], + 'unit_price' => [ + 'label' => 'Egységár', + ], + 'quantity' => [ + 'label' => 'Mennyiség', + ], + 'sub_total' => [ + 'label' => 'Részösszeg', + ], + 'discount_total' => [ + 'label' => 'Kedvezmény összege', + ], + 'total' => [ + 'label' => 'Végösszeg', + ], + 'current_stock_level' => [ + 'message' => 'Jelenlegi készletszint: :count', + ], + 'purchase_stock_level' => [ + 'message' => 'rendelés idején: :count', + ], + 'status' => [ + 'label' => 'Státusz', + ], + 'reference' => [ + 'label' => 'Hivatkozás', + ], + 'customer_reference' => [ + 'label' => 'Vásárlói azonosító', + ], + 'channel' => [ + 'label' => 'Csatorna', + ], + 'date_created' => [ + 'label' => 'Létrehozva', + ], + 'date_placed' => [ + 'label' => 'Rendelés dátuma', + ], + 'new_returning' => [ + 'label' => 'Új / Visszatérő', + ], + 'new_customer' => [ + 'label' => 'Új vásárló', + ], + 'returning_customer' => [ + 'label' => 'Visszatérő vásárló', + ], + 'shipping_address' => [ + 'label' => 'Szállítási cím', + ], + 'billing_address' => [ + 'label' => 'Számlázási cím', + ], + 'address_not_set' => [ + 'label' => 'Nincs cím megadva', + ], + 'billing_matches_shipping' => [ + 'label' => 'Ugyanaz, mint a szállítási cím', + ], + 'additional_info' => [ + 'label' => 'További információ', + ], + 'no_additional_info' => [ + 'label' => 'Nincs további információ', + ], + 'tags' => [ + 'label' => 'Címkék', + ], + 'timeline' => [ + 'label' => 'Idővonal', + ], + 'transactions' => [ + 'label' => 'Tranzakciók', + 'placeholder' => 'Nincsenek tranzakciók', + ], + 'alert' => [ + 'requires_capture' => 'Ennél a rendelésnél még fizetést kell lekönyvelni.', + 'partially_refunded' => 'Ennél a rendelésnél részleges visszatérítés történt.', + 'refunded' => 'Ennél a rendelésnél visszatérítés történt.', + ], + ], + + 'action' => [ + 'bulk_update_status' => [ + 'label' => 'Státusz frissítése', + 'notification' => 'Rendelések státusza frissítve', + ], + 'update_status' => [ + 'new_status' => [ + 'label' => 'Új státusz', + ], + 'additional_content' => [ + 'label' => 'További tartalom', + ], + 'additional_email_recipient' => [ + 'label' => 'További e-mail címzett', + 'placeholder' => 'opcionális', + ], + ], + 'download_order_pdf' => [ + 'label' => 'PDF letöltése', + 'notification' => 'Rendelés PDF letöltése', + ], + 'edit_address' => [ + 'label' => 'Szerkesztés', + + 'notification' => [ + 'error' => 'Hiba', + + 'billing_address' => [ + 'saved' => 'Számlázási cím mentve', + ], + + 'shipping_address' => [ + 'saved' => 'Szállítási cím mentve', + ], + ], + ], + 'edit_tags' => [ + 'label' => 'Szerkesztés', + 'form' => [ + 'tags' => [ + 'label' => 'Címkék', + 'helper_text' => 'Címkék elválasztása Enterrel, Tab-bal vagy vesszővel (,)', + ], + ], + ], + 'capture_payment' => [ + 'label' => 'Fizetés lekönyvelése', + + 'notification' => [ + 'error' => 'Hiba történt a lekönyvelés során', + 'success' => 'Lekönyvelés sikeres', + ], + ], + 'refund_payment' => [ + 'label' => 'Visszatérítés', + + 'notification' => [ + 'error' => 'Hiba történt a visszatérítés során', + 'success' => 'Visszatérítés sikeres', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/product.php b/packages/admin/resources/lang/hu/product.php new file mode 100644 index 0000000000..06c3fbdcd1 --- /dev/null +++ b/packages/admin/resources/lang/hu/product.php @@ -0,0 +1,131 @@ + 'Termék', + + 'plural_label' => 'Termékek', + + 'tabs' => [ + 'all' => 'Mind', + ], + + 'status' => [ + 'unpublished' => [ + 'content' => 'Jelenleg vázlat státuszban van, ez a termék minden csatornán és vásárlói csoportban rejtve van.', + ], + 'availability' => [ + 'customer_groups' => 'Ez a termék jelenleg nem elérhető egyik vásárlói csoport számára sem.', + 'channels' => 'Ez a termék jelenleg nem elérhető egyik csatornán sem.', + ], + ], + + 'table' => [ + 'status' => [ + 'label' => 'Státusz', + 'states' => [ + 'deleted' => 'Törölve', + 'draft' => 'Vázlat', + 'published' => 'Közzétéve', + ], + ], + 'name' => [ + 'label' => 'Név', + ], + 'brand' => [ + 'label' => 'Márka', + ], + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'stock' => [ + 'label' => 'Készlet', + ], + 'producttype' => [ + 'label' => 'Terméktípus', + ], + ], + + 'actions' => [ + 'edit_status' => [ + 'label' => 'Státusz frissítése', + 'heading' => 'Státusz frissítése', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'brand' => [ + 'label' => 'Márka', + ], + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'producttype' => [ + 'label' => 'Terméktípus', + ], + 'status' => [ + 'label' => 'Státusz', + 'options' => [ + 'published' => [ + 'label' => 'Közzétéve', + 'description' => 'Ez a termék elérhető lesz minden engedélyezett vásárlói csoportban és csatornán', + ], + 'draft' => [ + 'label' => 'Vázlat', + 'description' => 'Ez a termék rejtett lesz minden csatornán és vásárlói csoportban', + ], + ], + ], + 'tags' => [ + 'label' => 'Címkék', + 'helper_text' => 'Címkék elválasztása Enterrel, Tab-bal vagy vesszővel (,)', + ], + 'collections' => [ + 'label' => 'Gyűjtemények', + 'select_collection' => 'Válassz gyűjteményt', + ], + ], + + 'pages' => [ + 'availability' => [ + 'label' => 'Elérhetőség', + ], + 'edit' => [ + 'title' => 'Alapvető információk', + ], + 'identifiers' => [ + 'label' => 'Termékazonosítók', + ], + 'inventory' => [ + 'label' => 'Készlet', + ], + 'pricing' => [ + 'form' => [ + 'tax_class_id' => [ + 'label' => 'Adóosztály', + ], + 'tax_ref' => [ + 'label' => 'Adóreferencia', + 'helper_text' => 'Opcionális, külső rendszerekkel való integrációhoz.', + ], + ], + ], + 'shipping' => [ + 'label' => 'Szállítás', + ], + 'variants' => [ + 'label' => 'Változatok', + ], + 'collections' => [ + 'label' => 'Gyűjtemények', + 'select_collection' => 'Válassz gyűjteményt', + ], + 'associations' => [ + 'label' => 'Termékasszociációk', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/productoption.php b/packages/admin/resources/lang/hu/productoption.php new file mode 100644 index 0000000000..bb278d6240 --- /dev/null +++ b/packages/admin/resources/lang/hu/productoption.php @@ -0,0 +1,127 @@ + 'Termék opció', + + 'plural_label' => 'Termék opciók', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'label' => [ + 'label' => 'Címke', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + 'shared' => [ + 'label' => 'Megosztott', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'label' => [ + 'label' => 'Címke', + ], + 'handle' => [ + 'label' => 'Azonosító', + ], + ], + + 'widgets' => [ + 'product-options' => [ + 'notifications' => [ + 'save-variants' => [ + 'success' => [ + 'title' => 'Termékváltozatok mentve', + ], + ], + ], + 'actions' => [ + 'cancel' => [ + 'label' => 'Mégse', + ], + 'save-options' => [ + 'label' => 'Opciók mentése', + ], + 'add-shared-option' => [ + 'label' => 'Megosztott opció hozzáadása', + 'form' => [ + 'product_option' => [ + 'label' => 'Termék opció', + ], + 'no_shared_components' => [ + 'label' => 'Nincsenek elérhető megosztott opciók.', + ], + 'preselect' => [ + 'label' => 'Minden érték előzetes kiválasztása alapértelmezés szerint.', + ], + ], + ], + 'add-restricted-option' => [ + 'label' => 'Opció hozzáadása', + ], + ], + 'options-list' => [ + 'empty' => [ + 'heading' => 'Nincsenek konfigurált termék opciók', + 'description' => 'Adj hozzá egy megosztott vagy korlátozott termék opciót a változatok generálásának megkezdéséhez.', + ], + ], + 'options-table' => [ + 'title' => 'Termék opciók', + 'configure-options' => [ + 'label' => 'Opciók konfigurálása', + ], + 'table' => [ + 'option' => [ + 'label' => 'Opció', + ], + 'values' => [ + 'label' => 'Értékek', + ], + ], + ], + 'variants-table' => [ + 'title' => 'Termékváltozatok', + 'actions' => [ + 'create' => [ + 'label' => 'Termékváltozat létrehozása', + ], + 'edit' => [ + 'label' => 'Szerkesztés', + ], + 'delete' => [ + 'label' => 'Törlés', + ], + ], + 'empty' => [ + 'heading' => 'Nincsenek konfigurált változatok', + ], + 'table' => [ + 'new' => [ + 'label' => 'Új', + ], + 'option' => [ + 'label' => 'Opció', + ], + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'price' => [ + 'label' => 'Ár', + ], + 'stock' => [ + 'label' => 'Készlet', + ], + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/producttype.php b/packages/admin/resources/lang/hu/producttype.php new file mode 100644 index 0000000000..4e683404b2 --- /dev/null +++ b/packages/admin/resources/lang/hu/producttype.php @@ -0,0 +1,52 @@ + 'Terméktípus', + + 'plural_label' => 'Terméktípusok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'products_count' => [ + 'label' => 'Termékek száma', + ], + 'product_attributes_count' => [ + 'label' => 'Termék attribútumok', + ], + 'variant_attributes_count' => [ + 'label' => 'Termékváltozat attribútumok', + ], + ], + + 'tabs' => [ + 'product_attributes' => [ + 'label' => 'Termék attribútumok', + ], + 'variant_attributes' => [ + 'label' => 'Termékváltozat attribútumok', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + ], + + 'attributes' => [ + 'no_groups' => 'Nincsenek elérhető attribútumcsoportok.', + 'no_attributes' => 'Nincsenek elérhető attribútumok.', + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'A terméktípus nem törölhető, mert vannak hozzá rendelve termékek.', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/productvariant.php b/packages/admin/resources/lang/hu/productvariant.php new file mode 100644 index 0000000000..0061433e4c --- /dev/null +++ b/packages/admin/resources/lang/hu/productvariant.php @@ -0,0 +1,105 @@ + 'Termékváltozat', + 'plural_label' => 'Termékváltozatok', + 'pages' => [ + 'edit' => [ + 'title' => 'Alapinformációk', + ], + 'media' => [ + 'title' => 'Média', + 'form' => [ + 'no_selection' => [ + 'label' => 'Ehhez a termékváltozathoz jelenleg nincs kép kiválasztva.', + ], + 'no_media_available' => [ + 'label' => 'Jelenleg nincs elérhető média ehhez a termékhez.', + ], + 'images' => [ + 'label' => 'Elsődleges kép', + 'helper_text' => 'Válaszd ki azt a termékképet, amely ezt a termékváltozatot képviseli.', + ], + ], + ], + 'identifiers' => [ + 'title' => 'Azonosítók', + ], + 'inventory' => [ + 'title' => 'Készlet', + ], + 'shipping' => [ + 'title' => 'Szállítás', + ], + ], + 'form' => [ + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'gtin' => [ + 'label' => 'Globális kereskedelmi cikkszám (GTIN)', + ], + 'mpn' => [ + 'label' => 'Gyártói cikkszám (MPN)', + ], + 'ean' => [ + 'label' => 'UPC/EAN', + ], + 'stock' => [ + 'label' => 'Raktáron', + ], + 'backorder' => [ + 'label' => 'Utánrendelhető', + ], + 'purchasable' => [ + 'label' => 'Vásárolhatóság', + 'options' => [ + 'always' => 'Mindig', + 'in_stock' => 'Raktáron', + 'in_stock_or_on_backorder' => 'Raktáron vagy utánrendelhető', + ], + ], + 'unit_quantity' => [ + 'label' => 'Egység mennyiség', + 'helper_text' => 'Hány egyedi darabból áll egy egység.', + ], + 'min_quantity' => [ + 'label' => 'Minimális mennyiség', + 'helper_text' => 'A termékváltozat egy vásárlás során megvásárolható minimális mennyisége.', + ], + 'quantity_increment' => [ + 'label' => 'Mennyiség növelése', + 'helper_text' => 'A termékváltozat csak e mennyiség többszörösében vásárolható meg.', + ], + 'tax_class_id' => [ + 'label' => 'Adóosztály', + ], + 'shippable' => [ + 'label' => 'Szállítható', + ], + 'length_value' => [ + 'label' => 'Hossz', + ], + 'length_unit' => [ + 'label' => 'Hossz egység', + ], + 'width_value' => [ + 'label' => 'Szélesség', + ], + 'width_unit' => [ + 'label' => 'Szélesség egység', + ], + 'height_value' => [ + 'label' => 'Magasság', + ], + 'height_unit' => [ + 'label' => 'Magasság egység', + ], + 'weight_value' => [ + 'label' => 'Súly', + ], + 'weight_unit' => [ + 'label' => 'Súly egység', + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/relationmanagers.php b/packages/admin/resources/lang/hu/relationmanagers.php new file mode 100644 index 0000000000..0dc8f432c9 --- /dev/null +++ b/packages/admin/resources/lang/hu/relationmanagers.php @@ -0,0 +1,285 @@ + [ + 'title' => 'Vásárlói csoportok', + 'actions' => [ + 'attach' => [ + 'label' => 'Vásárlói csoport csatolása', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'enabled' => [ + 'label' => 'Engedélyezve', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + ], + 'visible' => [ + 'label' => 'Látható', + ], + 'purchasable' => [ + 'label' => 'Megvásárolható', + ], + ], + 'table' => [ + 'description' => 'Kapcsoljon vásárlói csoportokat a(z) :type-hoz az elérhetőség meghatározásához.', + 'name' => [ + 'label' => 'Név', + ], + 'enabled' => [ + 'label' => 'Engedélyezve', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + ], + 'visible' => [ + 'label' => 'Látható', + ], + 'purchasable' => [ + 'label' => 'Megvásárolható', + ], + ], + ], + 'channels' => [ + 'title' => 'Csatornák', + 'actions' => [ + 'attach' => [ + 'label' => 'Csatorna ütemezése', + ], + ], + 'form' => [ + 'enabled' => [ + 'label' => 'Engedélyezve', + 'helper_text_false' => 'A csatorna nem lesz engedélyezve, még ha van kezdő dátum sem.', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + 'helper_text' => 'Hagyd üresen, ha bármely dátumtól elérhető legyen.', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + 'helper_text' => 'Hagyd üresen, ha határozatlan ideig elérhető legyen.', + ], + ], + 'table' => [ + 'description' => 'Állítsd be, mely csatornák engedélyezettek és ütemezd az elérhetőséget.', + 'name' => [ + 'label' => 'Név', + ], + 'enabled' => [ + 'label' => 'Engedélyezve', + ], + 'starts_at' => [ + 'label' => 'Kezdés dátuma', + ], + 'ends_at' => [ + 'label' => 'Befejezés dátuma', + ], + ], + ], + 'medias' => [ + 'title' => 'Média', + 'title_plural' => 'Médiák', + 'actions' => [ + 'attach' => [ + 'label' => 'Média csatolása', + ], + 'create' => [ + 'label' => 'Média létrehozása', + ], + 'detach' => [ + 'label' => 'Leválasztás', + ], + 'view' => [ + 'label' => 'Megtekintés', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'media' => [ + 'label' => 'Kép', + ], + 'primary' => [ + 'label' => 'Elsődleges', + ], + ], + 'table' => [ + 'image' => [ + 'label' => 'Kép', + ], + 'file' => [ + 'label' => 'Fájl', + ], + 'name' => [ + 'label' => 'Név', + ], + 'primary' => [ + 'label' => 'Elsődleges', + ], + ], + 'all_media_attached' => 'Nincsenek elérhető termékképek csatoláshoz', + 'variant_description' => 'Csatoljon termékképeket ehhez a változathoz', + ], + 'urls' => [ + 'title' => 'URL', + 'title_plural' => 'URL-ek', + 'actions' => [ + 'create' => [ + 'label' => 'URL létrehozása', + ], + ], + 'filters' => [ + 'language_id' => [ + 'label' => 'Nyelv', + ], + ], + 'form' => [ + 'slug' => [ + 'label' => 'URL-azonosító (slug)', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + 'language' => [ + 'label' => 'Nyelv', + ], + ], + 'table' => [ + 'slug' => [ + 'label' => 'URL-azonosító (slug)', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + 'language' => [ + 'label' => 'Nyelv', + ], + ], + ], + 'customer_group_pricing' => [ + 'title' => 'Vásárlói csoport árképzés', + 'title_plural' => 'Vásárlói csoport árképzés', + 'table' => [ + 'heading' => 'Vásárlói csoport árképzés', + 'description' => 'Ár társítása vásárlói csoportokhoz a termék árának meghatározásához.', + 'empty_state' => [ + 'label' => 'Nincs vásárlói csoport ár.', + 'description' => 'Hozz létre vásárlói csoport árat a kezdéshez.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Vásárlói csoport ár hozzáadása', + 'modal' => [ + 'heading' => 'Vásárlói csoport ár létrehozása', + ], + ], + ], + ], + ], + 'pricing' => [ + 'title' => 'Árképzés', + 'title_plural' => 'Árképzés', + 'tab_name' => 'Ár lépcsők', + 'table' => [ + 'heading' => 'Ár lépcsők', + 'description' => 'Csökkentsd az árat, ha a vásárló nagyobb mennyiséget vásárol.', + 'empty_state' => [ + 'label' => 'Nincsenek ár lépcsők.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Ár lépcső hozzáadása', + ], + ], + 'price' => [ + 'label' => 'Ár', + ], + 'customer_group' => [ + 'label' => 'Vásárlói csoport', + 'placeholder' => 'Minden vásárlói csoport', + ], + 'min_quantity' => [ + 'label' => 'Minimum mennyiség', + ], + 'currency' => [ + 'label' => 'Pénznem', + ], + ], + 'form' => [ + 'price' => [ + 'label' => 'Ár', + 'helper_text' => 'A vásárlási ár, kedvezmények előtt.', + ], + 'customer_group_id' => [ + 'label' => 'Vásárlói csoport', + 'placeholder' => 'Minden vásárlói csoport', + 'helper_text' => 'Válaszd ki, melyik vásárlói csoportra alkalmazd ezt az árat.', + ], + 'min_quantity' => [ + 'label' => 'Minimum mennyiség', + 'helper_text' => 'Válaszd ki a minimális mennyiséget, amelyre ez az ár érvényes lesz.', + 'validation' => [ + 'unique' => 'A vásárlói csoport és a minimum mennyiség egyedinek kell lennie.', + ], + ], + 'currency_id' => [ + 'label' => 'Pénznem', + 'helper_text' => 'Válaszd ki az ár pénznemét.', + ], + 'compare_price' => [ + 'label' => 'Összehasonlító ár', + 'helper_text' => 'Az eredeti ár vagy ajánlott fogyasztói ár, az összehasonlításhoz.', + ], + 'basePrices' => [ + 'title' => 'Árak', + 'form' => [ + 'price' => [ + 'label' => 'Ár', + 'helper_text' => 'A vásárlási ár, kedvezmények előtt.', + 'sync_price' => 'Az ár szinkronizálva van az alapértelmezett pénznemmel.', + ], + 'compare_price' => [ + 'label' => 'Összehasonlító ár', + 'helper_text' => 'Az eredeti ár vagy ajánlott fogyasztói ár, az összehasonlításhoz.', + ], + ], + 'tooltip' => 'Automatikusan generálva a pénznemek közötti árfolyamok alapján.', + ], + ], + ], + 'tax_rate_amounts' => [ + 'table' => [ + 'description' => '', + 'percentage' => [ + 'label' => 'Százalék', + ], + 'tax_class' => [ + 'label' => 'Adóosztály', + ], + ], + ], + 'values' => [ + 'title' => 'Értékek', + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'position' => [ + 'label' => 'Pozíció', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/staff.php b/packages/admin/resources/lang/hu/staff.php new file mode 100644 index 0000000000..3397e73b36 --- /dev/null +++ b/packages/admin/resources/lang/hu/staff.php @@ -0,0 +1,81 @@ + 'Munkatárs', + + 'plural_label' => 'Munkatársak', + + 'table' => [ + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'admin' => [ + 'badge' => 'Főadminisztrátor', + ], + ], + + 'form' => [ + 'first_name' => [ + 'label' => 'Keresztnév', + ], + 'last_name' => [ + 'label' => 'Vezetéknév', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Jelszó', + 'hint' => 'Jelszó visszaállítása', + ], + 'admin' => [ + 'label' => 'Főadminisztrátor', + 'helper' => 'A főadminisztrátor szerepkörök nem változtathatók a hubban.', + ], + 'roles' => [ + 'label' => 'Szerepkörök', + 'helper' => ':roles teljes hozzáféréssel rendelkeznek', + ], + 'permissions' => [ + 'label' => 'Jogosultságok', + ], + 'role' => [ + 'label' => 'Szerepkör neve', + ], + ], + + 'action' => [ + 'acl' => [ + 'label' => 'Hozzáférés-vezérlés', + ], + 'add-role' => [ + 'label' => 'Szerepkör hozzáadása', + ], + 'delete-role' => [ + 'label' => 'Szerepkör törlése', + 'heading' => 'Szerepkör törlése: :role', + ], + ], + + 'acl' => [ + 'title' => 'Hozzáférés-vezérlés', + 'tooltip' => [ + 'roles-included' => 'A jogosultság a következő szerepkörökben szerepel', + ], + 'notification' => [ + 'updated' => 'Frissítve', + 'error' => 'Hiba', + 'no-role' => 'A szerepkör nincs regisztrálva a Lunarban', + 'no-permission' => 'A jogosultság nincs regisztrálva a Lunarban', + 'no-role-permission' => 'Szerepkör és jogosultság nincs regisztrálva a Lunarban', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/tag.php b/packages/admin/resources/lang/hu/tag.php new file mode 100644 index 0000000000..ed0e133f66 --- /dev/null +++ b/packages/admin/resources/lang/hu/tag.php @@ -0,0 +1,21 @@ + 'Címke', + + 'plural_label' => 'Címkék', + + 'table' => [ + 'value' => [ + 'label' => 'Érték', + ], + ], + + 'form' => [ + 'value' => [ + 'label' => 'Érték', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/tax.php b/packages/admin/resources/lang/hu/tax.php new file mode 100644 index 0000000000..06072c126e --- /dev/null +++ b/packages/admin/resources/lang/hu/tax.php @@ -0,0 +1,9 @@ + 'Adó', + + 'plural_label' => 'Adók', + +]; diff --git a/packages/admin/resources/lang/hu/taxclass.php b/packages/admin/resources/lang/hu/taxclass.php new file mode 100644 index 0000000000..203fce8da2 --- /dev/null +++ b/packages/admin/resources/lang/hu/taxclass.php @@ -0,0 +1,32 @@ + 'Adóosztály', + + 'plural_label' => 'Adóosztályok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + 'delete' => [ + 'error' => [ + 'title' => 'Nem törölhető az adóosztály', + 'body' => 'Ennek az adóosztálynak vannak hozzá rendelt termékváltozatai, ezért nem törölhető.', + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/taxrate.php b/packages/admin/resources/lang/hu/taxrate.php new file mode 100644 index 0000000000..7515ea9552 --- /dev/null +++ b/packages/admin/resources/lang/hu/taxrate.php @@ -0,0 +1,33 @@ + 'Adókulcs', + + 'plural_label' => 'Adókulcsok', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'tax_zone' => [ + 'label' => 'Adózóna', + ], + 'priority' => [ + 'label' => 'Prioritás', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'priority' => [ + 'label' => 'Prioritás', + ], + 'tax_zone_id' => [ + 'label' => 'Adózóna', + ], + ], + +]; diff --git a/packages/admin/resources/lang/hu/taxzone.php b/packages/admin/resources/lang/hu/taxzone.php new file mode 100644 index 0000000000..398838dde5 --- /dev/null +++ b/packages/admin/resources/lang/hu/taxzone.php @@ -0,0 +1,69 @@ + 'Adózóna', + + 'plural_label' => 'Adózónák', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'zone_type' => [ + 'label' => 'Zóna típusa', + ], + 'active' => [ + 'label' => 'Aktív', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'zone_type' => [ + 'label' => 'Zóna típusa', + 'options' => [ + 'country' => 'Csak országokra korlátozva', + 'states' => 'Csak államokra korlátozva', + 'postcodes' => 'Csak irányítószámokra korlátozva', + ], + ], + 'price_display' => [ + 'label' => 'Ár megjelenítése', + 'options' => [ + 'include_tax' => 'Adót is tartalmaz', + 'exclude_tax' => 'Adót nem tartalmaz', + ], + ], + 'active' => [ + 'label' => 'Aktív', + ], + 'default' => [ + 'label' => 'Alapértelmezett', + ], + + 'zone_countries' => [ + 'label' => 'Országok', + ], + + 'zone_country' => [ + 'label' => 'Ország', + ], + + 'zone_states' => [ + 'label' => 'Államok', + ], + + 'zone_postcodes' => [ + 'label' => 'Irányítószámok', + 'helper' => 'Listázd az egyes irányítószámokat új sorban. Támogatja a helyettesítő karaktereket, mint például NW*', + ], + + ], + +]; diff --git a/packages/admin/resources/lang/hu/user.php b/packages/admin/resources/lang/hu/user.php new file mode 100644 index 0000000000..e4722a26c7 --- /dev/null +++ b/packages/admin/resources/lang/hu/user.php @@ -0,0 +1,29 @@ + 'Felhasználó', + + 'plural_label' => 'Felhasználók', + + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'email' => [ + 'label' => 'E-mail', + ], + ], + + 'form' => [ + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Új jelszó', + ], + 'password_confirmation' => [ + 'label' => 'Új jelszó megerősítése', + ], + ], +]; diff --git a/packages/admin/resources/lang/hu/widgets.php b/packages/admin/resources/lang/hu/widgets.php new file mode 100644 index 0000000000..aabaf51b09 --- /dev/null +++ b/packages/admin/resources/lang/hu/widgets.php @@ -0,0 +1,118 @@ + [ + 'orders' => [ + 'order_stats_overview' => [ + 'stat_one' => [ + 'label' => 'Rendelések ma', + 'increase' => ':percentage% növekedés a tegnapi (:count) értékhez képest', + 'decrease' => ':percentage% csökkenés a tegnapi (:count) értékhez képest', + 'neutral' => 'Nincs változás tegnaphoz képest', + ], + 'stat_two' => [ + 'label' => 'Rendelések az elmúlt 7 napban', + 'increase' => ':percentage% növekedés a :count előző időszakhoz képest', + 'decrease' => ':percentage% csökkenés a :count előző időszakhoz képest', + 'neutral' => 'Nincs változás az előző időszakhoz képest', + ], + 'stat_three' => [ + 'label' => 'Rendelések az elmúlt 30 napban', + 'increase' => ':percentage% növekedés a :count előző időszakhoz képest', + 'decrease' => ':percentage% csökkenés a :count előző időszakhoz képest', + 'neutral' => 'Nincs változás az előző időszakhoz képest', + ], + 'stat_four' => [ + 'label' => 'Rendelések ma', + 'increase' => ':percentage% növekedés a :total tegnapi értékhez képest', + 'decrease' => ':percentage% csökkenés a :total tegnapi értékhez képest', + 'neutral' => 'Nincs változás tegnaphoz képest', + ], + 'stat_five' => [ + 'label' => 'Rendelések az elmúlt 7 napban', + 'increase' => ':percentage% növekedés a :total előző időszakhoz képest', + 'decrease' => ':percentage% csökkenés a :total előző időszakhoz képest', + 'neutral' => 'Nincs változás az előző időszakhoz képest', + ], + 'stat_six' => [ + 'label' => 'Rendelések az elmúlt 30 napban', + 'increase' => ':percentage% növekedés a :total előző időszakhoz képest', + 'decrease' => ':percentage% csökkenés a :total előző időszakhoz képest', + 'neutral' => 'Nincs változás az előző időszakhoz képest', + ], + ], + 'order_totals_chart' => [ + 'heading' => 'Rendelési összesítők az elmúlt évben', + 'series_one' => [ + 'label' => 'Ez az időszak', + ], + 'series_two' => [ + 'label' => 'Előző időszak', + ], + 'yaxis' => [ + 'label' => 'Forgalom :currency', + ], + ], + 'order_sales_chart' => [ + 'heading' => 'Rendelések / Értékesítési Jelentés', + 'series_one' => [ + 'label' => 'Rendelések', + ], + 'series_two' => [ + 'label' => 'Bevétel', + ], + 'yaxis' => [ + 'series_one' => [ + 'label' => '# Rendelések', + ], + 'series_two' => [ + 'label' => 'Összérték', + ], + ], + ], + 'average_order_value' => [ + 'heading' => 'Átlagos rendelési érték', + ], + 'new_returning_customers' => [ + 'heading' => 'Új vs Visszatérő Vásárlók', + 'series_one' => [ + 'label' => 'Új Vásárlók', + ], + 'series_two' => [ + 'label' => 'Visszatérő Vásárlók', + ], + ], + 'popular_products' => [ + 'heading' => 'Legnépszerűbb termékek (utolsó 12 hónap)', + 'description' => 'Ezek a számok arra épülnek, hányszor szerepel egy termék egy rendelésben, nem a megrendelt mennyiségre.', + ], + 'latest_orders' => [ + 'heading' => 'Legutóbbi rendelések', + ], + ], + ], + 'customer' => [ + 'stats_overview' => [ + 'total_orders' => [ + 'label' => 'Összes rendelés', + ], + 'avg_spend' => [ + 'label' => 'Átlagos költés', + ], + 'total_spend' => [ + 'label' => 'Összes költés', + ], + ], + ], + 'variant_switcher' => [ + 'label' => 'Variáns váltása', + 'table' => [ + 'sku' => [ + 'label' => 'Cikkszám (SKU)', + ], + 'values' => [ + 'label' => 'Értékek', + ], + ], + ], +]; diff --git a/packages/core/resources/lang/hu/base.php b/packages/core/resources/lang/hu/base.php new file mode 100644 index 0000000000..b2a36c2e17 --- /dev/null +++ b/packages/core/resources/lang/hu/base.php @@ -0,0 +1,9 @@ + [ + 'collection-titles' => [ + 'images' => 'Képek', + ], + ], +]; diff --git a/packages/core/resources/lang/hu/exceptions.php b/packages/core/resources/lang/hu/exceptions.php new file mode 100644 index 0000000000..35d947a6f9 --- /dev/null +++ b/packages/core/resources/lang/hu/exceptions.php @@ -0,0 +1,21 @@ + 'A ":class" modell nem valósítja meg a Purchasable interfészt.', + 'cart_line_id_mismatch' => 'Ez a kosártétel nem ehhez a kosárhoz tartozik.', + 'invalid_cart_line_quantity' => 'A mennyiségnek legalább "1"-nek kell lennie, de ":quantity" lett megadva.', + 'maximum_cart_line_quantity' => 'A mennyiség nem haladhatja meg a :quantity értéket.', + 'carts.invalid_action' => 'A kosárművelet érvénytelen volt.', + 'carts.shipping_missing' => 'Szállítási cím megadása kötelező.', + 'carts.billing_missing' => 'Számlázási cím megadása kötelező.', + 'carts.billing_incomplete' => 'A számlázási cím hiányos.', + 'carts.order_exists' => 'Ehhez a kosárhoz már létezik rendelés.', + 'carts.shipping_option_missing' => 'Hiányzik a szállítási opció.', + 'missing_currency_price' => 'A ":currency" pénznemhez nem létezik ár.', + 'minimum_quantity' => 'Legalább :quantity tételt kell hozzáadni.', + 'quantity_increment' => 'A mennyiségnek :increment lépésközzel adható meg.', + 'fieldtype_missing' => 'A ":class" FieldType nem létezik.', + 'invalid_fieldtype' => 'A ":class" osztály nem valósítja meg a FieldType interfészt.', + 'discounts.invalid_type' => 'A gyűjtemény kizárólag ":expected" elemeket tartalmazhat, de ":actual" található.', + 'disallow_multiple_cart_orders' => 'Egy kosárhoz csak egy rendelés társítható.', +]; diff --git a/packages/table-rate-shipping/resources/lang/hu/plugin.php b/packages/table-rate-shipping/resources/lang/hu/plugin.php new file mode 100644 index 0000000000..c6694b8728 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/hu/plugin.php @@ -0,0 +1,7 @@ + [ + 'group' => 'Szállítás', + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/hu/relationmanagers.php b/packages/table-rate-shipping/resources/lang/hu/relationmanagers.php new file mode 100644 index 0000000000..a65667e819 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/hu/relationmanagers.php @@ -0,0 +1,80 @@ + [ + 'customer_groups' => [ + 'description' => 'Rendeld hozzá a vásárlói csoportokat ehhez a szállítási módhoz az elérhetőség meghatározásához.', + ], + ], + 'shipping_rates' => [ + 'title_plural' => 'Szállítási díjak', + 'actions' => [ + 'create' => [ + 'label' => 'Szállítási díj létrehozása', + ], + ], + 'notices' => [ + 'prices_incl_tax' => 'Minden ár tartalmazza az adót, ezt figyelembe vesszük a minimális költés számításakor.', + 'prices_excl_tax' => 'Minden ár adó nélkül értendő, a minimális költés a kosár részösszege alapján kerül kiszámításra.', + ], + 'form' => [ + 'shipping_method_id' => [ + 'label' => 'Szállítási mód', + ], + 'price' => [ + 'label' => 'Ár', + ], + 'prices' => [ + 'label' => 'Árlépcsők', + 'repeater' => [ + 'customer_group_id' => [ + 'label' => 'Vásárlói csoport', + 'placeholder' => 'Bármely', + ], + 'currency_id' => [ + 'label' => 'Pénznem', + ], + 'min_spend' => [ + 'label' => 'Min. költés', + ], + 'min_weight' => [ + 'label' => 'Min. súly', + ], + 'price' => [ + 'label' => 'Ár', + ], + ], + ], + ], + 'table' => [ + 'shipping_method' => [ + 'label' => 'Szállítási mód', + ], + 'price' => [ + 'label' => 'Ár', + ], + 'price_breaks_count' => [ + 'label' => 'Árlépcsők', + ], + ], + ], + 'exclusions' => [ + 'title_plural' => 'Szállítási kizárások', + 'form' => [ + 'purchasable' => [ + 'label' => 'Termék', + ], + ], + 'actions' => [ + 'create' => [ + 'label' => 'Szállítási kizárási lista hozzáadása', + ], + 'attach' => [ + 'label' => 'Kizárási lista hozzáadása', + ], + 'detach' => [ + 'label' => 'Eltávolítás', + ], + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/hu/shippingexclusionlist.php b/packages/table-rate-shipping/resources/lang/hu/shippingexclusionlist.php new file mode 100644 index 0000000000..ed16f994d8 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/hu/shippingexclusionlist.php @@ -0,0 +1,19 @@ + 'Szállítási kizáró lista', + 'label_plural' => 'Szállítási kizáró listák', + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'exclusions_count' => [ + 'label' => 'Termékek száma', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php b/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php new file mode 100644 index 0000000000..612ce2c1df --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php @@ -0,0 +1,58 @@ + 'Szállítási módok', + 'label' => 'Szállítási mód', + 'form' => [ + 'name' => [ + 'label' => 'Név', + ], + 'description' => [ + 'label' => 'Leírás', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'cutoff' => [ + 'label' => 'Határidő', + ], + 'charge_by' => [ + 'label' => 'Számlázás', + 'options' => [ + 'cart_total' => 'Kosár végösszeg', + 'weight' => 'Súly', + ], + ], + 'driver' => [ + 'label' => 'Típus', + 'options' => [ + 'ship-by' => 'Házhozszállítás', + 'collection'=> 'Személyes átvétel', + ], + ], + 'stock_available' => [ + 'label' => 'A kosárban lévő összes terméknek raktáron kell lennie', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'code' => [ + 'label' => 'Kód', + ], + 'driver' => [ + 'label' => 'Típus', + 'options' => [ + 'ship-by' => 'Házhozszállítás', + 'collection' => 'Személyes átvétel', + ], + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Elérhetőség', + 'customer_groups' => 'Ez a szállítási mód jelenleg egyik vásárlói csoport számára sem elérhető.', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/hu/shippingzone.php b/packages/table-rate-shipping/resources/lang/hu/shippingzone.php new file mode 100644 index 0000000000..beffed5ff0 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/hu/shippingzone.php @@ -0,0 +1,50 @@ + 'Szállítási zóna', + 'label_plural' => 'Szállítási zónák', + 'form' => [ + 'unrestricted' => [ + 'content' => 'Ehhez a szállítási zónához nincs korlátozás, ezért a pénztárnál minden vásárló számára elérhető lesz.', + ], + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'options' => [ + 'unrestricted' => 'Korlátozás nélkül', + 'countries' => 'Országokra korlátozva', + 'states' => 'Államokra / megyékre korlátozva', + 'postcodes' => 'Irányítószámokra korlátozva', + ], + ], + 'country' => [ + 'label' => 'Ország', + ], + 'states' => [ + 'label' => 'Megyék', + ], + 'countries' => [ + 'label' => 'Országok', + ], + 'postcodes' => [ + 'label' => 'Irányítószámok', + 'helper' => 'Minden irányítószámot új sorba írj. Támogatja a helyettesítő karaktereket, például: NW*', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Név', + ], + 'type' => [ + 'label' => 'Típus', + 'options' => [ + 'unrestricted' => 'Korlátozás nélkül', + 'countries' => 'Országokra korlátozva', + 'states' => 'Államokra / megyékre korlátozva', + 'postcodes' => 'Irányítószámokra korlátozva', + ], + ], + ], +]; From 5d97b4fd09fcdaecdbb67eefc4c69cf18b39f64e Mon Sep 17 00:00:00 2001 From: Gleydson Silva Date: Tue, 11 Nov 2025 15:52:38 +0100 Subject: [PATCH 12/50] Add Brazilian Portuguese translations (#2310) I added Brazilian Portuguese translations to the package. --------- Co-authored-by: Glenn Jacobs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../admin/resources/lang/pt_BR/actions.php | 52 +++ .../admin/resources/lang/pt_BR/activity.php | 29 ++ .../admin/resources/lang/pt_BR/address.php | 99 +++++ .../admin/resources/lang/pt_BR/attribute.php | 55 +++ .../resources/lang/pt_BR/attributegroup.php | 46 +++ packages/admin/resources/lang/pt_BR/auth.php | 32 ++ packages/admin/resources/lang/pt_BR/brand.php | 75 ++++ .../admin/resources/lang/pt_BR/channel.php | 39 ++ .../admin/resources/lang/pt_BR/collection.php | 45 +++ .../resources/lang/pt_BR/collectiongroup.php | 37 ++ .../admin/resources/lang/pt_BR/components.php | 117 ++++++ .../admin/resources/lang/pt_BR/currency.php | 58 +++ .../admin/resources/lang/pt_BR/customer.php | 63 ++++ .../resources/lang/pt_BR/customergroup.php | 40 ++ .../admin/resources/lang/pt_BR/discount.php | 353 ++++++++++++++++++ .../admin/resources/lang/pt_BR/fieldtypes.php | 72 ++++ .../admin/resources/lang/pt_BR/global.php | 12 + .../admin/resources/lang/pt_BR/language.php | 33 ++ packages/admin/resources/lang/pt_BR/order.php | 305 +++++++++++++++ .../admin/resources/lang/pt_BR/product.php | 131 +++++++ .../resources/lang/pt_BR/productoption.php | 127 +++++++ .../resources/lang/pt_BR/producttype.php | 52 +++ .../resources/lang/pt_BR/productvariant.php | 105 ++++++ .../resources/lang/pt_BR/relationmanagers.php | 285 ++++++++++++++ packages/admin/resources/lang/pt_BR/staff.php | 81 ++++ packages/admin/resources/lang/pt_BR/tag.php | 21 ++ packages/admin/resources/lang/pt_BR/tax.php | 9 + .../admin/resources/lang/pt_BR/taxclass.php | 32 ++ .../admin/resources/lang/pt_BR/taxrate.php | 33 ++ .../admin/resources/lang/pt_BR/taxzone.php | 69 ++++ packages/admin/resources/lang/pt_BR/user.php | 29 ++ .../admin/resources/lang/pt_BR/widgets.php | 118 ++++++ packages/core/resources/lang/pt_BR/base.php | 9 + .../core/resources/lang/pt_BR/exceptions.php | 21 ++ .../resources/lang/pt_BR/plugin.php | 7 + .../resources/lang/pt_BR/relationmanagers.php | 80 ++++ .../lang/pt_BR/shippingexclusionlist.php | 19 + .../resources/lang/pt_BR/shippingmethod.php | 58 +++ .../resources/lang/pt_BR/shippingzone.php | 50 +++ 39 files changed, 2898 insertions(+) create mode 100644 packages/admin/resources/lang/pt_BR/actions.php create mode 100644 packages/admin/resources/lang/pt_BR/activity.php create mode 100644 packages/admin/resources/lang/pt_BR/address.php create mode 100644 packages/admin/resources/lang/pt_BR/attribute.php create mode 100644 packages/admin/resources/lang/pt_BR/attributegroup.php create mode 100644 packages/admin/resources/lang/pt_BR/auth.php create mode 100644 packages/admin/resources/lang/pt_BR/brand.php create mode 100644 packages/admin/resources/lang/pt_BR/channel.php create mode 100644 packages/admin/resources/lang/pt_BR/collection.php create mode 100644 packages/admin/resources/lang/pt_BR/collectiongroup.php create mode 100644 packages/admin/resources/lang/pt_BR/components.php create mode 100644 packages/admin/resources/lang/pt_BR/currency.php create mode 100644 packages/admin/resources/lang/pt_BR/customer.php create mode 100644 packages/admin/resources/lang/pt_BR/customergroup.php create mode 100644 packages/admin/resources/lang/pt_BR/discount.php create mode 100644 packages/admin/resources/lang/pt_BR/fieldtypes.php create mode 100644 packages/admin/resources/lang/pt_BR/global.php create mode 100644 packages/admin/resources/lang/pt_BR/language.php create mode 100644 packages/admin/resources/lang/pt_BR/order.php create mode 100644 packages/admin/resources/lang/pt_BR/product.php create mode 100644 packages/admin/resources/lang/pt_BR/productoption.php create mode 100644 packages/admin/resources/lang/pt_BR/producttype.php create mode 100644 packages/admin/resources/lang/pt_BR/productvariant.php create mode 100644 packages/admin/resources/lang/pt_BR/relationmanagers.php create mode 100644 packages/admin/resources/lang/pt_BR/staff.php create mode 100644 packages/admin/resources/lang/pt_BR/tag.php create mode 100644 packages/admin/resources/lang/pt_BR/tax.php create mode 100644 packages/admin/resources/lang/pt_BR/taxclass.php create mode 100644 packages/admin/resources/lang/pt_BR/taxrate.php create mode 100644 packages/admin/resources/lang/pt_BR/taxzone.php create mode 100644 packages/admin/resources/lang/pt_BR/user.php create mode 100644 packages/admin/resources/lang/pt_BR/widgets.php create mode 100644 packages/core/resources/lang/pt_BR/base.php create mode 100644 packages/core/resources/lang/pt_BR/exceptions.php create mode 100644 packages/table-rate-shipping/resources/lang/pt_BR/plugin.php create mode 100644 packages/table-rate-shipping/resources/lang/pt_BR/relationmanagers.php create mode 100644 packages/table-rate-shipping/resources/lang/pt_BR/shippingexclusionlist.php create mode 100644 packages/table-rate-shipping/resources/lang/pt_BR/shippingmethod.php create mode 100644 packages/table-rate-shipping/resources/lang/pt_BR/shippingzone.php diff --git a/packages/admin/resources/lang/pt_BR/actions.php b/packages/admin/resources/lang/pt_BR/actions.php new file mode 100644 index 0000000000..3fdc1e1f68 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/actions.php @@ -0,0 +1,52 @@ + [ + 'create_root' => [ + 'label' => 'Criar coleção raiz', + ], + 'create_child' => [ + 'label' => 'Criar coleção filha', + ], + 'move' => [ + 'label' => 'Mover coleção', + ], + 'delete' => [ + 'label' => 'Excluir', + 'notifications' => [ + 'cannot_delete' => [ + 'title' => 'Não é possível excluir', + 'body' => 'Esta coleção possui coleções filhas e não pode ser excluída.', + ], + ], + ], + ], + 'orders' => [ + 'update_status' => [ + 'label' => 'Atualizar status', + 'wizard' => [ + 'step_one' => [ + 'label' => 'Status', + ], + 'step_two' => [ + 'label' => 'E-mails e notificações', + 'no_mailers' => 'Não há e-mails disponíveis para este status.', + ], + 'step_three' => [ + 'label' => 'Pré-visualizar e salvar', + 'no_mailers' => 'Nenhum e-mail foi escolhido para pré-visualização.', + ], + ], + 'notification' => [ + 'label' => 'Status do pedido atualizado', + ], + 'billing_email' => [ + 'label' => 'E-mail de cobrança', + ], + 'shipping_email' => [ + 'label' => 'E-mail de entrega', + ], + ], + + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/activity.php b/packages/admin/resources/lang/pt_BR/activity.php new file mode 100644 index 0000000000..1abbbbed2e --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/activity.php @@ -0,0 +1,29 @@ + 'Atividade', + + 'plural_label' => 'Atividades', + + 'table' => [ + 'subject' => 'Assunto', + 'description' => 'Descrição', + 'log' => 'Registro', + 'logged_at' => 'Registrado em', + 'event' => 'Evento', + 'logged_from' => 'Registrado de', + 'logged_until' => 'Registrado até', + ], + + 'form' => [ + 'causer_type' => 'Tipo do agente', + 'causer_id' => 'ID do agente', + 'subject_type' => 'Tipo do assunto', + 'subject_id' => 'ID do assunto', + 'description' => 'Descrição', + 'attributes' => 'Atributos', + 'old' => 'Antigo', + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/address.php b/packages/admin/resources/lang/pt_BR/address.php new file mode 100644 index 0000000000..c5d17d1982 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/address.php @@ -0,0 +1,99 @@ + 'Endereço', + + 'plural_label' => 'Endereços', + + 'table' => [ + 'title' => [ + 'label' => 'Título', + ], + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'company_name' => [ + 'label' => 'Nome da empresa', + ], + 'tax_identifier' => [ + 'label' => 'Identificador fiscal', + ], + 'line_one' => [ + 'label' => 'Endereço', + ], + 'line_two' => [ + 'label' => 'Linha dois', + ], + 'line_three' => [ + 'label' => 'Linha três', + ], + 'city' => [ + 'label' => 'Cidade', + ], + 'country_id' => [ + 'label' => 'País', + ], + 'state' => [ + 'label' => 'Estado', + ], + 'postcode' => [ + 'label' => 'CEP', + ], + 'contact_email' => [ + 'label' => 'E-mail de contato', + ], + 'contact_phone' => [ + 'label' => 'Telefone de contato', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Título', + ], + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'company_name' => [ + 'label' => 'Nome da empresa', + ], + 'tax_identifier' => [ + 'label' => 'Identificador fiscal', + ], + 'line_one' => [ + 'label' => 'Linha um', + ], + 'line_two' => [ + 'label' => 'Linha dois', + ], + 'line_three' => [ + 'label' => 'Linha três', + ], + 'city' => [ + 'label' => 'Cidade', + ], + 'country_id' => [ + 'label' => 'País', + ], + 'state' => [ + 'label' => 'Estado', + ], + 'postcode' => [ + 'label' => 'CEP', + ], + 'contact_email' => [ + 'label' => 'E-mail de contato', + ], + 'contact_phone' => [ + 'label' => 'Telefone de contato', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/attribute.php b/packages/admin/resources/lang/pt_BR/attribute.php new file mode 100644 index 0000000000..0ed27a0822 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/attribute.php @@ -0,0 +1,55 @@ + 'Atributo', + + 'plural_label' => 'Atributos', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'description' => [ + 'label' => 'Descrição', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'type' => [ + 'label' => 'Tipo', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tipo', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'description' => [ + 'label' => 'Descrição', + 'helper' => 'Use para exibir um texto de ajuda abaixo do campo', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'searchable' => [ + 'label' => 'Pesquisável', + ], + 'filterable' => [ + 'label' => 'Filtrável', + ], + 'required' => [ + 'label' => 'Obrigatório', + ], + 'type' => [ + 'label' => 'Tipo', + ], + 'validation_rules' => [ + 'label' => 'Regras de validação', + 'helper' => 'Regras para o campo do atributo, exemplo: min:1|max:10|...', + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/attributegroup.php b/packages/admin/resources/lang/pt_BR/attributegroup.php new file mode 100644 index 0000000000..97121b9027 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/attributegroup.php @@ -0,0 +1,46 @@ + 'Grupo de atributos', + + 'plural_label' => 'Grupos de atributos', + + 'table' => [ + 'attributable_type' => [ + 'label' => 'Tipo', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'position' => [ + 'label' => 'Posição', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tipo', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'position' => [ + 'label' => 'Posição', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Este grupo de atributos não pode ser excluído pois há atributos associados.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/auth.php b/packages/admin/resources/lang/pt_BR/auth.php new file mode 100644 index 0000000000..60335acdb5 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/auth.php @@ -0,0 +1,32 @@ + 'Admin', + 'roles.admin.description' => 'Administrador com acesso total', + 'roles.staff.label' => 'Equipe', + 'roles.staff.description' => 'Equipe com acesso fundamental', + /** + * Permissions. + */ + 'permissions.settings.label' => 'Configurações', + 'permissions.settings.description' => 'Concede acesso à área de configurações do hub', + 'permissions.settings:core.label' => 'Configurações básicas', + 'permissions.settings:core.description' => 'Acessa configurações fundamentais da loja, como canais, idiomas, moedas etc.', + 'permissions.settings:manage-staff.label' => 'Gerenciar equipe', + 'permissions.settings:manage-staff.description' => 'Permite que o membro da equipe edite outros membros', + 'permissions.settings:manage-attributes.label' => 'Gerenciar atributos', + 'permissions.settings:manage-attributes.description' => 'Permite que o membro da equipe edite e crie atributos adicionais', + 'permissions.catalog:manage-products.label' => 'Gerenciar produtos', + 'permissions.catalog:manage-products.description' => 'Permite que o membro da equipe edite produtos, tipos de produto e marcas', + 'permissions.catalog:manage-collections.label' => 'Gerenciar coleções', + 'permissions.catalog:manage-collections.description' => 'Permite que o membro da equipe edite coleções e seus grupos', + 'permissions.sales:manage-orders.label' => 'Gerenciar pedidos', + 'permissions.sales:manage-orders.description' => 'Permite que o membro da equipe gerencie pedidos', + 'permissions.sales:manage-customers.label' => 'Gerenciar clientes', + 'permissions.sales:manage-customers.description' => 'Permite que o membro da equipe gerencie clientes', + 'permissions.sales:manage-discounts.label' => 'Gerenciar descontos', + 'permissions.sales:manage-discounts.description' => 'Permite que o membro da equipe gerencie descontos', +]; diff --git a/packages/admin/resources/lang/pt_BR/brand.php b/packages/admin/resources/lang/pt_BR/brand.php new file mode 100644 index 0000000000..11da6797f2 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/brand.php @@ -0,0 +1,75 @@ + 'Marca', + + 'plural_label' => 'Marcas', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'products_count' => [ + 'label' => 'Qtd. de produtos', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Esta marca não pode ser excluída pois há produtos associados.', + ], + ], + ], + 'pages' => [ + 'edit' => [ + 'title' => 'Informações básicas', + ], + 'products' => [ + 'label' => 'Produtos', + 'actions' => [ + 'attach' => [ + 'label' => 'Associar um produto', + 'form' => [ + 'record_id' => [ + 'label' => 'Produto', + ], + ], + 'notification' => [ + 'success' => 'Produto associado à marca', + ], + ], + 'detach' => [ + 'notification' => [ + 'success' => 'Produto desassociado.', + ], + ], + ], + ], + 'collections' => [ + 'label' => 'Coleções', + 'table' => [ + 'header_actions' => [ + 'attach' => [ + 'record_select' => [ + 'placeholder' => 'Selecione uma coleção', + ], + ], + ], + ], + 'actions' => [ + 'attach' => [ + 'label' => 'Associar uma coleção', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/channel.php b/packages/admin/resources/lang/pt_BR/channel.php new file mode 100644 index 0000000000..5ad7d06e6d --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/channel.php @@ -0,0 +1,39 @@ + 'Canal', + + 'plural_label' => 'Canais', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/collection.php b/packages/admin/resources/lang/pt_BR/collection.php new file mode 100644 index 0000000000..509c221559 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/collection.php @@ -0,0 +1,45 @@ + 'Coleção', + + 'plural_label' => 'Coleções', + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + ], + + 'pages' => [ + 'children' => [ + 'label' => 'Coleções filhas', + 'actions' => [ + 'create_child' => [ + 'label' => 'Criar coleção filha', + ], + ], + 'table' => [ + 'children_count' => [ + 'label' => 'Qtd. de filhas', + ], + 'name' => [ + 'label' => 'Nome', + ], + ], + ], + 'edit' => [ + 'label' => 'Informações básicas', + ], + 'products' => [ + 'label' => 'Produtos', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar produto', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/collectiongroup.php b/packages/admin/resources/lang/pt_BR/collectiongroup.php new file mode 100644 index 0000000000..65eae3be84 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/collectiongroup.php @@ -0,0 +1,37 @@ + 'Grupo de coleções', + + 'plural_label' => 'Grupos de coleções', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'collections_count' => [ + 'label' => 'Qtd. de coleções', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Este grupo de coleções não pode ser excluído pois há coleções associadas.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/components.php b/packages/admin/resources/lang/pt_BR/components.php new file mode 100644 index 0000000000..d87e62c40a --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/components.php @@ -0,0 +1,117 @@ + [ + 'notification' => [ + + 'updated' => 'Tags atualizadas', + + ], + ], + + 'activity-log' => [ + + 'input' => [ + + 'placeholder' => 'Adicionar um comentário', + + ], + + 'action' => [ + + 'add-comment' => 'Adicionar comentário', + + ], + + 'system' => 'Sistema', + + 'partials' => [ + 'orders' => [ + 'order_created' => 'Pedido criado', + + 'status_change' => 'Status atualizado', + + 'capture' => 'Pagamento de :amount no cartão com final :last_four', + + 'authorized' => 'Autorizado o valor de :amount no cartão com final :last_four', + + 'refund' => 'Reembolso de :amount no cartão com final :last_four', + + 'address' => ':type atualizado', + + 'billingAddress' => 'Endereço de cobrança', + + 'shippingAddress' => 'Endereço de entrega', + ], + + 'update' => [ + 'updated' => ':model atualizado', + ], + + 'create' => [ + 'created' => ':model criado', + ], + + 'tags' => [ + 'updated' => 'Tags atualizadas', + 'added' => 'Adicionado', + 'removed' => 'Removido', + ], + ], + + 'notification' => [ + 'comment_added' => 'Comentário adicionado', + ], + + ], + + 'forms' => [ + 'youtube' => [ + 'helperText' => 'Informe o ID do vídeo do YouTube. Ex.: dQw4w9WgXcQ', + ], + ], + + 'collection-tree-view' => [ + 'actions' => [ + 'move' => [ + 'form' => [ + 'target_id' => [ + 'label' => 'Coleção pai', + ], + ], + ], + ], + 'notifications' => [ + 'collections-reordered' => [ + 'success' => 'Coleções reordenadas', + ], + 'node-expanded' => [ + 'danger' => 'Não foi possível carregar as coleções', + ], + 'delete' => [ + 'danger' => 'Não foi possível excluir a coleção', + ], + ], + ], + + 'product-options-list' => [ + 'add-option' => [ + 'label' => 'Adicionar opção', + ], + 'delete-option' => [ + 'label' => 'Excluir opção', + ], + 'remove-shared-option' => [ + 'label' => 'Remover opção compartilhada', + ], + 'add-value' => [ + 'label' => 'Adicionar outro valor', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'values' => [ + 'label' => 'Valores', + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/currency.php b/packages/admin/resources/lang/pt_BR/currency.php new file mode 100644 index 0000000000..06ddba0eeb --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/currency.php @@ -0,0 +1,58 @@ + 'Moeda', + + 'plural_label' => 'Moedas', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'code' => [ + 'label' => 'Código', + ], + 'exchange_rate' => [ + 'label' => 'Taxa de câmbio', + ], + 'decimal_places' => [ + 'label' => 'Casas decimais', + ], + 'enabled' => [ + 'label' => 'Ativada', + ], + 'sync_prices' => [ + 'label' => 'Sincronizar preços', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'code' => [ + 'label' => 'Código', + ], + 'exchange_rate' => [ + 'label' => 'Taxa de câmbio', + ], + 'decimal_places' => [ + 'label' => 'Casas decimais', + ], + 'enabled' => [ + 'label' => 'Ativada', + ], + 'default' => [ + 'label' => 'Padrão', + ], + 'sync_prices' => [ + 'label' => 'Sincronizar preços', + 'helper_text' => 'Manter preços nesta moeda sincronizados com a moeda padrão.', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/customer.php b/packages/admin/resources/lang/pt_BR/customer.php new file mode 100644 index 0000000000..35eab5dc20 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/customer.php @@ -0,0 +1,63 @@ + 'Cliente', + + 'plural_label' => 'Clientes', + + 'table' => [ + 'full_name' => [ + 'label' => 'Nome', + ], + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'title' => [ + 'label' => 'Título', + ], + 'company_name' => [ + 'label' => 'Nome da empresa', + ], + 'tax_identifier' => [ + 'label' => 'Identificador fiscal', + ], + 'account_reference' => [ + 'label' => 'Referência da conta', + ], + 'new' => [ + 'label' => 'Novo', + ], + 'returning' => [ + 'label' => 'Recorrente', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Título', + ], + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'company_name' => [ + 'label' => 'Nome da empresa', + ], + 'account_ref' => [ + 'label' => 'Referência da conta', + ], + 'tax_identifier' => [ + 'label' => 'Identificador fiscal', + ], + 'customer_groups' => [ + 'label' => 'Grupos de clientes', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/customergroup.php b/packages/admin/resources/lang/pt_BR/customergroup.php new file mode 100644 index 0000000000..b4e4968b93 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/customergroup.php @@ -0,0 +1,40 @@ + 'Grupo de clientes', + + 'plural_label' => 'Grupos de clientes', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Este grupo de clientes não pode ser excluído pois há clientes associados.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/discount.php b/packages/admin/resources/lang/pt_BR/discount.php new file mode 100644 index 0000000000..1c550ba5de --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/discount.php @@ -0,0 +1,353 @@ + 'Descontos', + 'label' => 'Desconto', + 'form' => [ + 'conditions' => [ + 'heading' => 'Condições', + ], + 'buy_x_get_y' => [ + 'heading' => 'Compre X, leve Y', + ], + 'amount_off' => [ + 'heading' => 'Valor de desconto', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'starts_at' => [ + 'label' => 'Data de início', + ], + 'ends_at' => [ + 'label' => 'Data de término', + ], + 'priority' => [ + 'label' => 'Prioridade', + 'helper_text' => 'Descontos com maior prioridade serão aplicados primeiro.', + 'options' => [ + 'low' => [ + 'label' => 'Baixa', + ], + 'medium' => [ + 'label' => 'Média', + ], + 'high' => [ + 'label' => 'Alta', + ], + ], + ], + 'stop' => [ + 'label' => 'Interromper outros descontos após este', + ], + 'coupon' => [ + 'label' => 'Cupom', + 'helper_text' => 'Informe o cupom necessário para aplicar o desconto; se deixado em branco, será aplicado automaticamente.', + ], + 'max_uses' => [ + 'label' => 'Máximo de usos', + 'helper_text' => 'Deixe em branco para usos ilimitados.', + ], + 'max_uses_per_user' => [ + 'label' => 'Máximo de usos por usuário', + 'helper_text' => 'Deixe em branco para usos ilimitados.', + ], + 'minimum_cart_amount' => [ + 'label' => 'Valor mínimo do carrinho', + ], + 'min_qty' => [ + 'label' => 'Quantidade de produtos', + 'helper_text' => 'Defina quantos produtos qualificados são necessários para aplicar o desconto.', + ], + 'reward_qty' => [ + 'label' => 'Qtd. de itens grátis', + 'helper_text' => 'Quantos de cada item serão descontados.', + ], + 'max_reward_qty' => [ + 'label' => 'Quantidade máxima de recompensa', + 'helper_text' => 'Quantidade máxima de produtos que podem ser descontados, independentemente do critério.', + ], + 'automatic_rewards' => [ + 'label' => 'Adicionar recompensas automaticamente', + 'helper_text' => 'Ative para adicionar produtos de recompensa quando não estiverem no carrinho.', + ], + 'fixed_value' => [ + 'label' => 'Valor fixo', + ], + 'percentage' => [ + 'label' => 'Percentual', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'status' => [ + 'label' => 'Status', + \Lunar\Models\Discount::ACTIVE => [ + 'label' => 'Ativo', + ], + \Lunar\Models\Discount::PENDING => [ + 'label' => 'Pendente', + ], + \Lunar\Models\Discount::EXPIRED => [ + 'label' => 'Expirado', + ], + \Lunar\Models\Discount::SCHEDULED => [ + 'label' => 'Agendado', + ], + ], + 'type' => [ + 'label' => 'Tipo', + ], + 'starts_at' => [ + 'label' => 'Data de início', + ], + 'ends_at' => [ + 'label' => 'Data de término', + ], + 'created_at' => [ + 'label' => 'Criado em', + ], + 'coupon' => [ + 'label' => 'Cupom', + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilidade', + ], + 'edit' => [ + 'title' => 'Informações básicas', + ], + 'limitations' => [ + 'label' => 'Limitações', + ], + ], + 'relationmanagers' => [ + 'collections' => [ + 'title' => 'Coleções', + 'description' => 'Selecione a quais coleções este desconto deve se limitar.', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar coleção', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + 'customers' => [ + 'title' => 'Clientes', + 'description' => 'Selecione a quais clientes este desconto deve se limitar.', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar cliente', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + ], + ], + 'brands' => [ + 'title' => 'Marcas', + 'description' => 'Selecione a quais marcas este desconto deve se limitar.', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar marca', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + 'products' => [ + 'title' => 'Produtos', + 'description' => 'Selecione a quais produtos este desconto deve se limitar.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adicionar produto', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + 'rewards' => [ + 'title' => 'Recompensas', + 'description' => 'Selecione quais produtos serão descontados se estiverem no carrinho e as condições acima forem atendidas.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adicionar recompensa', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + 'conditions' => [ + 'title' => 'Condições', + 'description' => 'Selecione as condições necessárias para aplicar o desconto.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adicionar condição', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + 'productvariants' => [ + 'title' => 'Variações de produto', + 'description' => 'Selecione a quais variações de produto este desconto deve se limitar.', + 'actions' => [ + 'attach' => [ + 'label' => 'Adicionar variação de produto', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'values' => [ + 'label' => 'Opção(ões)', + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Limitação', + ], + 'exclusion' => [ + 'label' => 'Exclusão', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/fieldtypes.php b/packages/admin/resources/lang/pt_BR/fieldtypes.php new file mode 100644 index 0000000000..4dadbe7690 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/fieldtypes.php @@ -0,0 +1,72 @@ + [ + 'label' => 'Dropdown', + 'form' => [ + 'lookups' => [ + 'label' => 'Consultas', + 'key_label' => 'Rótulo', + 'value_label' => 'Valor', + ], + ], + ], + 'listfield' => [ + 'label' => 'Campo de lista', + ], + 'text' => [ + 'label' => 'Texto', + 'form' => [ + 'richtext' => [ + 'label' => 'Rich Text', + ], + ], + ], + 'translatedtext' => [ + 'label' => 'Texto traduzido', + 'form' => [ + 'richtext' => [ + 'label' => 'Rich Text', + ], + 'locales' => 'Idiomas', + ], + ], + 'toggle' => [ + 'label' => 'Alternar', + ], + 'youtube' => [ + 'label' => 'YouTube', + ], + 'vimeo' => [ + 'label' => 'Vimeo', + ], + 'number' => [ + 'label' => 'Número', + 'form' => [ + 'min' => [ + 'label' => 'Mín.', + ], + 'max' => [ + 'label' => 'Máx.', + ], + ], + ], + 'file' => [ + 'label' => 'Arquivo', + 'form' => [ + 'file_types' => [ + 'label' => 'Tipos de arquivo permitidos', + 'placeholder' => 'Novo MIME', + ], + 'multiple' => [ + 'label' => 'Permitir múltiplos arquivos', + ], + 'min_files' => [ + 'label' => 'Mín. de arquivos', + ], + 'max_files' => [ + 'label' => 'Máx. de arquivos', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/global.php b/packages/admin/resources/lang/pt_BR/global.php new file mode 100644 index 0000000000..2bfc6203f0 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/global.php @@ -0,0 +1,12 @@ + [ + 'catalog' => 'Catálogo', + 'sales' => 'Vendas', + 'reports' => 'Relatórios', + 'settings' => 'Configurações', + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/language.php b/packages/admin/resources/lang/pt_BR/language.php new file mode 100644 index 0000000000..8c97548422 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/language.php @@ -0,0 +1,33 @@ + 'Idioma', + + 'plural_label' => 'Idiomas', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'code' => [ + 'label' => 'Código', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'code' => [ + 'label' => 'Código', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/order.php b/packages/admin/resources/lang/pt_BR/order.php new file mode 100644 index 0000000000..5603233995 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/order.php @@ -0,0 +1,305 @@ + 'Pedido', + + 'plural_label' => 'Pedidos', + + 'breadcrumb' => [ + 'manage' => 'Gerenciar', + ], + + 'tabs' => [ + 'all' => 'Todos', + ], + + 'transactions' => [ + 'capture' => 'Capturado', + 'intent' => 'Intenção', + 'refund' => 'Reembolsado', + 'failed' => 'Falhou', + ], + + 'table' => [ + 'status' => [ + 'label' => 'Status', + ], + 'reference' => [ + 'label' => 'Referência', + ], + 'customer_reference' => [ + 'label' => 'Referência do cliente', + ], + 'customer' => [ + 'label' => 'Cliente', + ], + 'tags' => [ + 'label' => 'Tags', + ], + 'postcode' => [ + 'label' => 'CEP', + ], + 'email' => [ + 'label' => 'E-mail', + 'copy_message' => 'Endereço de e-mail copiado', + ], + 'phone' => [ + 'label' => 'Telefone', + ], + 'total' => [ + 'label' => 'Total', + ], + 'date' => [ + 'label' => 'Data', + ], + 'new_customer' => [ + 'label' => 'Tipo de cliente', + ], + 'placed_after' => [ + 'label' => 'Realizado após', + ], + 'placed_before' => [ + 'label' => 'Realizado antes', + ], + ], + + 'form' => [ + 'address' => [ + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'line_one' => [ + 'label' => 'Endereço - linha 1', + ], + 'line_two' => [ + 'label' => 'Endereço - linha 2', + ], + 'line_three' => [ + 'label' => 'Endereço - linha 3', + ], + 'company_name' => [ + 'label' => 'Nome da empresa', + ], + 'tax_identifier' => [ + 'label' => 'Identificador fiscal', + ], + 'contact_phone' => [ + 'label' => 'Telefone', + ], + 'contact_email' => [ + 'label' => 'Endereço de e-mail', + ], + 'city' => [ + 'label' => 'Cidade', + ], + 'state' => [ + 'label' => 'Estado / Província', + ], + 'postcode' => [ + 'label' => 'CEP', + ], + 'country_id' => [ + 'label' => 'País', + ], + ], + + 'reference' => [ + 'label' => 'Referência', + ], + 'status' => [ + 'label' => 'Status', + ], + 'transaction' => [ + 'label' => 'Transação', + ], + 'amount' => [ + 'label' => 'Valor', + + 'hint' => [ + 'less_than_total' => 'Você está prestes a capturar um valor menor que o total da transação', + ], + ], + + 'notes' => [ + 'label' => 'Notas', + ], + 'confirm' => [ + 'label' => 'Confirmar', + + 'alert' => 'Confirmação necessária', + + 'hint' => [ + 'capture' => 'Confirme que deseja capturar este pagamento', + 'refund' => 'Confirme que deseja reembolsar este valor.', + ], + ], + ], + + 'infolist' => [ + 'notes' => [ + 'label' => 'Notas', + 'placeholder' => 'Sem notas neste pedido', + ], + 'delivery_instructions' => [ + 'label' => 'Instruções de entrega', + ], + 'shipping_total' => [ + 'label' => 'Total de frete', + ], + 'paid' => [ + 'label' => 'Pago', + ], + 'refund' => [ + 'label' => 'Reembolso', + ], + 'unit_price' => [ + 'label' => 'Preço unitário', + ], + 'quantity' => [ + 'label' => 'Quantidade', + ], + 'sub_total' => [ + 'label' => 'Subtotal', + ], + 'discount_total' => [ + 'label' => 'Total de desconto', + ], + 'total' => [ + 'label' => 'Total', + ], + 'current_stock_level' => [ + 'message' => 'Nível de estoque atual: :count', + ], + 'purchase_stock_level' => [ + 'message' => 'no momento do pedido: :count', + ], + 'status' => [ + 'label' => 'Status', + ], + 'reference' => [ + 'label' => 'Referência', + ], + 'customer_reference' => [ + 'label' => 'Referência do cliente', + ], + 'channel' => [ + 'label' => 'Canal', + ], + 'date_created' => [ + 'label' => 'Data de criação', + ], + 'date_placed' => [ + 'label' => 'Data do pedido', + ], + 'new_returning' => [ + 'label' => 'Novo / Recorrente', + ], + 'new_customer' => [ + 'label' => 'Cliente novo', + ], + 'returning_customer' => [ + 'label' => 'Cliente recorrente', + ], + 'shipping_address' => [ + 'label' => 'Endereço de entrega', + ], + 'billing_address' => [ + 'label' => 'Endereço de cobrança', + ], + 'address_not_set' => [ + 'label' => 'Nenhum endereço definido', + ], + 'billing_matches_shipping' => [ + 'label' => 'Igual ao endereço de entrega', + ], + 'additional_info' => [ + 'label' => 'Informações adicionais', + ], + 'no_additional_info' => [ + 'label' => 'Sem informações adicionais', + ], + 'tags' => [ + 'label' => 'Tags', + ], + 'timeline' => [ + 'label' => 'Linha do tempo', + ], + 'transactions' => [ + 'label' => 'Transações', + 'placeholder' => 'Sem transações', + ], + 'alert' => [ + 'requires_capture' => 'Este pedido ainda requer captura do pagamento.', + 'partially_refunded' => 'Este pedido foi parcialmente reembolsado.', + 'refunded' => 'Este pedido foi reembolsado.', + ], + ], + + 'action' => [ + 'bulk_update_status' => [ + 'label' => 'Atualizar status', + 'notification' => 'Status dos pedidos atualizado', + ], + 'update_status' => [ + 'new_status' => [ + 'label' => 'Novo status', + ], + 'additional_content' => [ + 'label' => 'Conteúdo adicional', + ], + 'additional_email_recipient' => [ + 'label' => 'Destinatário de e-mail adicional', + 'placeholder' => 'opcional', + ], + ], + 'download_order_pdf' => [ + 'label' => 'Baixar PDF', + 'notification' => 'Baixando PDF do pedido', + ], + 'edit_address' => [ + 'label' => 'Editar', + + 'notification' => [ + 'error' => 'Erro', + + 'billing_address' => [ + 'saved' => 'Endereço de cobrança salvo', + ], + + 'shipping_address' => [ + 'saved' => 'Endereço de entrega salvo', + ], + ], + ], + 'edit_tags' => [ + 'label' => 'Editar', + 'form' => [ + 'tags' => [ + 'label' => 'Tags', + 'helper_text' => 'Separe tags pressionando Enter, Tab ou vírgula (,)', + ], + ], + ], + 'capture_payment' => [ + 'label' => 'Capturar pagamento', + + 'notification' => [ + 'error' => 'Houve um problema na captura', + 'success' => 'Captura realizada com sucesso', + ], + ], + 'refund_payment' => [ + 'label' => 'Reembolsar', + + 'notification' => [ + 'error' => 'Houve um problema no reembolso', + 'success' => 'Reembolso realizado com sucesso', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/product.php b/packages/admin/resources/lang/pt_BR/product.php new file mode 100644 index 0000000000..f2407d31e1 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/product.php @@ -0,0 +1,131 @@ + 'Produto', + + 'plural_label' => 'Produtos', + + 'tabs' => [ + 'all' => 'Todos', + ], + + 'status' => [ + 'unpublished' => [ + 'content' => 'Atualmente em rascunho, este produto está oculto em todos os canais e grupos de clientes.', + ], + 'availability' => [ + 'customer_groups' => 'Este produto está indisponível para todos os grupos de clientes.', + 'channels' => 'Este produto está indisponível para todos os canais.', + ], + ], + + 'table' => [ + 'status' => [ + 'label' => 'Status', + 'states' => [ + 'deleted' => 'Excluído', + 'draft' => 'Rascunho', + 'published' => 'Publicado', + ], + ], + 'name' => [ + 'label' => 'Nome', + ], + 'brand' => [ + 'label' => 'Marca', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'stock' => [ + 'label' => 'Estoque', + ], + 'producttype' => [ + 'label' => 'Tipo de produto', + ], + ], + + 'actions' => [ + 'edit_status' => [ + 'label' => 'Atualizar status', + 'heading' => 'Atualizar status', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'brand' => [ + 'label' => 'Marca', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'producttype' => [ + 'label' => 'Tipo de produto', + ], + 'status' => [ + 'label' => 'Status', + 'options' => [ + 'published' => [ + 'label' => 'Publicado', + 'description' => 'Este produto ficará disponível em todos os canais e grupos de clientes habilitados', + ], + 'draft' => [ + 'label' => 'Rascunho', + 'description' => 'Este produto ficará oculto em todos os canais e grupos de clientes', + ], + ], + ], + 'tags' => [ + 'label' => 'Tags', + 'helper_text' => 'Separe tags pressionando Enter, Tab ou vírgula (,)', + ], + 'collections' => [ + 'label' => 'Coleções', + 'select_collection' => 'Selecione uma coleção', + ], + ], + + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilidade', + ], + 'edit' => [ + 'title' => 'Informações básicas', + ], + 'identifiers' => [ + 'label' => 'Identificadores do produto', + ], + 'inventory' => [ + 'label' => 'Estoque', + ], + 'pricing' => [ + 'form' => [ + 'tax_class_id' => [ + 'label' => 'Classe de imposto', + ], + 'tax_ref' => [ + 'label' => 'Referência de imposto', + 'helper_text' => 'Opcional, para integração com sistemas de terceiros.', + ], + ], + ], + 'shipping' => [ + 'label' => 'Envio', + ], + 'variants' => [ + 'label' => 'Variações', + ], + 'collections' => [ + 'label' => 'Coleções', + 'select_collection' => 'Selecione uma coleção', + ], + 'associations' => [ + 'label' => 'Associações de produto', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/productoption.php b/packages/admin/resources/lang/pt_BR/productoption.php new file mode 100644 index 0000000000..65cdf1db50 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/productoption.php @@ -0,0 +1,127 @@ + 'Opção de produto', + + 'plural_label' => 'Opções de produto', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'label' => [ + 'label' => 'Rótulo', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + 'shared' => [ + 'label' => 'Compartilhada', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'label' => [ + 'label' => 'Rótulo', + ], + 'handle' => [ + 'label' => 'Identificador', + ], + ], + + 'widgets' => [ + 'product-options' => [ + 'notifications' => [ + 'save-variants' => [ + 'success' => [ + 'title' => 'Variações de produto salvas', + ], + ], + ], + 'actions' => [ + 'cancel' => [ + 'label' => 'Cancelar', + ], + 'save-options' => [ + 'label' => 'Salvar opções', + ], + 'add-shared-option' => [ + 'label' => 'Adicionar opção compartilhada', + 'form' => [ + 'product_option' => [ + 'label' => 'Opção de produto', + ], + 'no_shared_components' => [ + 'label' => 'Não há opções compartilhadas disponíveis.', + ], + 'preselect' => [ + 'label' => 'Pré-selecionar todos os valores por padrão.', + ], + ], + ], + 'add-restricted-option' => [ + 'label' => 'Adicionar opção', + ], + ], + 'options-list' => [ + 'empty' => [ + 'heading' => 'Não há opções de produto configuradas', + 'description' => 'Adicione uma opção de produto compartilhada ou restrita para começar a gerar variações.', + ], + ], + 'options-table' => [ + 'title' => 'Opções de produto', + 'configure-options' => [ + 'label' => 'Configurar opções', + ], + 'table' => [ + 'option' => [ + 'label' => 'Opção', + ], + 'values' => [ + 'label' => 'Valores', + ], + ], + ], + 'variants-table' => [ + 'title' => 'Variações de produto', + 'actions' => [ + 'create' => [ + 'label' => 'Criar variação', + ], + 'edit' => [ + 'label' => 'Editar', + ], + 'delete' => [ + 'label' => 'Excluir', + ], + ], + 'empty' => [ + 'heading' => 'Nenhuma variação configurada', + ], + 'table' => [ + 'new' => [ + 'label' => 'NOVO', + ], + 'option' => [ + 'label' => 'Opção', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'price' => [ + 'label' => 'Preço', + ], + 'stock' => [ + 'label' => 'Estoque', + ], + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/producttype.php b/packages/admin/resources/lang/pt_BR/producttype.php new file mode 100644 index 0000000000..fd61067ef3 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/producttype.php @@ -0,0 +1,52 @@ + 'Tipo de produto', + + 'plural_label' => 'Tipos de produto', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'products_count' => [ + 'label' => 'Qtd. de produtos', + ], + 'product_attributes_count' => [ + 'label' => 'Atributos do produto', + ], + 'variant_attributes_count' => [ + 'label' => 'Atributos da variação', + ], + ], + + 'tabs' => [ + 'product_attributes' => [ + 'label' => 'Atributos do produto', + ], + 'variant_attributes' => [ + 'label' => 'Atributos da variação', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + ], + + 'attributes' => [ + 'no_groups' => 'Não há grupos de atributos disponíveis.', + 'no_attributes' => 'Não há atributos disponíveis.', + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Este tipo de produto não pode ser excluído pois há produtos associados.', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/productvariant.php b/packages/admin/resources/lang/pt_BR/productvariant.php new file mode 100644 index 0000000000..905f41fd1c --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/productvariant.php @@ -0,0 +1,105 @@ + 'Variação de produto', + 'plural_label' => 'Variações de produto', + 'pages' => [ + 'edit' => [ + 'title' => 'Informações básicas', + ], + 'media' => [ + 'title' => 'Mídia', + 'form' => [ + 'no_selection' => [ + 'label' => 'Você não possui uma imagem selecionada para esta variação.', + ], + 'no_media_available' => [ + 'label' => 'Não há mídia disponível neste produto.', + ], + 'images' => [ + 'label' => 'Imagem principal', + 'helper_text' => 'Selecione a imagem do produto que representa esta variação.', + ], + ], + ], + 'identifiers' => [ + 'title' => 'Identificadores', + ], + 'inventory' => [ + 'title' => 'Estoque', + ], + 'shipping' => [ + 'title' => 'Envio', + ], + ], + 'form' => [ + 'sku' => [ + 'label' => 'SKU', + ], + 'gtin' => [ + 'label' => 'GTIN (Global Trade Item Number)', + ], + 'mpn' => [ + 'label' => 'MPN (Manufacturer Part Number)', + ], + 'ean' => [ + 'label' => 'UPC/EAN', + ], + 'stock' => [ + 'label' => 'Em estoque', + ], + 'backorder' => [ + 'label' => 'Sob encomenda', + ], + 'purchasable' => [ + 'label' => 'Disponibilidade de compra', + 'options' => [ + 'always' => 'Sempre', + 'in_stock' => 'Em estoque', + 'in_stock_or_on_backorder' => 'Em estoque ou sob encomenda', + ], + ], + 'unit_quantity' => [ + 'label' => 'Quantidade por unidade', + 'helper_text' => 'Quantos itens individuais formam 1 unidade.', + ], + 'min_quantity' => [ + 'label' => 'Quantidade mínima', + 'helper_text' => 'Quantidade mínima da variação que pode ser comprada em uma única compra.', + ], + 'quantity_increment' => [ + 'label' => 'Incremento de quantidade', + 'helper_text' => 'A variação deve ser comprada em múltiplos desta quantidade.', + ], + 'tax_class_id' => [ + 'label' => 'Classe de imposto', + ], + 'shippable' => [ + 'label' => 'Enviável', + ], + 'length_value' => [ + 'label' => 'Comprimento', + ], + 'length_unit' => [ + 'label' => 'Unidade de comprimento', + ], + 'width_value' => [ + 'label' => 'Largura', + ], + 'width_unit' => [ + 'label' => 'Unidade de largura', + ], + 'height_value' => [ + 'label' => 'Altura', + ], + 'height_unit' => [ + 'label' => 'Unidade de altura', + ], + 'weight_value' => [ + 'label' => 'Peso', + ], + 'weight_unit' => [ + 'label' => 'Unidade de peso', + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/relationmanagers.php b/packages/admin/resources/lang/pt_BR/relationmanagers.php new file mode 100644 index 0000000000..078f9b6c2b --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/relationmanagers.php @@ -0,0 +1,285 @@ + [ + 'title' => 'Grupos de clientes', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar grupo de clientes', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'enabled' => [ + 'label' => 'Ativado', + ], + 'starts_at' => [ + 'label' => 'Data de início', + ], + 'ends_at' => [ + 'label' => 'Data de término', + ], + 'visible' => [ + 'label' => 'Visível', + ], + 'purchasable' => [ + 'label' => 'Disponível para compra', + ], + ], + 'table' => [ + 'description' => 'Associe grupos de clientes a este :type para determinar sua disponibilidade.', + 'name' => [ + 'label' => 'Nome', + ], + 'enabled' => [ + 'label' => 'Ativado', + ], + 'starts_at' => [ + 'label' => 'Data de início', + ], + 'ends_at' => [ + 'label' => 'Data de término', + ], + 'visible' => [ + 'label' => 'Visível', + ], + 'purchasable' => [ + 'label' => 'Disponível para compra', + ], + ], + ], + 'channels' => [ + 'title' => 'Canais', + 'actions' => [ + 'attach' => [ + 'label' => 'Agendar outro canal', + ], + ], + 'form' => [ + 'enabled' => [ + 'label' => 'Ativado', + 'helper_text_false' => 'Este canal não será ativado mesmo que uma data de início esteja definida.', + ], + 'starts_at' => [ + 'label' => 'Data de início', + 'helper_text' => 'Deixe em branco para ficar disponível a partir de qualquer data.', + ], + 'ends_at' => [ + 'label' => 'Data de término', + 'helper_text' => 'Deixe em branco para ficar disponível indefinidamente.', + ], + ], + 'table' => [ + 'description' => 'Defina quais canais estão ativados e agende a disponibilidade.', + 'name' => [ + 'label' => 'Nome', + ], + 'enabled' => [ + 'label' => 'Ativado', + ], + 'starts_at' => [ + 'label' => 'Data de início', + ], + 'ends_at' => [ + 'label' => 'Data de término', + ], + ], + ], + 'medias' => [ + 'title' => 'Mídia', + 'title_plural' => 'Mídia', + 'actions' => [ + 'attach' => [ + 'label' => 'Anexar mídia', + ], + 'create' => [ + 'label' => 'Criar mídia', + ], + 'detach' => [ + 'label' => 'Desanexar', + ], + 'view' => [ + 'label' => 'Visualizar', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'media' => [ + 'label' => 'Imagem', + ], + 'primary' => [ + 'label' => 'Principal', + ], + ], + 'table' => [ + 'image' => [ + 'label' => 'Imagem', + ], + 'file' => [ + 'label' => 'Arquivo', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'primary' => [ + 'label' => 'Principal', + ], + ], + 'all_media_attached' => 'Não há imagens de produtos disponíveis para anexar', + 'variant_description' => 'Anexe imagens de produto a esta variação', + ], + 'urls' => [ + 'title' => 'URL', + 'title_plural' => 'URLs', + 'actions' => [ + 'create' => [ + 'label' => 'Criar URL', + ], + ], + 'filters' => [ + 'language_id' => [ + 'label' => 'Idioma', + ], + ], + 'form' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Padrão', + ], + 'language' => [ + 'label' => 'Idioma', + ], + ], + 'table' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Padrão', + ], + 'language' => [ + 'label' => 'Idioma', + ], + ], + ], + 'customer_group_pricing' => [ + 'title' => 'Preços por grupo de clientes', + 'title_plural' => 'Preços por grupo de clientes', + 'table' => [ + 'heading' => 'Preços por grupo de clientes', + 'description' => 'Associe preços a grupos de clientes para determinar o preço do produto.', + 'empty_state' => [ + 'label' => 'Não existem preços por grupo de clientes.', + 'description' => 'Crie um preço para grupo de clientes para começar.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adicionar preço por grupo de clientes', + 'modal' => [ + 'heading' => 'Criar preço por grupo de clientes', + ], + ], + ], + ], + ], + 'pricing' => [ + 'title' => 'Preço', + 'title_plural' => 'Preços', + 'tab_name' => 'Faixas de preço', + 'table' => [ + 'heading' => 'Faixas de preço', + 'description' => 'Reduza o preço quando o cliente comprar em maiores quantidades.', + 'empty_state' => [ + 'label' => 'Não existem faixas de preço.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adicionar faixa de preço', + ], + ], + 'price' => [ + 'label' => 'Preço', + ], + 'customer_group' => [ + 'label' => 'Grupo de clientes', + 'placeholder' => 'Todos os grupos de clientes', + ], + 'min_quantity' => [ + 'label' => 'Quantidade mínima', + ], + 'currency' => [ + 'label' => 'Moeda', + ], + ], + 'form' => [ + 'price' => [ + 'label' => 'Preço', + 'helper_text' => 'Preço de compra, antes de descontos.', + ], + 'customer_group_id' => [ + 'label' => 'Grupo de clientes', + 'placeholder' => 'Todos os grupos de clientes', + 'helper_text' => 'Selecione a qual grupo de clientes aplicar este preço.', + ], + 'min_quantity' => [ + 'label' => 'Quantidade mínima', + 'helper_text' => 'Selecione a quantidade mínima para a qual este preço estará disponível.', + 'validation' => [ + 'unique' => 'Grupo de clientes e quantidade mínima devem ser únicos.', + ], + ], + 'currency_id' => [ + 'label' => 'Moeda', + 'helper_text' => 'Selecione a moeda para este preço.', + ], + 'compare_price' => [ + 'label' => 'Preço de comparação', + 'helper_text' => 'O preço original (ou preço sugerido) para comparação com o preço de compra.', + ], + 'basePrices' => [ + 'title' => 'Preços', + 'form' => [ + 'price' => [ + 'label' => 'Preço', + 'helper_text' => 'Preço de compra, antes de descontos.', + 'sync_price' => 'Preço sincronizado com a moeda padrão.', + ], + 'compare_price' => [ + 'label' => 'Preço de comparação', + 'helper_text' => 'O preço original (ou preço sugerido) para comparação com o preço de compra.', + ], + ], + 'tooltip' => 'Gerado automaticamente com base nas taxas de câmbio.', + ], + ], + ], + 'tax_rate_amounts' => [ + 'table' => [ + 'description' => '', + 'percentage' => [ + 'label' => 'Percentual', + ], + 'tax_class' => [ + 'label' => 'Classe de imposto', + ], + ], + ], + 'values' => [ + 'title' => 'Valores', + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'position' => [ + 'label' => 'Posição', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/staff.php b/packages/admin/resources/lang/pt_BR/staff.php new file mode 100644 index 0000000000..3ddda69733 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/staff.php @@ -0,0 +1,81 @@ + 'Equipe', + + 'plural_label' => 'Equipe', + + 'table' => [ + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'admin' => [ + 'badge' => 'Super Admin', + ], + ], + + 'form' => [ + 'first_name' => [ + 'label' => 'Nome', + ], + 'last_name' => [ + 'label' => 'Sobrenome', + ], + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Senha', + 'hint' => 'Redefinir senha', + ], + 'admin' => [ + 'label' => 'Super Admin', + 'helper' => 'Funções de super admin não podem ser alteradas no hub.', + ], + 'roles' => [ + 'label' => 'Funções', + 'helper' => ':roles têm acesso total', + ], + 'permissions' => [ + 'label' => 'Permissões', + ], + 'role' => [ + 'label' => 'Nome da função', + ], + ], + + 'action' => [ + 'acl' => [ + 'label' => 'Controle de acesso', + ], + 'add-role' => [ + 'label' => 'Adicionar função', + ], + 'delete-role' => [ + 'label' => 'Excluir função', + 'heading' => 'Excluir função: :role', + ], + ], + + 'acl' => [ + 'title' => 'Controle de acesso', + 'tooltip' => [ + 'roles-included' => 'Permissão incluída nas seguintes funções', + ], + 'notification' => [ + 'updated' => 'Atualizado', + 'error' => 'Erro', + 'no-role' => 'Função não registrada no Lunar', + 'no-permission' => 'Permissão não registrada no Lunar', + 'no-role-permission' => 'Função e permissão não registradas no Lunar', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/tag.php b/packages/admin/resources/lang/pt_BR/tag.php new file mode 100644 index 0000000000..b45f70b296 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/tag.php @@ -0,0 +1,21 @@ + 'Tag', + + 'plural_label' => 'Tags', + + 'table' => [ + 'value' => [ + 'label' => 'Valor', + ], + ], + + 'form' => [ + 'value' => [ + 'label' => 'Valor', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/tax.php b/packages/admin/resources/lang/pt_BR/tax.php new file mode 100644 index 0000000000..d220c56e54 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/tax.php @@ -0,0 +1,9 @@ + 'Imposto', + + 'plural_label' => 'Impostos', + +]; diff --git a/packages/admin/resources/lang/pt_BR/taxclass.php b/packages/admin/resources/lang/pt_BR/taxclass.php new file mode 100644 index 0000000000..215afc4a56 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/taxclass.php @@ -0,0 +1,32 @@ + 'Classe de imposto', + + 'plural_label' => 'Classes de imposto', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + 'delete' => [ + 'error' => [ + 'title' => 'Não é possível excluir a classe de imposto', + 'body' => 'Esta classe de imposto possui variações de produto associadas e não pode ser excluída.', + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/taxrate.php b/packages/admin/resources/lang/pt_BR/taxrate.php new file mode 100644 index 0000000000..adb9ce6937 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/taxrate.php @@ -0,0 +1,33 @@ + 'Taxa de imposto', + + 'plural_label' => 'Taxas de imposto', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'tax_zone' => [ + 'label' => 'Zona de imposto', + ], + 'priority' => [ + 'label' => 'Prioridade', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'priority' => [ + 'label' => 'Prioridade', + ], + 'tax_zone_id' => [ + 'label' => 'Zona de imposto', + ], + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/taxzone.php b/packages/admin/resources/lang/pt_BR/taxzone.php new file mode 100644 index 0000000000..55f5754ea0 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/taxzone.php @@ -0,0 +1,69 @@ + 'Zona de imposto', + + 'plural_label' => 'Zonas de imposto', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'zone_type' => [ + 'label' => 'Tipo de zona', + ], + 'active' => [ + 'label' => 'Ativa', + ], + 'default' => [ + 'label' => 'Padrão', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'zone_type' => [ + 'label' => 'Tipo de zona', + 'options' => [ + 'country' => 'Limitar a países', + 'states' => 'Limitar a estados', + 'postcodes' => 'Limitar a CEPs', + ], + ], + 'price_display' => [ + 'label' => 'Exibição de preço', + 'options' => [ + 'include_tax' => 'Incluir imposto', + 'exclude_tax' => 'Excluir imposto', + ], + ], + 'active' => [ + 'label' => 'Ativa', + ], + 'default' => [ + 'label' => 'Padrão', + ], + + 'zone_countries' => [ + 'label' => 'Países', + ], + + 'zone_country' => [ + 'label' => 'País', + ], + + 'zone_states' => [ + 'label' => 'Estados', + ], + + 'zone_postcodes' => [ + 'label' => 'CEPs', + 'helper' => 'Liste cada CEP em uma nova linha. Suporta curingas como NW*', + ], + + ], + +]; diff --git a/packages/admin/resources/lang/pt_BR/user.php b/packages/admin/resources/lang/pt_BR/user.php new file mode 100644 index 0000000000..e9fb25aa28 --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/user.php @@ -0,0 +1,29 @@ + 'Usuário', + + 'plural_label' => 'Usuários', + + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'email' => [ + 'label' => 'E-mail', + ], + ], + + 'form' => [ + 'email' => [ + 'label' => 'E-mail', + ], + 'password' => [ + 'label' => 'Nova senha', + ], + 'password_confirmation' => [ + 'label' => 'Confirmar nova senha', + ], + ], +]; diff --git a/packages/admin/resources/lang/pt_BR/widgets.php b/packages/admin/resources/lang/pt_BR/widgets.php new file mode 100644 index 0000000000..96ad1d535a --- /dev/null +++ b/packages/admin/resources/lang/pt_BR/widgets.php @@ -0,0 +1,118 @@ + [ + 'orders' => [ + 'order_stats_overview' => [ + 'stat_one' => [ + 'label' => 'Pedidos hoje', + 'increase' => ':percentage% de aumento em relação a :count ontem', + 'decrease' => ':percentage% de queda em relação a :count ontem', + 'neutral' => 'Sem mudança em relação a ontem', + ], + 'stat_two' => [ + 'label' => 'Pedidos nos últimos 7 dias', + 'increase' => ':percentage% de aumento em relação a :count no período anterior', + 'decrease' => ':percentage% de queda em relação a :count no período anterior', + 'neutral' => 'Sem mudança em relação ao período anterior', + ], + 'stat_three' => [ + 'label' => 'Pedidos nos últimos 30 dias', + 'increase' => ':percentage% de aumento em relação a :count no período anterior', + 'decrease' => ':percentage% de queda em relação a :count no período anterior', + 'neutral' => 'Sem mudança em relação ao período anterior', + ], + 'stat_four' => [ + 'label' => 'Vendas hoje', + 'increase' => ':percentage% de aumento em relação a :total ontem', + 'decrease' => ':percentage% de queda em relação a :total ontem', + 'neutral' => 'Sem mudança em relação a ontem', + ], + 'stat_five' => [ + 'label' => 'Vendas nos últimos 7 dias', + 'increase' => ':percentage% de aumento em relação a :total no período anterior', + 'decrease' => ':percentage% de queda em relação a :total no período anterior', + 'neutral' => 'Sem mudança em relação ao período anterior', + ], + 'stat_six' => [ + 'label' => 'Vendas nos últimos 30 dias', + 'increase' => ':percentage% de aumento em relação a :total no período anterior', + 'decrease' => ':percentage% de queda em relação a :total no período anterior', + 'neutral' => 'Sem mudança em relação ao período anterior', + ], + ], + 'order_totals_chart' => [ + 'heading' => 'Totais de pedidos do último ano', + 'series_one' => [ + 'label' => 'Este período', + ], + 'series_two' => [ + 'label' => 'Período anterior', + ], + 'yaxis' => [ + 'label' => 'Faturamento :currency', + ], + ], + 'order_sales_chart' => [ + 'heading' => 'Relatório de pedidos/vendas', + 'series_one' => [ + 'label' => 'Pedidos', + ], + 'series_two' => [ + 'label' => 'Receita', + ], + 'yaxis' => [ + 'series_one' => [ + 'label' => '# Pedidos', + ], + 'series_two' => [ + 'label' => 'Valor total', + ], + ], + ], + 'average_order_value' => [ + 'heading' => 'Ticket médio', + ], + 'new_returning_customers' => [ + 'heading' => 'Clientes novos x recorrentes', + 'series_one' => [ + 'label' => 'Clientes novos', + ], + 'series_two' => [ + 'label' => 'Clientes recorrentes', + ], + ], + 'popular_products' => [ + 'heading' => 'Mais vendidos (últimos 12 meses)', + 'description' => 'Os números se baseiam no número de vezes que um produto aparece em um pedido, e não na quantidade pedida.', + ], + 'latest_orders' => [ + 'heading' => 'Pedidos mais recentes', + ], + ], + ], + 'customer' => [ + 'stats_overview' => [ + 'total_orders' => [ + 'label' => 'Total de pedidos', + ], + 'avg_spend' => [ + 'label' => 'Gasto médio', + ], + 'total_spend' => [ + 'label' => 'Gasto total', + ], + ], + ], + 'variant_switcher' => [ + 'label' => 'Trocar variação', + 'table' => [ + 'sku' => [ + 'label' => 'SKU', + ], + 'values' => [ + 'label' => 'Valores', + ], + ], + ], +]; diff --git a/packages/core/resources/lang/pt_BR/base.php b/packages/core/resources/lang/pt_BR/base.php new file mode 100644 index 0000000000..5d46562d84 --- /dev/null +++ b/packages/core/resources/lang/pt_BR/base.php @@ -0,0 +1,9 @@ + [ + 'collection-titles' => [ + 'images' => 'Imagens', + ], + ], +]; diff --git a/packages/core/resources/lang/pt_BR/exceptions.php b/packages/core/resources/lang/pt_BR/exceptions.php new file mode 100644 index 0000000000..6089c1f419 --- /dev/null +++ b/packages/core/resources/lang/pt_BR/exceptions.php @@ -0,0 +1,21 @@ + 'O modelo ":class" não implementa a interface de compra (purchasable).', + 'cart_line_id_mismatch' => 'Esta linha de carrinho não pertence a este carrinho', + 'invalid_cart_line_quantity' => 'Quantidade esperada de pelo menos "1"; encontrado ":quantity".', + 'maximum_cart_line_quantity' => 'A quantidade não pode exceder :quantity.', + 'carts.invalid_action' => 'A ação do carrinho é inválida', + 'carts.shipping_missing' => 'É necessário um endereço de entrega', + 'carts.billing_missing' => 'É necessário um endereço de cobrança', + 'carts.billing_incomplete' => 'O endereço de cobrança está incompleto', + 'carts.order_exists' => 'Já existe um pedido para este carrinho', + 'carts.shipping_option_missing' => 'Opção de envio ausente', + 'missing_currency_price' => 'Não existe preço para a moeda ":currency"', + 'minimum_quantity' => 'Você deve adicionar no mínimo :quantity itens.', + 'quantity_increment' => 'A quantidade :quantity deve estar em incrementos de :increment', + 'fieldtype_missing' => 'FieldType ":class" não existe', + 'invalid_fieldtype' => 'A classe ":class" não implementa a interface FieldType.', + 'discounts.invalid_type' => 'A coleção deve conter apenas ":expected"; encontrado ":actual"', + 'disallow_multiple_cart_orders' => 'Carrinhos só podem ter um pedido associado a eles.', +]; diff --git a/packages/table-rate-shipping/resources/lang/pt_BR/plugin.php b/packages/table-rate-shipping/resources/lang/pt_BR/plugin.php new file mode 100644 index 0000000000..c0ca4022de --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/pt_BR/plugin.php @@ -0,0 +1,7 @@ + [ + 'group' => 'Envio', + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/pt_BR/relationmanagers.php b/packages/table-rate-shipping/resources/lang/pt_BR/relationmanagers.php new file mode 100644 index 0000000000..3a2e56f568 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/pt_BR/relationmanagers.php @@ -0,0 +1,80 @@ + [ + 'customer_groups' => [ + 'description' => 'Associe grupos de clientes a este método de envio para determinar sua disponibilidade.', + ], + ], + 'shipping_rates' => [ + 'title_plural' => 'Taxas de envio', + 'actions' => [ + 'create' => [ + 'label' => 'Criar taxa de envio', + ], + ], + 'notices' => [ + 'prices_incl_tax' => 'Todos os preços incluem imposto, o que será considerado ao calcular o gasto mínimo.', + 'prices_excl_tax' => 'Todos os preços excluem imposto; o gasto mínimo será baseado no subtotal do carrinho.', + ], + 'form' => [ + 'shipping_method_id' => [ + 'label' => 'Método de envio', + ], + 'price' => [ + 'label' => 'Preço', + ], + 'prices' => [ + 'label' => 'Faixas de preço', + 'repeater' => [ + 'customer_group_id' => [ + 'label' => 'Grupo de clientes', + 'placeholder' => 'Qualquer', + ], + 'currency_id' => [ + 'label' => 'Moeda', + ], + 'min_spend' => [ + 'label' => 'Gasto mín.', + ], + 'min_weight' => [ + 'label' => 'Peso mín.', + ], + 'price' => [ + 'label' => 'Preço', + ], + ], + ], + ], + 'table' => [ + 'shipping_method' => [ + 'label' => 'Método de envio', + ], + 'price' => [ + 'label' => 'Preço', + ], + 'price_breaks_count' => [ + 'label' => 'Faixas de preço', + ], + ], + ], + 'exclusions' => [ + 'title_plural' => 'Exclusões de envio', + 'form' => [ + 'purchasable' => [ + 'label' => 'Produto', + ], + ], + 'actions' => [ + 'create' => [ + 'label' => 'Adicionar lista de exclusões de envio', + ], + 'attach' => [ + 'label' => 'Adicionar lista de exclusões', + ], + 'detach' => [ + 'label' => 'Remover', + ], + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/pt_BR/shippingexclusionlist.php b/packages/table-rate-shipping/resources/lang/pt_BR/shippingexclusionlist.php new file mode 100644 index 0000000000..0429dc87b2 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/pt_BR/shippingexclusionlist.php @@ -0,0 +1,19 @@ + 'Lista de exclusões de envio', + 'label_plural' => 'Listas de exclusões de envio', + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'exclusions_count' => [ + 'label' => 'Qtd. de produtos', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/pt_BR/shippingmethod.php b/packages/table-rate-shipping/resources/lang/pt_BR/shippingmethod.php new file mode 100644 index 0000000000..7b4b7cd19f --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/pt_BR/shippingmethod.php @@ -0,0 +1,58 @@ + 'Métodos de envio', + 'label' => 'Método de envio', + 'form' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'description' => [ + 'label' => 'Descrição', + ], + 'code' => [ + 'label' => 'Código', + ], + 'cutoff' => [ + 'label' => 'Horário limite', + ], + 'charge_by' => [ + 'label' => 'Cobrar por', + 'options' => [ + 'cart_total' => 'Total do carrinho', + 'weight' => 'Peso', + ], + ], + 'driver' => [ + 'label' => 'Tipo', + 'options' => [ + 'ship-by' => 'Padrão', + 'collection' => 'Coleta', + ], + ], + 'stock_available' => [ + 'label' => 'Estoque de todos os itens do carrinho deve estar disponível', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'code' => [ + 'label' => 'Código', + ], + 'driver' => [ + 'label' => 'Tipo', + 'options' => [ + 'ship-by' => 'Padrão', + 'collection' => 'Coleta', + ], + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Disponibilidade', + 'customer_groups' => 'Este método de envio está indisponível para todos os grupos de clientes.', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/pt_BR/shippingzone.php b/packages/table-rate-shipping/resources/lang/pt_BR/shippingzone.php new file mode 100644 index 0000000000..81267c8598 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/pt_BR/shippingzone.php @@ -0,0 +1,50 @@ + 'Zona de envio', + 'label_plural' => 'Zonas de envio', + 'form' => [ + 'unrestricted' => [ + 'content' => 'Esta zona de envio não possui restrições e estará disponível para todos os clientes no checkout.', + ], + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'options' => [ + 'unrestricted' => 'Sem restrições', + 'countries' => 'Limitar a países', + 'states' => 'Limitar a estados / municípios', + 'postcodes' => 'Limitar a CEPs', + ], + ], + 'country' => [ + 'label' => 'País', + ], + 'states' => [ + 'label' => 'Estados', + ], + 'countries' => [ + 'label' => 'Estados', + ], + 'postcodes' => [ + 'label' => 'CEPs', + 'helper' => 'Liste cada CEP em uma nova linha. Suporta curingas como NW*', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Nome', + ], + 'type' => [ + 'label' => 'Tipo', + 'options' => [ + 'unrestricted' => 'Sem restrições', + 'countries' => 'Limitar a países', + 'states' => 'Limitar a estados / municípios', + 'postcodes' => 'Limitar a CEPs', + ], + ], + ], +]; From 43a737122e36e5c5fea939ca9bb5242da2c760cb Mon Sep 17 00:00:00 2001 From: Jona Goldman Date: Tue, 11 Nov 2025 11:53:15 -0300 Subject: [PATCH 13/50] Add missing translation files (#2304) Add missing translation files related to *taxes*. ## Summary by CodeRabbit * **New Features** * Added Tax resource translations for German, Spanish, French, Dutch, Polish, and Vietnamese (singular and plural labels). * Added German translations for the Tax Rate resource, including table columns (Name, Tax Zone, Priority) and form fields (Name, Priority, Tax Zone). * Improves multilingual support across the admin interface for tax-related sections. --- packages/admin/resources/lang/de/tax.php | 9 ++++++ packages/admin/resources/lang/de/taxrate.php | 33 ++++++++++++++++++++ packages/admin/resources/lang/es/tax.php | 9 ++++++ packages/admin/resources/lang/fr/tax.php | 9 ++++++ packages/admin/resources/lang/nl/tax.php | 9 ++++++ packages/admin/resources/lang/pl/tax.php | 9 ++++++ packages/admin/resources/lang/vi/tax.php | 9 ++++++ 7 files changed, 87 insertions(+) create mode 100644 packages/admin/resources/lang/de/tax.php create mode 100644 packages/admin/resources/lang/de/taxrate.php create mode 100644 packages/admin/resources/lang/es/tax.php create mode 100644 packages/admin/resources/lang/fr/tax.php create mode 100644 packages/admin/resources/lang/nl/tax.php create mode 100644 packages/admin/resources/lang/pl/tax.php create mode 100644 packages/admin/resources/lang/vi/tax.php diff --git a/packages/admin/resources/lang/de/tax.php b/packages/admin/resources/lang/de/tax.php new file mode 100644 index 0000000000..3de9113804 --- /dev/null +++ b/packages/admin/resources/lang/de/tax.php @@ -0,0 +1,9 @@ + 'Steuer', + + 'plural_label' => 'Steuern', + +]; diff --git a/packages/admin/resources/lang/de/taxrate.php b/packages/admin/resources/lang/de/taxrate.php new file mode 100644 index 0000000000..9363eaf449 --- /dev/null +++ b/packages/admin/resources/lang/de/taxrate.php @@ -0,0 +1,33 @@ + 'Steuersatz', + + 'plural_label' => 'Steuersätze', + + 'table' => [ + 'name' => [ + 'label' => 'Name', + ], + 'tax_zone' => [ + 'label' => 'Steuerzone', + ], + 'priority' => [ + 'label' => 'Priorität', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Name', + ], + 'priority' => [ + 'label' => 'Priorität', + ], + 'tax_zone_id' => [ + 'label' => 'Steuerzone', + ], + ], + +]; diff --git a/packages/admin/resources/lang/es/tax.php b/packages/admin/resources/lang/es/tax.php new file mode 100644 index 0000000000..fe230731ca --- /dev/null +++ b/packages/admin/resources/lang/es/tax.php @@ -0,0 +1,9 @@ + 'Impuesto', + + 'plural_label' => 'Impuestos', + +]; diff --git a/packages/admin/resources/lang/fr/tax.php b/packages/admin/resources/lang/fr/tax.php new file mode 100644 index 0000000000..cc480a4776 --- /dev/null +++ b/packages/admin/resources/lang/fr/tax.php @@ -0,0 +1,9 @@ + 'Taxe', + + 'plural_label' => 'Taxes', + +]; diff --git a/packages/admin/resources/lang/nl/tax.php b/packages/admin/resources/lang/nl/tax.php new file mode 100644 index 0000000000..72bee0f347 --- /dev/null +++ b/packages/admin/resources/lang/nl/tax.php @@ -0,0 +1,9 @@ + 'Belasting', + + 'plural_label' => 'Belastingen', + +]; diff --git a/packages/admin/resources/lang/pl/tax.php b/packages/admin/resources/lang/pl/tax.php new file mode 100644 index 0000000000..0000b987e9 --- /dev/null +++ b/packages/admin/resources/lang/pl/tax.php @@ -0,0 +1,9 @@ + 'Podatek', + + 'plural_label' => 'Podatki', + +]; diff --git a/packages/admin/resources/lang/vi/tax.php b/packages/admin/resources/lang/vi/tax.php new file mode 100644 index 0000000000..4e10fd586e --- /dev/null +++ b/packages/admin/resources/lang/vi/tax.php @@ -0,0 +1,9 @@ + 'Thuế', + + 'plural_label' => 'Thuế', + +]; From 4aaac8fe86d5cf337a48c81ac2b95bd1f4e613cf Mon Sep 17 00:00:00 2001 From: SecurID Date: Tue, 11 Nov 2025 15:55:41 +0100 Subject: [PATCH 14/50] feat: add translations for name label in create product option value (#2282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added these translations while I investigated this issue: #2263 ## Summary by CodeRabbit * **New Features** * Added “Values” section translations across multiple languages (de, en, es, fr, nl, pl, vi). * Introduced form label for the “Name” field in the Values section for all supported languages. * Enhanced German locale with a section title (“Werte”) and table column labels (“Name”, “Position”) for the Values list. * Improves multilingual consistency and clarity in the admin interface. --- .../admin/resources/lang/de/relationmanagers.php | 16 ++++++++++++++++ .../admin/resources/lang/en/relationmanagers.php | 5 +++++ .../admin/resources/lang/es/relationmanagers.php | 5 +++++ .../admin/resources/lang/fr/relationmanagers.php | 5 +++++ .../admin/resources/lang/nl/relationmanagers.php | 5 +++++ .../admin/resources/lang/pl/relationmanagers.php | 5 +++++ .../admin/resources/lang/vi/relationmanagers.php | 5 +++++ 7 files changed, 46 insertions(+) diff --git a/packages/admin/resources/lang/de/relationmanagers.php b/packages/admin/resources/lang/de/relationmanagers.php index daca16dbfe..8ea3a15502 100644 --- a/packages/admin/resources/lang/de/relationmanagers.php +++ b/packages/admin/resources/lang/de/relationmanagers.php @@ -212,4 +212,20 @@ ], ], ], + 'values' => [ + 'title' => 'Werte', + 'form' => [ + 'name' => [ + 'label' => 'Name', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Name', + ], + 'position' => [ + 'label' => 'Position', + ], + ], + ], ]; diff --git a/packages/admin/resources/lang/en/relationmanagers.php b/packages/admin/resources/lang/en/relationmanagers.php index bbaa0b3270..e9101309f5 100644 --- a/packages/admin/resources/lang/en/relationmanagers.php +++ b/packages/admin/resources/lang/en/relationmanagers.php @@ -272,6 +272,11 @@ ], 'values' => [ 'title' => 'Values', + 'form' => [ + 'name' => [ + 'label' => 'Name', + ], + ], 'table' => [ 'name' => [ 'label' => 'Name', diff --git a/packages/admin/resources/lang/es/relationmanagers.php b/packages/admin/resources/lang/es/relationmanagers.php index a470c8364b..991f3911ad 100644 --- a/packages/admin/resources/lang/es/relationmanagers.php +++ b/packages/admin/resources/lang/es/relationmanagers.php @@ -263,6 +263,11 @@ ], 'values' => [ 'title' => 'Valores', + 'form' => [ + 'name' => [ + 'label' => 'Nombre', + ], + ], 'table' => [ 'name' => [ 'label' => 'Nombre', diff --git a/packages/admin/resources/lang/fr/relationmanagers.php b/packages/admin/resources/lang/fr/relationmanagers.php index 93ce6df69f..419dbedb19 100644 --- a/packages/admin/resources/lang/fr/relationmanagers.php +++ b/packages/admin/resources/lang/fr/relationmanagers.php @@ -263,6 +263,11 @@ ], 'values' => [ 'title' => 'Valeurs', + 'form' => [ + 'name' => [ + 'label' => 'Nom', + ], + ], 'table' => [ 'name' => [ 'label' => 'Nom', diff --git a/packages/admin/resources/lang/nl/relationmanagers.php b/packages/admin/resources/lang/nl/relationmanagers.php index dc3e77a9c3..6d15eeeb91 100644 --- a/packages/admin/resources/lang/nl/relationmanagers.php +++ b/packages/admin/resources/lang/nl/relationmanagers.php @@ -263,6 +263,11 @@ ], 'values' => [ 'title' => 'Waarden', + 'form' => [ + 'name' => [ + 'label' => 'Naam', + ], + ], 'table' => [ 'name' => [ 'label' => 'Naam', diff --git a/packages/admin/resources/lang/pl/relationmanagers.php b/packages/admin/resources/lang/pl/relationmanagers.php index 54b3189b4b..1ef2bd82eb 100644 --- a/packages/admin/resources/lang/pl/relationmanagers.php +++ b/packages/admin/resources/lang/pl/relationmanagers.php @@ -226,6 +226,11 @@ ], 'values' => [ 'title' => 'Wartości', + 'form' => [ + 'name' => [ + 'label' => 'Nazwa', + ], + ], 'table' => [ 'name' => [ 'label' => 'Nazwa', diff --git a/packages/admin/resources/lang/vi/relationmanagers.php b/packages/admin/resources/lang/vi/relationmanagers.php index 2ecf6043e0..12000b6011 100644 --- a/packages/admin/resources/lang/vi/relationmanagers.php +++ b/packages/admin/resources/lang/vi/relationmanagers.php @@ -263,6 +263,11 @@ ], 'values' => [ 'title' => 'Giá trị', + 'form' => [ + 'name' => [ + 'label' => 'Tên', + ], + ], 'table' => [ 'name' => [ 'label' => 'Tên', From 5df0c311ec8a0b42d4e0f39073be2a46c8605f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Furkan=20=C3=87avdar?= Date: Tue, 11 Nov 2025 17:56:28 +0300 Subject: [PATCH 15/50] Add Turkish translations (#2271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds Turkish translation files to the project. ## Summary by CodeRabbit * **New Features** * Comprehensive Turkish (tr) locale added across the admin UI (resources, forms, tables, pages, widgets, relation managers) covering catalog, sales, settings and staff areas. * Turkish translations added for core exception messages. * Turkish locale support added for table‑rate shipping (navigation, shipping methods, zones, exclusion lists, relation managers). --- packages/admin/resources/lang/tr/actions.php | 52 +++ packages/admin/resources/lang/tr/activity.php | 29 ++ packages/admin/resources/lang/tr/address.php | 99 +++++ .../admin/resources/lang/tr/attribute.php | 55 +++ .../resources/lang/tr/attributegroup.php | 46 +++ packages/admin/resources/lang/tr/auth.php | 32 ++ packages/admin/resources/lang/tr/brand.php | 75 ++++ packages/admin/resources/lang/tr/channel.php | 39 ++ .../admin/resources/lang/tr/collection.php | 45 +++ .../resources/lang/tr/collectiongroup.php | 37 ++ .../admin/resources/lang/tr/components.php | 117 ++++++ packages/admin/resources/lang/tr/currency.php | 58 +++ packages/admin/resources/lang/tr/customer.php | 63 ++++ .../admin/resources/lang/tr/customergroup.php | 40 ++ packages/admin/resources/lang/tr/discount.php | 353 ++++++++++++++++++ .../admin/resources/lang/tr/fieldtypes.php | 72 ++++ packages/admin/resources/lang/tr/global.php | 12 + packages/admin/resources/lang/tr/language.php | 33 ++ packages/admin/resources/lang/tr/order.php | 305 +++++++++++++++ packages/admin/resources/lang/tr/product.php | 131 +++++++ .../admin/resources/lang/tr/productoption.php | 127 +++++++ .../admin/resources/lang/tr/producttype.php | 52 +++ .../resources/lang/tr/productvariant.php | 105 ++++++ .../resources/lang/tr/relationmanagers.php | 285 ++++++++++++++ packages/admin/resources/lang/tr/staff.php | 81 ++++ packages/admin/resources/lang/tr/tag.php | 21 ++ packages/admin/resources/lang/tr/tax.php | 9 + packages/admin/resources/lang/tr/taxclass.php | 32 ++ packages/admin/resources/lang/tr/taxrate.php | 33 ++ packages/admin/resources/lang/tr/taxzone.php | 69 ++++ packages/admin/resources/lang/tr/user.php | 29 ++ packages/admin/resources/lang/tr/widgets.php | 118 ++++++ packages/core/resources/lang/tr/base.php | 9 + .../core/resources/lang/tr/exceptions.php | 21 ++ .../resources/lang/tr/plugin.php | 7 + .../resources/lang/tr/relationmanagers.php | 80 ++++ .../lang/tr/shippingexclusionlist.php | 19 + .../resources/lang/tr/shippingmethod.php | 58 +++ .../resources/lang/tr/shippingzone.php | 50 +++ 39 files changed, 2898 insertions(+) create mode 100644 packages/admin/resources/lang/tr/actions.php create mode 100644 packages/admin/resources/lang/tr/activity.php create mode 100644 packages/admin/resources/lang/tr/address.php create mode 100644 packages/admin/resources/lang/tr/attribute.php create mode 100644 packages/admin/resources/lang/tr/attributegroup.php create mode 100644 packages/admin/resources/lang/tr/auth.php create mode 100644 packages/admin/resources/lang/tr/brand.php create mode 100644 packages/admin/resources/lang/tr/channel.php create mode 100644 packages/admin/resources/lang/tr/collection.php create mode 100644 packages/admin/resources/lang/tr/collectiongroup.php create mode 100644 packages/admin/resources/lang/tr/components.php create mode 100644 packages/admin/resources/lang/tr/currency.php create mode 100644 packages/admin/resources/lang/tr/customer.php create mode 100644 packages/admin/resources/lang/tr/customergroup.php create mode 100644 packages/admin/resources/lang/tr/discount.php create mode 100644 packages/admin/resources/lang/tr/fieldtypes.php create mode 100644 packages/admin/resources/lang/tr/global.php create mode 100644 packages/admin/resources/lang/tr/language.php create mode 100644 packages/admin/resources/lang/tr/order.php create mode 100644 packages/admin/resources/lang/tr/product.php create mode 100644 packages/admin/resources/lang/tr/productoption.php create mode 100644 packages/admin/resources/lang/tr/producttype.php create mode 100644 packages/admin/resources/lang/tr/productvariant.php create mode 100644 packages/admin/resources/lang/tr/relationmanagers.php create mode 100644 packages/admin/resources/lang/tr/staff.php create mode 100644 packages/admin/resources/lang/tr/tag.php create mode 100644 packages/admin/resources/lang/tr/tax.php create mode 100644 packages/admin/resources/lang/tr/taxclass.php create mode 100644 packages/admin/resources/lang/tr/taxrate.php create mode 100644 packages/admin/resources/lang/tr/taxzone.php create mode 100644 packages/admin/resources/lang/tr/user.php create mode 100644 packages/admin/resources/lang/tr/widgets.php create mode 100644 packages/core/resources/lang/tr/base.php create mode 100644 packages/core/resources/lang/tr/exceptions.php create mode 100644 packages/table-rate-shipping/resources/lang/tr/plugin.php create mode 100644 packages/table-rate-shipping/resources/lang/tr/relationmanagers.php create mode 100644 packages/table-rate-shipping/resources/lang/tr/shippingexclusionlist.php create mode 100644 packages/table-rate-shipping/resources/lang/tr/shippingmethod.php create mode 100644 packages/table-rate-shipping/resources/lang/tr/shippingzone.php diff --git a/packages/admin/resources/lang/tr/actions.php b/packages/admin/resources/lang/tr/actions.php new file mode 100644 index 0000000000..552e05afe5 --- /dev/null +++ b/packages/admin/resources/lang/tr/actions.php @@ -0,0 +1,52 @@ + [ + 'create_root' => [ + 'label' => 'Ana Koleksiyon Oluştur', + ], + 'create_child' => [ + 'label' => 'Alt Koleksiyon Oluştur', + ], + 'move' => [ + 'label' => 'Koleksiyonu Taşı', + ], + 'delete' => [ + 'label' => 'Sil', + 'notifications' => [ + 'cannot_delete' => [ + 'title' => 'Silinemiyor', + 'body' => 'Bu koleksiyonun alt koleksiyonları var ve silinemez.', + ], + ], + ], + ], + 'orders' => [ + 'update_status' => [ + 'label' => 'Durumu Güncelle', + 'wizard' => [ + 'step_one' => [ + 'label' => 'Durum', + ], + 'step_two' => [ + 'label' => 'E-posta & Bildirimler', + 'no_mailers' => 'Bu durum için kullanılabilir e-posta şablonu yok.', + ], + 'step_three' => [ + 'label' => 'Önizleme & Kaydet', + 'no_mailers' => 'Önizleme için seçilmiş e-posta şablonu yok.', + ], + ], + 'notification' => [ + 'label' => 'Sipariş durumu güncellendi', + ], + 'billing_email' => [ + 'label' => 'Fatura E-postası', + ], + 'shipping_email' => [ + 'label' => 'Kargo E-postası', + ], + ], + + ], +]; diff --git a/packages/admin/resources/lang/tr/activity.php b/packages/admin/resources/lang/tr/activity.php new file mode 100644 index 0000000000..333871b495 --- /dev/null +++ b/packages/admin/resources/lang/tr/activity.php @@ -0,0 +1,29 @@ + 'Aktivite', + + 'plural_label' => 'Aktiviteler', + + 'table' => [ + 'subject' => 'Konu', + 'description' => 'Açıklama', + 'log' => 'Log', + 'logged_at' => 'Kayıt Tarihi', + 'event' => 'Etkinlik', + 'logged_from' => 'Kayıt Başlangıcı', + 'logged_until' => 'Kayıt Bitişi', + ], + + 'form' => [ + 'causer_type' => 'Oluşturan Türü', + 'causer_id' => 'Oluşturan ID', + 'subject_type' => 'Konu türü', + 'subject_id' => 'Konu ID', + 'description' => 'Açıklama', + 'attributes' => 'Özellikler', + 'old' => 'Eski', + ], + +]; diff --git a/packages/admin/resources/lang/tr/address.php b/packages/admin/resources/lang/tr/address.php new file mode 100644 index 0000000000..47438e05c9 --- /dev/null +++ b/packages/admin/resources/lang/tr/address.php @@ -0,0 +1,99 @@ + 'Adres', + + 'plural_label' => 'Adresler', + + 'table' => [ + 'title' => [ + 'label' => 'Başlık', + ], + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'company_name' => [ + 'label' => 'Şirket Adı', + ], + 'tax_identifier' => [ + 'label' => 'Vergi Kimlik Numarası', + ], + 'line_one' => [ + 'label' => 'Adres', + ], + 'line_two' => [ + 'label' => 'Satır İki', + ], + 'line_three' => [ + 'label' => 'Satır Üç', + ], + 'city' => [ + 'label' => 'Şehir', + ], + 'country_id' => [ + 'label' => 'Ülke', + ], + 'state' => [ + 'label' => 'Eyalet', + ], + 'postcode' => [ + 'label' => 'Posta Kodu', + ], + 'contact_email' => [ + 'label' => 'İletişim E-postası', + ], + 'contact_phone' => [ + 'label' => 'İletişim Telefonu', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Başlık', + ], + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'company_name' => [ + 'label' => 'Şirket Adı', + ], + 'tax_identifier' => [ + 'label' => 'Vergi Kimlik Numarası', + ], + 'line_one' => [ + 'label' => 'Satır Bir', + ], + 'line_two' => [ + 'label' => 'Satır İki', + ], + 'line_three' => [ + 'label' => 'Satır Üç', + ], + 'city' => [ + 'label' => 'Şehir', + ], + 'country_id' => [ + 'label' => 'Ülke', + ], + 'state' => [ + 'label' => 'Eyalet', + ], + 'postcode' => [ + 'label' => 'Posta Kodu', + ], + 'contact_email' => [ + 'label' => 'İletişim E-postası', + ], + 'contact_phone' => [ + 'label' => 'İletişim Telefonu', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/attribute.php b/packages/admin/resources/lang/tr/attribute.php new file mode 100644 index 0000000000..5d82aac076 --- /dev/null +++ b/packages/admin/resources/lang/tr/attribute.php @@ -0,0 +1,55 @@ + 'Özellik', + + 'plural_label' => 'Özellikler', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'description' => [ + 'label' => 'Açıklama', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'type' => [ + 'label' => 'Tür', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tür', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'description' => [ + 'label' => 'Açıklama', + 'helper' => 'Alan altındaki yardım metnini görüntülemek için kullanın' + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'searchable' => [ + 'label' => 'Aranabilir', + ], + 'filterable' => [ + 'label' => 'Filtrelenebilir', + ], + 'required' => [ + 'label' => 'Gerekli', + ], + 'type' => [ + 'label' => 'Tür', + ], + 'validation_rules' => [ + 'label' => 'Doğrulama Kuralları', + 'helper' => 'Özellik alanı için kurallar, örnek: min:1|max:10|...', + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/attributegroup.php b/packages/admin/resources/lang/tr/attributegroup.php new file mode 100644 index 0000000000..89d5e3fda9 --- /dev/null +++ b/packages/admin/resources/lang/tr/attributegroup.php @@ -0,0 +1,46 @@ + 'Özellik Grubu', + + 'plural_label' => 'Özellik Grupları', + + 'table' => [ + 'attributable_type' => [ + 'label' => 'Tür', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'position' => [ + 'label' => 'Pozisyon', + ], + ], + + 'form' => [ + 'attributable_type' => [ + 'label' => 'Tür', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'position' => [ + 'label' => 'Pozisyon', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Bu özellik grubu silinemez çünkü ilişkili özellikler var.', + ], + ], + ], +]; \ No newline at end of file diff --git a/packages/admin/resources/lang/tr/auth.php b/packages/admin/resources/lang/tr/auth.php new file mode 100644 index 0000000000..54a32b20d9 --- /dev/null +++ b/packages/admin/resources/lang/tr/auth.php @@ -0,0 +1,32 @@ + 'Yönetici', + 'roles.admin.description' => 'Tam yetkili yönetici', + 'roles.staff.label' => 'Personel', + 'roles.staff.description' => 'Temel yetkili personel', + /** + * İzinler. + */ + 'permissions.settings.label' => 'Ayarlar', + 'permissions.settings.description' => 'Yönetim panelinin ayarlar alanına erişim sağlar', + 'permissions.settings:core.label' => 'Ana Ayarlar', + 'permissions.settings:core.description' => 'Kanallar, diller, para birimleri vb. temel mağaza ayarlarına erişim', + 'permissions.settings:manage-staff.label' => 'Personel Yönetimi', + 'permissions.settings:manage-staff.description' => 'Personelin diğer personeli düzenlemesine izin verir', + 'permissions.settings:manage-attributes.label' => 'Özellik Yönetimi', + 'permissions.settings:manage-attributes.description' => 'Personelin ek özellikler düzenlemesi ve oluşturmasına izin verir', + 'permissions.catalog:manage-products.label' => 'Ürün Yönetimi', + 'permissions.catalog:manage-products.description' => 'Personelin ürünleri, ürün türlerini ve markaları düzenlemesine izin verir', + 'permissions.catalog:manage-collections.label' => 'Koleksiyon Yönetimi', + 'permissions.catalog:manage-collections.description' => 'Personelin koleksiyonları ve gruplarını düzenlemesine izin verir', + 'permissions.sales:manage-orders.label' => 'Sipariş Yönetimi', + 'permissions.sales:manage-orders.description' => 'Personelin siparişleri yönetmesine izin verir', + 'permissions.sales:manage-customers.label' => 'Müşteri Yönetimi', + 'permissions.sales:manage-customers.description' => 'Personelin müşterileri yönetmesine izin verir', + 'permissions.sales:manage-discounts.label' => 'İndirim Yönetimi', + 'permissions.sales:manage-discounts.description' => 'Personelin indirimleri yönetmesine izin verir', +]; diff --git a/packages/admin/resources/lang/tr/brand.php b/packages/admin/resources/lang/tr/brand.php new file mode 100644 index 0000000000..e2a479b423 --- /dev/null +++ b/packages/admin/resources/lang/tr/brand.php @@ -0,0 +1,75 @@ + 'Marka', + + 'plural_label' => 'Markalar', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'products_count' => [ + 'label' => 'Ürün Sayısı', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Bu marka silinemez çünkü ilişkili ürünler var.', + ], + ], + ], + 'pages' => [ + 'edit' => [ + 'title' => 'Temel Bilgiler', + ], + 'products' => [ + 'label' => 'Ürünler', + 'actions' => [ + 'attach' => [ + 'label' => 'Ürün ilişkilendir', + 'form' => [ + 'record_id' => [ + 'label' => 'Ürün', + ], + ], + 'notification' => [ + 'success' => 'Ürün markaya eklendi', + ], + ], + 'detach' => [ + 'notification' => [ + 'success' => 'Ürün ilişkisi kaldırıldı.', + ], + ], + ], + ], + 'collections' => [ + 'label' => 'Koleksiyonlar', + 'table' => [ + 'header_actions' => [ + 'attach' => [ + 'record_select' => [ + 'placeholder' => 'Bir koleksiyon seçin', + ], + ], + ], + ], + 'actions' => [ + 'attach' => [ + 'label' => 'Koleksiyon ilişkilendir', + ], + ], + ], + ], + +]; \ No newline at end of file diff --git a/packages/admin/resources/lang/tr/channel.php b/packages/admin/resources/lang/tr/channel.php new file mode 100644 index 0000000000..ff56f1126a --- /dev/null +++ b/packages/admin/resources/lang/tr/channel.php @@ -0,0 +1,39 @@ + 'Kanal', + + 'plural_label' => 'Kanallar', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'url' => [ + 'label' => 'URL', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + +]; \ No newline at end of file diff --git a/packages/admin/resources/lang/tr/collection.php b/packages/admin/resources/lang/tr/collection.php new file mode 100644 index 0000000000..458fa637e2 --- /dev/null +++ b/packages/admin/resources/lang/tr/collection.php @@ -0,0 +1,45 @@ + 'Koleksiyon', + + 'plural_label' => 'Koleksiyonlar', + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + ], + + 'pages' => [ + 'children' => [ + 'label' => 'Alt Koleksiyonlar', + 'actions' => [ + 'create_child' => [ + 'label' => 'Alt Koleksiyon Oluştur', + ], + ], + 'table' => [ + 'children_count' => [ + 'label' => 'Alt Koleksiyon Sayısı', + ], + 'name' => [ + 'label' => 'Ad', + ], + ], + ], + 'edit' => [ + 'label' => 'Temel Bilgiler', + ], + 'products' => [ + 'label' => 'Ürünler', + 'actions' => [ + 'attach' => [ + 'label' => 'Ürün Ekle', + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/collectiongroup.php b/packages/admin/resources/lang/tr/collectiongroup.php new file mode 100644 index 0000000000..0669e45953 --- /dev/null +++ b/packages/admin/resources/lang/tr/collectiongroup.php @@ -0,0 +1,37 @@ + 'Koleksiyon Grubu', + + 'plural_label' => 'Koleksiyon Grupları', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'collections_count' => [ + 'label' => 'Koleksiyon Sayısı', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Bu koleksiyon grubu silinemez çünkü ilişkili koleksiyonlar var.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/components.php b/packages/admin/resources/lang/tr/components.php new file mode 100644 index 0000000000..daa0144ca4 --- /dev/null +++ b/packages/admin/resources/lang/tr/components.php @@ -0,0 +1,117 @@ + [ + 'notification' => [ + + 'updated' => 'Etiketler güncellendi', + + ], + ], + + 'activity-log' => [ + + 'input' => [ + + 'placeholder' => 'Yorum ekle', + + ], + + 'action' => [ + + 'add-comment' => 'Yorum Ekle', + + ], + + 'system' => 'Sistem', + + 'partials' => [ + 'orders' => [ + 'order_created' => 'Sipariş Oluşturuldu', + + 'status_change' => 'Durum güncellendi', + + 'capture' => ':last_four ile biten kart üzerinde :amount ödeme', + + 'authorized' => ':last_four ile biten kart üzerinde :amount yetkilendirildi', + + 'refund' => ':last_four ile biten kart üzerinde :amount iade', + + 'address' => ':type güncellendi', + + 'billingAddress' => 'Fatura adresi', + + 'shippingAddress' => 'Kargo adresi', + ], + + 'update' => [ + 'updated' => ':model güncellendi', + ], + + 'create' => [ + 'created' => ':model oluşturuldu', + ], + + 'tags' => [ + 'updated' => 'Etiketler güncellendi', + 'added' => 'Eklendi', + 'removed' => 'Kaldırıldı', + ], + ], + + 'notification' => [ + 'comment_added' => 'Yorum eklendi', + ], + + ], + + 'forms' => [ + 'youtube' => [ + 'helperText' => 'YouTube videosunun ID\'sini girin. örn. dQw4w9WgXcQ', + ], + ], + + 'collection-tree-view' => [ + 'actions' => [ + 'move' => [ + 'form' => [ + 'target_id' => [ + 'label' => 'Ana Koleksiyon', + ], + ], + ], + ], + 'notifications' => [ + 'collections-reordered' => [ + 'success' => 'Koleksiyonlar Yeniden Sıralandı', + ], + 'node-expanded' => [ + 'danger' => 'Koleksiyonlar yüklenemiyor', + ], + 'delete' => [ + 'danger' => 'Koleksiyon silinemiyor', + ], + ], + ], + + 'product-options-list' => [ + 'add-option' => [ + 'label' => 'Seçenek Ekle', + ], + 'delete-option' => [ + 'label' => 'Seçeneği Sil', + ], + 'remove-shared-option' => [ + 'label' => 'Paylaşılan Seçeneği Kaldır', + ], + 'add-value' => [ + 'label' => 'Başka Değer Ekle', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'values' => [ + 'label' => 'Değerler', + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/currency.php b/packages/admin/resources/lang/tr/currency.php new file mode 100644 index 0000000000..11b52d343c --- /dev/null +++ b/packages/admin/resources/lang/tr/currency.php @@ -0,0 +1,58 @@ + 'Para Birimi', + + 'plural_label' => 'Para Birimleri', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'exchange_rate' => [ + 'label' => 'Döviz Kuru', + ], + 'decimal_places' => [ + 'label' => 'Ondalık Basamak', + ], + 'enabled' => [ + 'label' => 'Etkin', + ], + 'sync_prices' => [ + 'label' => 'Fiyatları Senkronize Et', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'exchange_rate' => [ + 'label' => 'Döviz Kuru', + ], + 'decimal_places' => [ + 'label' => 'Ondalık Basamak', + ], + 'enabled' => [ + 'label' => 'Etkin', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + 'sync_prices' => [ + 'label' => 'Fiyatları Senkronize Et', + 'helper_text' => 'Bu para birimindeki fiyatları varsayılan para birimi ile senkronize tutun.', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/customer.php b/packages/admin/resources/lang/tr/customer.php new file mode 100644 index 0000000000..f37506048a --- /dev/null +++ b/packages/admin/resources/lang/tr/customer.php @@ -0,0 +1,63 @@ + 'Müşteri', + + 'plural_label' => 'Müşteriler', + + 'table' => [ + 'full_name' => [ + 'label' => 'Ad Soyad', + ], + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'title' => [ + 'label' => 'Ünvan', + ], + 'company_name' => [ + 'label' => 'Şirket Adı', + ], + 'tax_identifier' => [ + 'label' => 'Vergi Kimlik Numarası', + ], + 'account_reference' => [ + 'label' => 'Hesap Referansı', + ], + 'new' => [ + 'label' => 'Yeni', + ], + 'returning' => [ + 'label' => 'Geri Dönen', + ], + ], + + 'form' => [ + 'title' => [ + 'label' => 'Ünvan', + ], + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'company_name' => [ + 'label' => 'Şirket Adı', + ], + 'account_ref' => [ + 'label' => 'Hesap Referansı', + ], + 'tax_identifier' => [ + 'label' => 'Vergi Kimlik Numarası', + ], + 'customer_groups' => [ + 'label' => 'Müşteri Grupları', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/customergroup.php b/packages/admin/resources/lang/tr/customergroup.php new file mode 100644 index 0000000000..3aa960ef6a --- /dev/null +++ b/packages/admin/resources/lang/tr/customergroup.php @@ -0,0 +1,40 @@ + 'Müşteri Grubu', + + 'plural_label' => 'Müşteri Grupları', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Bu müşteri grubu silinemez çünkü ilişkili müşteriler var.', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/discount.php b/packages/admin/resources/lang/tr/discount.php new file mode 100644 index 0000000000..6533eae417 --- /dev/null +++ b/packages/admin/resources/lang/tr/discount.php @@ -0,0 +1,353 @@ + 'İndirimler', + 'label' => 'İndirim', + 'form' => [ + 'conditions' => [ + 'heading' => 'Koşullar', + ], + 'buy_x_get_y' => [ + 'heading' => 'X Al Y Kazan', + ], + 'amount_off' => [ + 'heading' => 'Sabit Tutar İndirimi' + ], + 'name' => [ + 'label' => 'İndirim Adı', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + ], + 'priority' => [ + 'label' => 'Öncelik', + 'helper_text' => 'Yüksek öncelikli indirimler önce uygulanır.', + 'options' => [ + 'low' => [ + 'label' => 'Düşük', + ], + 'medium' => [ + 'label' => 'Orta', + ], + 'high' => [ + 'label' => 'Yüksek', + ], + ], + ], + 'stop' => [ + 'label' => 'Bundan sonra diğer indirimlerin uygulanmasını durdur', + ], + 'coupon' => [ + 'label' => 'Kupon', + 'helper_text' => 'İndirimin uygulanması için gerekli kuponu girin, boş bırakılırsa otomatik olarak uygulanır.', + ], + 'max_uses' => [ + 'label' => 'Maksimum kullanım', + 'helper_text' => 'Sınırsız kullanım için boş bırakın.', + ], + 'max_uses_per_user' => [ + 'label' => 'Kullanıcı başına maksimum kullanım', + 'helper_text' => 'Sınırsız kullanım için boş bırakın.', + ], + 'minimum_cart_amount' => [ + 'label' => 'Minimum Sepet Tutarı', + ], + 'min_qty' => [ + 'label' => 'Ürün Miktarı', + 'helper_text' => 'İndirimin uygulanması için gerekli ürün adedini belirleyin.' + ], + 'reward_qty' => [ + 'label' => 'Ücretsiz ürün sayısı', + 'helper_text' => 'Her üründen kaçının indirimli olacağı.', + ], + 'max_reward_qty' => [ + 'label' => 'Maksimum ödül miktarı', + 'helper_text' => 'Kriterlere bakılmaksızın indirime tabi tutulabilecek maksimum ürün miktarı.', + ], + 'automatic_rewards' => [ + 'label' => 'Ödülleri otomatik ekle', + 'helper_text' => 'Ödül ürünlerini sepette yokken eklemek için açın.', + ], + 'fixed_value' => [ + 'label' => 'Sabit değer', + ], + 'percentage' => [ + 'label' => 'Yüzde', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'status' => [ + 'label' => 'Durum', + \Lunar\Models\Discount::ACTIVE => [ + 'label' => 'Aktif', + ], + \Lunar\Models\Discount::PENDING => [ + 'label' => 'Beklemede', + ], + \Lunar\Models\Discount::EXPIRED => [ + 'label' => 'Süresi Dolmuş', + ], + \Lunar\Models\Discount::SCHEDULED => [ + 'label' => 'Planlanmış', + ], + ], + 'type' => [ + 'label' => 'Tür', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + ], + 'created_at' => [ + 'label' => 'Oluşturulma Tarihi', + ], + 'coupon' => [ + 'label' => 'Kupon', + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Erişilebilirlik' + ], + 'edit' => [ + 'title' => 'Temel Bilgiler', + ], + 'limitations' => [ + 'label' => 'Sınırlamalar', + ], + ], + 'relationmanagers' => [ + 'collections' => [ + 'title' => 'Koleksiyonlar', + 'description' => 'Bu indirimin hangi koleksiyonlarla sınırlanacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Koleksiyon Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + 'customers' => [ + 'title' => 'Müşteriler', + 'description' => 'Bu indirimin hangi müşterilerle sınırlanacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Müşteri Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + ], + ], + 'brands' => [ + 'title' => 'Markalar', + 'description' => 'Bu indirimin hangi markalarla sınırlanacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Marka Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + 'products' => [ + 'title' => 'Ürünler', + 'description' => 'Bu indirimin hangi ürünlerle sınırlanacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Ürün Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + 'rewards' => [ + 'title' => 'Ödüller', + 'description' => 'Sepette mevcut olmaları ve yukarıdaki koşulların karşılanması durumunda hangi ürünlerin indirimli olacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Ödül Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + 'conditions' => [ + 'title' => 'Koşullar', + 'description' => 'İndirimin uygulanması için gereken koşulları seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Koşul Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + 'productvariants' => [ + 'title' => 'Ürün Varyantları', + 'description' => 'Bu indirimin hangi ürün varyantlarıyla sınırlanacağını seçin.', + 'actions' => [ + 'attach' => [ + 'label' => 'Ürün Varyantı Ekle', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'values' => [ + 'label' => 'Seçenek(ler)', + ], + ], + 'form' => [ + 'type' => [ + 'options' => [ + 'limitation' => [ + 'label' => 'Sınırlama', + ], + 'exclusion' => [ + 'label' => 'Hariç Tutma', + ], + ], + ], + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/fieldtypes.php b/packages/admin/resources/lang/tr/fieldtypes.php new file mode 100644 index 0000000000..4f5f8f3367 --- /dev/null +++ b/packages/admin/resources/lang/tr/fieldtypes.php @@ -0,0 +1,72 @@ + [ + 'label' => 'Açılır Menü', + 'form' => [ + 'lookups' => [ + 'label' => 'Arama Tablosu', + 'key_label' => 'Etiket', + 'value_label' => 'Değer', + ], + ], + ], + 'listfield' => [ + 'label' => 'Liste Alanı', + ], + 'text' => [ + 'label' => 'Metin', + 'form' => [ + 'richtext' => [ + 'label' => 'Zengin Metin', + ], + ], + ], + 'translatedtext' => [ + 'label' => 'Çevrilmiş Metin', + 'form' => [ + 'richtext' => [ + 'label' => 'Zengin Metin', + ], + 'locales' => 'Diller', + ], + ], + 'toggle' => [ + 'label' => 'Aç/Kapat' + ], + 'youtube' => [ + 'label' => 'YouTube', + ], + 'vimeo' => [ + 'label' => 'Vimeo', + ], + 'number' => [ + 'label' => 'Sayı', + 'form' => [ + 'min' => [ + 'label' => 'Min.', + ], + 'max' => [ + 'label' => 'Maks.', + ], + ], + ], + 'file' => [ + 'label' => 'Dosya', + 'form' => [ + 'file_types' => [ + 'label' => 'İzin Verilen Dosya Türleri', + 'placeholder' => 'Yeni MIME', + ], + 'multiple' => [ + 'label' => 'Birden Fazla Dosyaya İzin Ver', + ], + 'min_files' => [ + 'label' => 'Min. Dosya', + ], + 'max_files' => [ + 'label' => 'Maks. Dosya', + ], + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/global.php b/packages/admin/resources/lang/tr/global.php new file mode 100644 index 0000000000..8347b47843 --- /dev/null +++ b/packages/admin/resources/lang/tr/global.php @@ -0,0 +1,12 @@ + [ + 'catalog' => 'Katalog', + 'sales' => 'Satışlar', + 'reports' => 'Raporlar', + 'settings' => 'Ayarlar', + ], + +]; diff --git a/packages/admin/resources/lang/tr/language.php b/packages/admin/resources/lang/tr/language.php new file mode 100644 index 0000000000..81973de7d4 --- /dev/null +++ b/packages/admin/resources/lang/tr/language.php @@ -0,0 +1,33 @@ + 'Dil', + + 'plural_label' => 'Diller', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/order.php b/packages/admin/resources/lang/tr/order.php new file mode 100644 index 0000000000..56b05497d3 --- /dev/null +++ b/packages/admin/resources/lang/tr/order.php @@ -0,0 +1,305 @@ + 'Sipariş', + + 'plural_label' => 'Siparişler', + + 'breadcrumb' => [ + 'manage' => 'Yönet', + ], + + 'tabs' => [ + 'all' => 'Tümü', + ], + + 'transactions' => [ + 'capture' => 'Tahsil Edildi', + 'intent' => 'Ödeme Niyeti', + 'refund' => 'İade Edildi', + 'failed' => 'Başarısız', + ], + + 'table' => [ + 'status' => [ + 'label' => 'Durum', + ], + 'reference' => [ + 'label' => 'Referans', + ], + 'customer_reference' => [ + 'label' => 'Müşteri Referansı', + ], + 'customer' => [ + 'label' => 'Müşteri', + ], + 'tags' => [ + 'label' => 'Etiketler', + ], + 'postcode' => [ + 'label' => 'Posta Kodu', + ], + 'email' => [ + 'label' => 'E-posta', + 'copy_message' => 'E-posta adresi kopyalandı', + ], + 'phone' => [ + 'label' => 'Telefon', + ], + 'total' => [ + 'label' => 'Toplam', + ], + 'date' => [ + 'label' => 'Tarih', + ], + 'new_customer' => [ + 'label' => 'Müşteri Türü', + ], + 'placed_after' => [ + 'label' => 'Bu Tarihten Sonra' + ], + 'placed_before' => [ + 'label' => 'Bu Tarihten Önce' + ], + ], + + 'form' => [ + 'address' => [ + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'line_one' => [ + 'label' => 'Adres Satırı 1', + ], + 'line_two' => [ + 'label' => 'Adres Satırı 2', + ], + 'line_three' => [ + 'label' => 'Adres Satırı 3', + ], + 'company_name' => [ + 'label' => 'Şirket Adı', + ], + 'tax_identifier' => [ + 'label' => 'Vergi Kimlik Numarası', + ], + 'contact_phone' => [ + 'label' => 'Telefon', + ], + 'contact_email' => [ + 'label' => 'E-posta Adresi', + ], + 'city' => [ + 'label' => 'Şehir', + ], + 'state' => [ + 'label' => 'Eyalet / İl', + ], + 'postcode' => [ + 'label' => 'Posta Kodu', + ], + 'country_id' => [ + 'label' => 'Ülke', + ], + ], + + 'reference' => [ + 'label' => 'Referans', + ], + 'status' => [ + 'label' => 'Durum', + ], + 'transaction' => [ + 'label' => 'İşlem', + ], + 'amount' => [ + 'label' => 'Miktar', + + 'hint' => [ + 'less_than_total' => "Toplam işlem tutarından daha az bir miktar tahsil etmek üzeresiniz" + ], + ], + + 'notes' => [ + 'label' => 'Notlar', + ], + 'confirm' => [ + 'label' => 'Onayla', + + 'alert' => 'Onay gerekli', + + 'hint' => [ + 'capture' => 'Lütfen bu ödemeyi tahsil etmek istediğinizi onaylayın', + 'refund' => 'Lütfen bu tutarı iade etmek istediğinizi onaylayın.', + ], + ], + ], + + 'infolist' => [ + 'notes' => [ + 'label' => 'Notlar', + 'placeholder' => 'Bu siparişte not yok', + ], + 'delivery_instructions' => [ + 'label' => 'Teslimat Talimatları', + ], + 'shipping_total' => [ + 'label' => 'Kargo Toplamı', + ], + 'paid' => [ + 'label' => 'Ödendi', + ], + 'refund' => [ + 'label' => 'İade', + ], + 'unit_price' => [ + 'label' => 'Birim Fiyat', + ], + 'quantity' => [ + 'label' => 'Miktar', + ], + 'sub_total' => [ + 'label' => 'Ara Toplam', + ], + 'discount_total' => [ + 'label' => 'İndirim Toplamı', + ], + 'total' => [ + 'label' => 'Toplam', + ], + 'current_stock_level' => [ + 'message' => 'Mevcut Stok Seviyesi: :count', + ], + 'purchase_stock_level' => [ + 'message' => 'sipariş anında: :count', + ], + 'status' => [ + 'label' => 'Durum', + ], + 'reference' => [ + 'label' => 'Referans', + ], + 'customer_reference' => [ + 'label' => 'Müşteri Referansı', + ], + 'channel' => [ + 'label' => 'Kanal', + ], + 'date_created' => [ + 'label' => 'Oluşturulma Tarihi', + ], + 'date_placed' => [ + 'label' => 'Sipariş Tarihi', + ], + 'new_returning' => [ + 'label' => 'Yeni / Geri Dönen', + ], + 'new_customer' => [ + 'label' => 'Yeni Müşteri', + ], + 'returning_customer' => [ + 'label' => 'Geri Dönen Müşteri', + ], + 'shipping_address' => [ + 'label' => 'Kargo Adresi', + ], + 'billing_address' => [ + 'label' => 'Fatura Adresi', + ], + 'address_not_set' => [ + 'label' => 'Adres ayarlanmamış', + ], + 'billing_matches_shipping' => [ + 'label' => 'Kargo adresi ile aynı', + ], + 'additional_info' => [ + 'label' => 'Ek Bilgiler', + ], + 'no_additional_info' => [ + 'label' => 'Ek Bilgi Yok', + ], + 'tags' => [ + 'label' => 'Etiketler', + ], + 'timeline' => [ + 'label' => 'Zaman Çizelgesi', + ], + 'transactions' => [ + 'label' => 'İşlemler', + 'placeholder' => 'İşlem yok', + ], + 'alert' => [ + 'requires_capture' => 'Bu sipariş hala ödemenin tahsil edilmesini gerektiriyor.', + 'partially_refunded' => 'Bu sipariş kısmen iade edildi.', + 'refunded' => 'Bu sipariş iade edildi.', + ], + ], + + 'action' => [ + 'bulk_update_status' => [ + 'label' => 'Durumu Güncelle', + 'notification' => 'Siparişlerin durumu güncellendi', + ], + 'update_status' => [ + 'new_status' => [ + 'label' => 'Yeni durum', + ], + 'additional_content' => [ + 'label' => 'Ek içerik', + ], + 'additional_email_recipient' => [ + 'label' => 'Ek e-posta alıcısı', + 'placeholder' => 'opsiyonel', + ], + ], + 'download_order_pdf' => [ + 'label' => 'PDF İndir', + 'notification' => 'Sipariş PDF\'i indiriliyor', + ], + 'edit_address' => [ + 'label' => 'Düzenle', + + 'notification' => [ + 'error' => 'Hata', + + 'billing_address' => [ + 'saved' => 'Fatura adresi kaydedildi', + ], + + 'shipping_address' => [ + 'saved' => 'Kargo adresi kaydedildi', + ], + ], + ], + 'edit_tags' => [ + 'label' => 'Düzenle', + 'form' => [ + 'tags' => [ + 'label' => 'Etiketler', + 'helper_text' => 'Etiketleri Enter, Tab veya virgül (,) tuşuna basarak ayırın', + ], + ], + ], + 'capture_payment' => [ + 'label' => 'Ödemeyi Tahsil Et', + + 'notification' => [ + 'error' => 'Yakalama ile ilgili bir sorun oluştu', + 'success' => 'Yakalama başarılı', + ], + ], + 'refund_payment' => [ + 'label' => 'İade', + + 'notification' => [ + 'error' => 'İade ile ilgili bir sorun oluştu', + 'success' => 'İade başarılı', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/product.php b/packages/admin/resources/lang/tr/product.php new file mode 100644 index 0000000000..93d8780359 --- /dev/null +++ b/packages/admin/resources/lang/tr/product.php @@ -0,0 +1,131 @@ + 'Ürün', + + 'plural_label' => 'Ürünler', + + 'tabs' => [ + 'all' => 'Tümü', + ], + + 'status' => [ + 'unpublished' => [ + 'content' => 'Şu anda taslak durumunda olan bu ürün, tüm kanallarda ve müşteri gruplarında gizlidir.', + ], + 'availability' => [ + 'customer_groups' => 'Bu ürün şu anda tüm müşteri grupları için mevcut değil.', + 'channels' => 'Bu ürün şu anda tüm kanallar için mevcut değil.', + ], + ], + + 'table' => [ + 'status' => [ + 'label' => 'Durum', + 'states' => [ + 'deleted' => 'Silindi', + 'draft' => 'Taslak', + 'published' => 'Yayınlandı', + ], + ], + 'name' => [ + 'label' => 'Ad', + ], + 'brand' => [ + 'label' => 'Marka', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'stock' => [ + 'label' => 'Stok', + ], + 'producttype' => [ + 'label' => 'Ürün Türü', + ], + ], + + 'actions' => [ + 'edit_status' => [ + 'label' => 'Durumu Güncelle', + 'heading' => 'Durumu Güncelle', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'brand' => [ + 'label' => 'Marka', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'producttype' => [ + 'label' => 'Ürün Türü', + ], + 'status' => [ + 'label' => 'Durum', + 'options' => [ + 'published' => [ + 'label' => 'Yayınlandı', + 'description' => 'Bu ürün tüm etkin müşteri grupları ve kanallarda mevcut olacak', + ], + 'draft' => [ + 'label' => 'Taslak', + 'description' => 'Bu ürün tüm kanallarda ve müşteri gruplarında gizlenecek', + ], + ], + ], + 'tags' => [ + 'label' => 'Etiketler', + 'helper_text' => 'Etiketleri Enter, Tab veya virgül (,) tuşuna basarak ayırın', + ], + 'collections' => [ + 'label' => 'Koleksiyonlar', + 'select_collection' => 'Bir koleksiyon seçin', + ], + ], + + 'pages' => [ + 'availability' => [ + 'label' => 'Erişilebilirlik' + ], + 'edit' => [ + 'title' => 'Temel Bilgiler', + ], + 'identifiers' => [ + 'label' => 'Ürün Tanımlayıcıları', + ], + 'inventory' => [ + 'label' => 'Envanter', + ], + 'pricing' => [ + 'form' => [ + 'tax_class_id' => [ + 'label' => 'Vergi Sınıfı', + ], + 'tax_ref' => [ + 'label' => 'Vergi Referansı', + 'helper_text' => 'İsteğe bağlı, 3. parti sistemlerle entegrasyon için.', + ], + ], + ], + 'shipping' => [ + 'label' => 'Kargo', + ], + 'variants' => [ + 'label' => 'Varyantlar', + ], + 'collections' => [ + 'label' => 'Koleksiyonlar', + 'select_collection' => 'Bir koleksiyon seçin', + ], + 'associations' => [ + 'label' => 'Ürün İlişkileri', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/productoption.php b/packages/admin/resources/lang/tr/productoption.php new file mode 100644 index 0000000000..1b58f42cf7 --- /dev/null +++ b/packages/admin/resources/lang/tr/productoption.php @@ -0,0 +1,127 @@ + 'Ürün Seçeneği', + + 'plural_label' => 'Ürün Seçenekleri', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'label' => [ + 'label' => 'Etiket', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + 'shared' => [ + 'label' => 'Paylaşılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'label' => [ + 'label' => 'Etiket', + ], + 'handle' => [ + 'label' => 'Tanımlayıcı', + ], + ], + + 'widgets' => [ + 'product-options' => [ + 'notifications' => [ + 'save-variants' => [ + 'success' => [ + 'title' => 'Ürün Varyantları Kaydedildi', + ], + ], + ], + 'actions' => [ + 'cancel' => [ + 'label' => 'İptal', + ], + 'save-options' => [ + 'label' => 'Seçenekleri Kaydet', + ], + 'add-shared-option' => [ + 'label' => 'Paylaşılan Seçenek Ekle', + 'form' => [ + 'product_option' => [ + 'label' => 'Ürün Seçeneği', + ], + 'no_shared_components' => [ + 'label' => 'Mevcut paylaşılan seçenek yok.' + ], + 'preselect' => [ + 'label' => 'Varsayılan olarak tüm değerleri önceden seç.', + ], + ], + ], + 'add-restricted-option' => [ + 'label' => 'Seçenek Ekle', + ], + ], + 'options-list' => [ + 'empty' => [ + 'heading' => 'Yapılandırılmış ürün seçeneği yok', + 'description' => 'Varyant oluşturmaya başlamak için paylaşılan veya kısıtlanmış ürün seçeneği ekleyin.', + ], + ], + 'options-table' => [ + 'title' => 'Ürün Seçenekleri', + 'configure-options' => [ + 'label' => 'Seçenekleri Yapılandır', + ], + 'table' => [ + 'option' => [ + 'label' => 'Seçenek', + ], + 'values' => [ + 'label' => 'Değerler', + ], + ], + ], + 'variants-table' => [ + 'title' => 'Ürün Varyantları', + 'actions' => [ + 'create' => [ + 'label' => 'Varyant Oluştur', + ], + 'edit' => [ + 'label' => 'Düzenle', + ], + 'delete' => [ + 'label' => 'Sil', + ], + ], + 'empty' => [ + 'heading' => 'Yapılandırılmış Varyant Yok', + ], + 'table' => [ + 'new' => [ + 'label' => 'YENİ', + ], + 'option' => [ + 'label' => 'Seçenek', + ], + 'sku' => [ + 'label' => 'SKU', + ], + 'price' => [ + 'label' => 'Fiyat', + ], + 'stock' => [ + 'label' => 'Stok', + ], + ], + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/producttype.php b/packages/admin/resources/lang/tr/producttype.php new file mode 100644 index 0000000000..f44b138766 --- /dev/null +++ b/packages/admin/resources/lang/tr/producttype.php @@ -0,0 +1,52 @@ + 'Ürün Türü', + + 'plural_label' => 'Ürün Türleri', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'products_count' => [ + 'label' => 'Ürün sayısı', + ], + 'product_attributes_count' => [ + 'label' => 'Ürün Özellikleri', + ], + 'variant_attributes_count' => [ + 'label' => 'Varyant Özellikleri', + ], + ], + + 'tabs' => [ + 'product_attributes' => [ + 'label' => 'Ürün Özellikleri', + ], + 'variant_attributes' => [ + 'label' => 'Varyant Özellikleri', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + ], + + 'attributes' => [ + 'no_groups' => 'Mevcut özellik grubu yok.', + 'no_attributes' => 'Mevcut özellik yok.', + ], + + 'action' => [ + 'delete' => [ + 'notification' => [ + 'error_protected' => 'Bu ürün türü silinemez çünkü ilişkili ürünler var.', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/productvariant.php b/packages/admin/resources/lang/tr/productvariant.php new file mode 100644 index 0000000000..39db0dcee2 --- /dev/null +++ b/packages/admin/resources/lang/tr/productvariant.php @@ -0,0 +1,105 @@ + 'Ürün Varyantı', + 'plural_label' => 'Ürün Varyantları', + 'pages' => [ + 'edit' => [ + 'title' => 'Temel Bilgiler', + ], + 'media' => [ + 'title' => 'Medya', + 'form' => [ + 'no_selection' => [ + 'label' => 'Bu varyant için şu anda seçilmiş bir görseliniz yok.', + ], + 'no_media_available' => [ + 'label' => 'Bu üründe şu anda mevcut medya yok.', + ], + 'images' => [ + 'label' => 'Ana Görsel', + 'helper_text' => 'Bu varyantı temsil eden ürün görselini seçin.', + ], + ], + ], + 'identifiers' => [ + 'title' => 'Tanımlayıcılar', + ], + 'inventory' => [ + 'title' => 'Envanter', + ], + 'shipping' => [ + 'title' => 'Kargo', + ], + ], + 'form' => [ + 'sku' => [ + 'label' => 'SKU', + ], + 'gtin' => [ + 'label' => 'Küresel Ticaret Ürün Numarası (GTIN)', + ], + 'mpn' => [ + 'label' => 'Üretici Parça Numarası (MPN)', + ], + 'ean' => [ + 'label' => 'UPC/EAN', + ], + 'stock' => [ + 'label' => 'Stokta', + ], + 'backorder' => [ + 'label' => 'Sipariş Üzerine' + ], + 'purchasable' => [ + 'label' => 'Satın Alınabilirlik', + 'options' => [ + 'always' => 'Her Zaman', + 'in_stock' => 'Stokta', + 'in_stock_or_on_backorder' => 'Stokta veya Sipariş Üzerine' + ], + ], + 'unit_quantity' => [ + 'label' => 'Birim Miktarı', + 'helper_text' => '1 birimi kaç adet ürün oluşturur.', + ], + 'min_quantity' => [ + 'label' => 'Minimum Miktar', + 'helper_text' => 'Tek bir satın alma işleminde satın alınabilecek minimum ürün varyantı miktarı.', + ], + 'quantity_increment' => [ + 'label' => 'Miktar Artışı', + 'helper_text' => 'Ürün varyantı bu miktarın katları halinde satın alınmalıdır.', + ], + 'tax_class_id' => [ + 'label' => 'Vergi Sınıfı', + ], + 'shippable' => [ + 'label' => 'Kargo Edilebilir', + ], + 'length_value' => [ + 'label' => 'Uzunluk', + ], + 'length_unit' => [ + 'label' => 'Uzunluk Birimi', + ], + 'width_value' => [ + 'label' => 'Genişlik', + ], + 'width_unit' => [ + 'label' => 'Genişlik Birimi', + ], + 'height_value' => [ + 'label' => 'Yükseklik', + ], + 'height_unit' => [ + 'label' => 'Yükseklik Birimi', + ], + 'weight_value' => [ + 'label' => 'Ağırlık', + ], + 'weight_unit' => [ + 'label' => 'Ağırlık Birimi', + ], + ], +]; \ No newline at end of file diff --git a/packages/admin/resources/lang/tr/relationmanagers.php b/packages/admin/resources/lang/tr/relationmanagers.php new file mode 100644 index 0000000000..d6747686a7 --- /dev/null +++ b/packages/admin/resources/lang/tr/relationmanagers.php @@ -0,0 +1,285 @@ + [ + 'title' => 'Müşteri Grupları', + 'actions' => [ + 'attach' => [ + 'label' => 'Müşteri Grubu Ekle', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'enabled' => [ + 'label' => 'Etkin', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + ], + 'visible' => [ + 'label' => 'Görünür', + ], + 'purchasable' => [ + 'label' => 'Satın Alınabilir', + ], + ], + 'table' => [ + 'description' => 'Müşteri gruplarını bu :type ile ilişkilendirerek kullanılabilirliğini belirleyin.', + 'name' => [ + 'label' => 'Ad', + ], + 'enabled' => [ + 'label' => 'Etkin', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + ], + 'visible' => [ + 'label' => 'Görünür', + ], + 'purchasable' => [ + 'label' => 'Satın Alınabilir', + ], + ], + ], + 'channels' => [ + 'title' => 'Kanallar', + 'actions' => [ + 'attach' => [ + 'label' => 'Başka Bir Kanal Zamanla', + ], + ], + 'form' => [ + 'enabled' => [ + 'label' => 'Etkin', + 'helper_text_false' => 'Bu kanal, başlangıç tarihi mevcut olsa bile etkinleştirilmeyecek.', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + 'helper_text' => 'Herhangi bir tarihten itibaren kullanılabilir olması için boş bırakın.', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + 'helper_text' => 'Süresiz olarak kullanılabilir olması için boş bırakın.', + ], + ], + 'table' => [ + 'description' => 'Hangi kanalların etkin olduğunu belirleyin ve kullanılabilirliği zamanlayin.', + 'name' => [ + 'label' => 'Ad', + ], + 'enabled' => [ + 'label' => 'Etkin', + ], + 'starts_at' => [ + 'label' => 'Başlangıç Tarihi', + ], + 'ends_at' => [ + 'label' => 'Bitiş Tarihi', + ], + ], + ], + 'medias' => [ + 'title' => 'Medya', + 'title_plural' => 'Medya', + 'actions' => [ + 'attach' => [ + 'label' => 'Medya Ekle', + ], + 'create' => [ + 'label' => 'Medya Oluştur', + ], + 'detach' => [ + 'label' => 'Ayır', + ], + 'view' => [ + 'label' => 'Görüntüle', + ], + ], + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'media' => [ + 'label' => 'Görsel', + ], + 'primary' => [ + 'label' => 'Ana', + ], + ], + 'table' => [ + 'image' => [ + 'label' => 'Görsel', + ], + 'file' => [ + 'label' => 'Dosya', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'primary' => [ + 'label' => 'Ana', + ], + ], + 'all_media_attached' => 'Eklenecek ürün görseli yok', + 'variant_description' => 'Bu varyanta ürün görsellerini ekleyin', + ], + 'urls' => [ + 'title' => 'URL', + 'title_plural' => 'URL\'ler', + 'actions' => [ + 'create' => [ + 'label' => 'URL Oluştur', + ], + ], + 'filters' => [ + 'language_id' => [ + 'label' => 'Dil', + ], + ], + 'form' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + 'language' => [ + 'label' => 'Dil', + ], + ], + 'table' => [ + 'slug' => [ + 'label' => 'Slug', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + 'language' => [ + 'label' => 'Dil', + ], + ], + ], + 'customer_group_pricing' => [ + 'title' => 'Müşteri Grubu Fiyatlandırması', + 'title_plural' => 'Müşteri Grubu Fiyatlandırması', + 'table' => [ + 'heading' => 'Müşteri Grubu Fiyatlandırması', + 'description' => 'Ürün fiyatını belirlemek için müşteri gruplarına fiyat ilişkilendirin.', + 'empty_state' => [ + 'label' => 'Müşteri grubu fiyatlandırması yok.', + 'description' => 'Başlamak için bir müşteri grubu fiyatı oluşturun.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Müşteri Grubu Fiyatı Ekle', + 'modal' => [ + 'heading' => 'Müşteri Grubu Fiyatı Oluştur', + ], + ], + ], + ], + ], + 'pricing' => [ + 'title' => 'Fiyatlandırma', + 'title_plural' => 'Fiyatlandırma', + 'tab_name' => 'Basamaklı Fiyatlandırma', + 'table' => [ + 'heading' => 'Basamaklı Fiyatlandırma', + 'description' => 'Müşteri daha büyük miktarlarda satın aldığında fiyatı düşürün.', + 'empty_state' => [ + 'label' => 'Basamaklı fiyatlandırma yok.', + ], + 'actions' => [ + 'create' => [ + 'label' => 'Basamaklı Fiyat Ekle', + ], + ], + 'price' => [ + 'label' => 'Fiyat', + ], + 'customer_group' => [ + 'label' => 'Müşteri Grubu', + 'placeholder' => 'Tüm Müşteri Grupları', + ], + 'min_quantity' => [ + 'label' => 'Minimum Miktar', + ], + 'currency' => [ + 'label' => 'Para Birimi', + ], + ], + 'form' => [ + 'price' => [ + 'label' => 'Fiyat', + 'helper_text' => 'İndirimlerden önce satın alma fiyatı.', + ], + 'customer_group_id' => [ + 'label' => 'Müşteri Grubu', + 'placeholder' => 'Tüm Müşteri Grupları', + 'helper_text' => 'Bu fiyatın uygulanacağı müşteri grubunu seçin.', + ], + 'min_quantity' => [ + 'label' => 'Minimum Miktar', + 'helper_text' => 'Bu fiyatın geçerli olacağı minimum miktarı seçin.', + 'validation' => [ + 'unique' => 'Müşteri Grubu ve Minimum Miktar benzersiz olmalıdır.', + ], + ], + 'currency_id' => [ + 'label' => 'Para Birimi', + 'helper_text' => 'Bu fiyat için para birimini seçin.', + ], + 'compare_price' => [ + 'label' => 'Karşılaştırma Fiyatı', + 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).' + ], + 'basePrices' => [ + 'title' => 'Fiyatlar', + 'form' => [ + 'price' => [ + 'label' => 'Fiyat', + 'helper_text' => 'İndirimlerden önce satın alma fiyatı.', + 'sync_price' => 'Fiyat varsayılan para birimi ile senkronize edildi.', + ], + 'compare_price' => [ + 'label' => 'Karşılaştırma Fiyatı', + 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).' + ], + ], + 'tooltip' => 'Döviz kurlarına göre otomatik olarak oluşturuldu.', + ], + ], + ], + 'tax_rate_amounts' => [ + 'table' => [ + 'description' => '', + 'percentage' => [ + 'label' => 'Yüzde', + ], + 'tax_class' => [ + 'label' => 'Vergi Sınıfı', + ], + ], + ], + 'values' => [ + 'title' => 'Değerler', + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'position' => [ + 'label' => 'Pozisyon', + ], + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/staff.php b/packages/admin/resources/lang/tr/staff.php new file mode 100644 index 0000000000..6d7c3691db --- /dev/null +++ b/packages/admin/resources/lang/tr/staff.php @@ -0,0 +1,81 @@ + 'Personel', + + 'plural_label' => 'Personel', + + 'table' => [ + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'email' => [ + 'label' => 'E-posta', + ], + 'admin' => [ + 'badge' => 'Süper Yönetici', + ], + ], + + 'form' => [ + 'first_name' => [ + 'label' => 'Ad', + ], + 'last_name' => [ + 'label' => 'Soyad', + ], + 'email' => [ + 'label' => 'E-posta', + ], + 'password' => [ + 'label' => 'Şifre', + 'hint' => 'Şifreyi sıfırla', + ], + 'admin' => [ + 'label' => 'Süper Yönetici', + 'helper' => 'Süper yönetici rolleri yönetim panelinde değiştirilemez.' + ], + 'roles' => [ + 'label' => 'Roller', + 'helper' => ':roles tam erişime sahip', + ], + 'permissions' => [ + 'label' => 'İzinler', + ], + 'role' => [ + 'label' => 'Rol Adı', + ], + ], + + 'action' => [ + 'acl' => [ + 'label' => 'Erişim Kontrolü', + ], + 'add-role' => [ + 'label' => 'Rol Ekle', + ], + 'delete-role' => [ + 'label' => 'Rolü Sil', + 'heading' => 'Rolü sil: :role', + ], + ], + + 'acl' => [ + 'title' => 'Erişim Kontrolü', + 'tooltip' => [ + 'roles-included' => 'İzin şu rollere dahildir', + ], + 'notification' => [ + 'updated' => 'Güncellendi', + 'error' => 'Hata', + 'no-role' => 'Rol Lunar\'da kayıtlı değil', + 'no-permission' => 'İzin Lunar\'da kayıtlı değil', + 'no-role-permission' => 'Rol ve İzin Lunar\'da kayıtlı değil', + ], + ], + +]; \ No newline at end of file diff --git a/packages/admin/resources/lang/tr/tag.php b/packages/admin/resources/lang/tr/tag.php new file mode 100644 index 0000000000..8e9701d19f --- /dev/null +++ b/packages/admin/resources/lang/tr/tag.php @@ -0,0 +1,21 @@ + 'Etiket', + + 'plural_label' => 'Etiketler', + + 'table' => [ + 'value' => [ + 'label' => 'Değer', + ], + ], + + 'form' => [ + 'value' => [ + 'label' => 'Değer', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/tax.php b/packages/admin/resources/lang/tr/tax.php new file mode 100644 index 0000000000..8904313b47 --- /dev/null +++ b/packages/admin/resources/lang/tr/tax.php @@ -0,0 +1,9 @@ + 'Vergi', + + 'plural_label' => 'Vergiler', + +]; diff --git a/packages/admin/resources/lang/tr/taxclass.php b/packages/admin/resources/lang/tr/taxclass.php new file mode 100644 index 0000000000..c40413fdd9 --- /dev/null +++ b/packages/admin/resources/lang/tr/taxclass.php @@ -0,0 +1,32 @@ + 'Vergi Sınıfı', + + 'plural_label' => 'Vergi Sınıfları', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + 'delete' => [ + 'error' => [ + 'title' => 'Vergi sınıfı silinemez', + 'body' => 'Bu vergi sınıfının ilişkili ürün varyantları var ve silinemez.', + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/taxrate.php b/packages/admin/resources/lang/tr/taxrate.php new file mode 100644 index 0000000000..643ce27f66 --- /dev/null +++ b/packages/admin/resources/lang/tr/taxrate.php @@ -0,0 +1,33 @@ + 'Vergi Oranı', + + 'plural_label' => 'Vergi Oranları', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'tax_zone' => [ + 'label' => 'Vergi Bölgesi', + ], + 'priority' => [ + 'label' => 'Öncelik', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'priority' => [ + 'label' => 'Öncelik', + ], + 'tax_zone_id' => [ + 'label' => 'Vergi Bölgesi', + ], + ], + +]; diff --git a/packages/admin/resources/lang/tr/taxzone.php b/packages/admin/resources/lang/tr/taxzone.php new file mode 100644 index 0000000000..230cc39198 --- /dev/null +++ b/packages/admin/resources/lang/tr/taxzone.php @@ -0,0 +1,69 @@ + 'Vergi Bölgesi', + + 'plural_label' => 'Vergi Bölgeleri', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'zone_type' => [ + 'label' => 'Bölge Türü', + ], + 'active' => [ + 'label' => 'Aktif', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + ], + + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'zone_type' => [ + 'label' => 'Bölge Türü', + 'options' => [ + 'country' => 'Ülkelerle Sınırla', + 'states' => 'Eyaletlerle Sınırla', + 'postcodes' => 'Posta Kodlarıyla Sınırla', + ], + ], + 'price_display' => [ + 'label' => 'Fiyat Gösterimi', + 'options' => [ + 'include_tax' => 'Vergi Dahil', + 'exclude_tax' => 'Vergi Hariç', + ], + ], + 'active' => [ + 'label' => 'Aktif', + ], + 'default' => [ + 'label' => 'Varsayılan', + ], + + 'zone_countries' => [ + 'label' => 'Ülkeler', + ], + + 'zone_country' => [ + 'label' => 'Ülke', + ], + + 'zone_states' => [ + 'label' => 'Eyaletler', + ], + + 'zone_postcodes' => [ + 'label' => 'Posta Kodları', + 'helper' => 'Her posta kodunu yeni bir satırda listeleyin. NW* gibi joker karakterleri destekler', + ], + + ], + +]; diff --git a/packages/admin/resources/lang/tr/user.php b/packages/admin/resources/lang/tr/user.php new file mode 100644 index 0000000000..9400eacb46 --- /dev/null +++ b/packages/admin/resources/lang/tr/user.php @@ -0,0 +1,29 @@ + 'Kullanıcı', + + 'plural_label' => 'Kullanıcılar', + + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'email' => [ + 'label' => 'E-posta', + ], + ], + + 'form' => [ + 'email' => [ + 'label' => 'E-posta', + ], + 'password' => [ + 'label' => 'Yeni Şifre', + ], + 'password_confirmation' => [ + 'label' => 'Yeni Şifreyi Onayla', + ], + ], +]; diff --git a/packages/admin/resources/lang/tr/widgets.php b/packages/admin/resources/lang/tr/widgets.php new file mode 100644 index 0000000000..5746f5453b --- /dev/null +++ b/packages/admin/resources/lang/tr/widgets.php @@ -0,0 +1,118 @@ + [ + 'orders' => [ + 'order_stats_overview' => [ + 'stat_one' => [ + 'label' => 'Bugünkü siparişler', + 'increase' => 'Düne göre %:percentage artış (Dün: :count)', + 'decrease' => 'Düne göre %:percentage azalış (Dün: :count)', + 'neutral' => 'Düne göre değişiklik yok', + ], + 'stat_two' => [ + 'label' => 'Son 7 günün siparişleri', + 'increase' => 'Önceki döneme göre %:percentage artış (Önceki: :count)', + 'decrease' => 'Önceki döneme göre %:percentage azalış (Önceki: :count)', + 'neutral' => 'Önceki döneme göre değişiklik yok', + ], + 'stat_three' => [ + 'label' => 'Son 30 günün siparişleri', + 'increase' => 'Önceki döneme göre %:percentage artış (Önceki: :count)', + 'decrease' => 'Önceki döneme göre %:percentage azalış (Önceki: :count)', + 'neutral' => 'Önceki döneme göre değişiklik yok', + ], + 'stat_four' => [ + 'label' => 'Bugünkü satışlar', + 'increase' => 'Düne göre %:percentage artış (Dün: :total)', + 'decrease' => 'Düne göre %:percentage azalış (Dün: :total)', + 'neutral' => 'Düne göre değişiklik yok', + ], + 'stat_five' => [ + 'label' => 'Son 7 günün satışları', + 'increase' => 'Önceki döneme göre %:percentage artış (Önceki: :total)', + 'decrease' => 'Önceki döneme göre %:percentage azalış (Önceki: :total)', + 'neutral' => 'Önceki döneme göre değişiklik yok', + ], + 'stat_six' => [ + 'label' => 'Son 30 günün satışları', + 'increase' => 'Önceki döneme göre %:percentage artış (Önceki: :total)', + 'decrease' => 'Önceki döneme göre %:percentage azalış (Önceki: :total)', + 'neutral' => 'Önceki döneme göre değişiklik yok', + ], + ], + 'order_totals_chart' => [ + 'heading' => 'Geçen yılın sipariş toplamları', + 'series_one' => [ + 'label' => 'Bu Dönem', + ], + 'series_two' => [ + 'label' => 'Önceki Dönem', + ], + 'yaxis' => [ + 'label' => 'Ciro :currency', + ], + ], + 'order_sales_chart' => [ + 'heading' => 'Siparişler / Satışlar Raporu', + 'series_one' => [ + 'label' => 'Siparişler', + ], + 'series_two' => [ + 'label' => 'Gelir', + ], + 'yaxis' => [ + 'series_one' => [ + 'label' => '# Siparişler', + ], + 'series_two' => [ + 'label' => 'Toplam Değer', + ], + ], + ], + 'average_order_value' => [ + 'heading' => 'Ortalama Sipariş Değeri', + ], + 'new_returning_customers' => [ + 'heading' => 'Yeni vs Geri Dönen Müşteriler', + 'series_one' => [ + 'label' => 'Yeni Müşteriler', + ], + 'series_two' => [ + 'label' => 'Geri Dönen Müşteriler', + ], + ], + 'popular_products' => [ + 'heading' => 'En çok satanlar (son 12 ay)', + 'description' => 'Bu rakamlar bir ürünün siparişte kaç kez göründüğüne dayanır, sipariş edilen miktara değil.', + ], + 'latest_orders' => [ + 'heading' => 'Son siparişler', + ], + ], + ], + 'customer' => [ + 'stats_overview' => [ + 'total_orders' => [ + 'label' => 'Toplam siparişler', + ], + 'avg_spend' => [ + 'label' => 'Ort. Harcama', + ], + 'total_spend' => [ + 'label' => 'Toplam Harcama', + ], + ], + ], + 'variant_switcher' => [ + 'label' => 'Varyant Değiştir', + 'table' => [ + 'sku' => [ + 'label' => 'SKU', + ], + 'values' => [ + 'label' => 'Değerler', + ], + ], + ], +]; diff --git a/packages/core/resources/lang/tr/base.php b/packages/core/resources/lang/tr/base.php new file mode 100644 index 0000000000..4b9aa0f86a --- /dev/null +++ b/packages/core/resources/lang/tr/base.php @@ -0,0 +1,9 @@ + [ + 'collection-titles' => [ + 'images' => 'Görseller', + ], + ], +]; diff --git a/packages/core/resources/lang/tr/exceptions.php b/packages/core/resources/lang/tr/exceptions.php new file mode 100644 index 0000000000..0cb321b1db --- /dev/null +++ b/packages/core/resources/lang/tr/exceptions.php @@ -0,0 +1,21 @@ + '":class" modeli satın alınabilir arayüzünü uygulamamaktadır.', + 'cart_line_id_mismatch' => 'Bu sepet satırı bu sepete ait değil', + 'invalid_cart_line_quantity' => 'Miktar en az "1" olması bekleniyordu, ":quantity" bulundu.', + 'maximum_cart_line_quantity' => 'Miktar :quantity\'yi aşamaz.', + 'carts.invalid_action' => 'Sepet işlemi geçersizdi', + 'carts.shipping_missing' => 'Kargo adresi gerekli', + 'carts.billing_missing' => 'Fatura adresi gerekli', + 'carts.billing_incomplete' => 'Fatura adresi eksik', + 'carts.order_exists' => 'Bu sepet için zaten bir sipariş var', + 'carts.shipping_option_missing' => 'Kargo Seçeneği Eksik', + 'missing_currency_price' => '":currency" para birimi için fiyat mevcut değil', + 'minimum_quantity' => 'En az :quantity adet eklemelisiniz.', + 'quantity_increment' => ':quantity miktarı :increment\'lik artışlarla olmalıdır', + 'fieldtype_missing' => 'FieldType ":class" mevcut değil', + 'invalid_fieldtype' => '":class" sınıfı FieldType arayüzünü uygulamamaktadır.', + 'discounts.invalid_type' => 'Koleksiyon sadece ":expected" içermeli, ":actual" bulundu', + 'disallow_multiple_cart_orders' => 'Sepetler sadece bir siparişle ilişkilendirilebilir.', +]; diff --git a/packages/table-rate-shipping/resources/lang/tr/plugin.php b/packages/table-rate-shipping/resources/lang/tr/plugin.php new file mode 100644 index 0000000000..ab9a765aaa --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/tr/plugin.php @@ -0,0 +1,7 @@ + [ + 'group' => 'Kargo', + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php b/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php new file mode 100644 index 0000000000..16ca24080d --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php @@ -0,0 +1,80 @@ + [ + 'customer_groups' => [ + 'description' => "Müşteri gruplarını bu kargo yöntemiyle ilişkilendirerek kullanılabilirliğini belirleyin.", + ], + ], + 'shipping_rates' => [ + 'title_plural' => 'Kargo Tarifeleri', + 'actions' => [ + 'create' => [ + 'label' => 'Kargo Tarifesi Oluştur' + ], + ], + 'notices' => [ + 'prices_incl_tax' => 'Tüm fiyatlar vergi dahildir, minimum harcama hesaplanırken dikkate alınacaktır.', + 'prices_excl_tax' => 'Tüm fiyatlar vergi hariçtir, minimum harcama sepet ara toplamına göre olacaktır.', + ], + 'form' => [ + 'shipping_method_id' => [ + 'label' => 'Kargo Yöntemi', + ], + 'price' => [ + 'label' => 'Fiyat', + ], + 'prices' => [ + 'label' => 'Fiyat Aralıkları', + 'repeater' => [ + 'customer_group_id' => [ + 'label' => 'Müşteri Grubu', + 'placeholder' => 'Herhangi', + ], + 'currency_id' => [ + 'label' => 'Para Birimi', + ], + 'min_spend' => [ + 'label' => 'Min. Harcama', + ], + 'min_weight' => [ + 'label' => 'Min. Ağırlık', + ], + 'price' => [ + 'label' => 'Fiyat', + ], + ], + ], + ], + 'table' => [ + 'shipping_method' => [ + 'label' => 'Kargo Yöntemi', + ], + 'price' => [ + 'label' => 'Fiyat', + ], + 'price_breaks_count' => [ + 'label' => 'Fiyat Aralıkları' + ], + ], + ], + 'exclusions' => [ + 'title_plural' => 'Kargo İstisnaları', + 'form' => [ + 'purchasable' => [ + 'label' => 'Ürün', + ], + ], + 'actions' => [ + 'create' => [ + 'label' => 'Kargo istisna listesi ekle' + ], + 'attach' => [ + 'label' => 'İstisna listesi ekle' + ], + 'detach' => [ + 'label' => 'Kaldır', + ], + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/tr/shippingexclusionlist.php b/packages/table-rate-shipping/resources/lang/tr/shippingexclusionlist.php new file mode 100644 index 0000000000..e61cad7d3a --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/tr/shippingexclusionlist.php @@ -0,0 +1,19 @@ + 'Kargo İstisna Listesi', + 'label_plural' => 'Kargo İstisna Listeleri', + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'exclusions_count' => [ + 'label' => 'Ürün Sayısı', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/tr/shippingmethod.php b/packages/table-rate-shipping/resources/lang/tr/shippingmethod.php new file mode 100644 index 0000000000..0c8e902ab8 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/tr/shippingmethod.php @@ -0,0 +1,58 @@ + 'Kargo Yöntemleri', + 'label' => 'Kargo Yöntemi', + 'form' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'description' => [ + 'label' => 'Açıklama', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'cutoff' => [ + 'label' => 'Son Sipariş Saati', + ], + 'charge_by' => [ + 'label' => 'Ücretlendirme Ölçütü', + 'options' => [ + 'cart_total' => 'Sepet Toplamı', + 'weight' => 'Ağırlık', + ], + ], + 'driver' => [ + 'label' => 'Tür', + 'options' => [ + 'ship-by' => 'Standart', + 'collection' => 'Mağazadan Teslim Alma', + ], + ], + 'stock_available' => [ + 'label' => 'Tüm sepet öğelerinin stoku mevcut olmalı', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'code' => [ + 'label' => 'Kod', + ], + 'driver' => [ + 'label' => 'Tür', + 'options' => [ + 'ship-by' => 'Standart', + 'collection' => 'Mağazadan Teslim Alma', + ], + ], + ], + 'pages' => [ + 'availability' => [ + 'label' => 'Kullanılabilirlik', + 'customer_groups' => 'Bu kargo yöntemi şu anda tüm müşteri grupları için mevcut değil.', + ], + ], +]; diff --git a/packages/table-rate-shipping/resources/lang/tr/shippingzone.php b/packages/table-rate-shipping/resources/lang/tr/shippingzone.php new file mode 100644 index 0000000000..307365d765 --- /dev/null +++ b/packages/table-rate-shipping/resources/lang/tr/shippingzone.php @@ -0,0 +1,50 @@ + 'Kargo Bölgesi', + 'label_plural' => 'Kargo Bölgeleri', + 'form' => [ + 'unrestricted' => [ + 'content' => 'Bu kargo bölgesinin herhangi bir kısıtlaması yoktur ve ödeme sırasında tüm müşteriler için mevcut olacaktır.', + ], + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'options' => [ + 'unrestricted' => 'Kısıtsız', + 'countries' => 'Ülkelerle Sınırla', + 'states' => 'Eyaletler / İller ile Sınırla', + 'postcodes' => 'Posta Kodlarıyla Sınırla', + ], + ], + 'country' => [ + 'label' => 'Ülke', + ], + 'states' => [ + 'label' => 'Eyaletler', + ], + 'countries' => [ + 'label' => 'Eyaletler', + ], + 'postcodes' => [ + 'label' => 'Posta Kodları', + 'helper' => 'Her posta kodunu yeni bir satırda listeleyin. NW* gibi joker karakterleri destekler', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Ad', + ], + 'type' => [ + 'label' => 'Tür', + 'options' => [ + 'unrestricted' => 'Kısıtsız', + 'countries' => 'Ülkelerle Sınırla', + 'states' => 'Eyaletler / İller ile Sınırla', + 'postcodes' => 'Posta Kodlarıyla Sınırla', + ], + ], + ], +]; From b162db08d8a894c2285031dfccd61e693f3a32f7 Mon Sep 17 00:00:00 2001 From: ChaDonSom Date: Tue, 11 Nov 2025 10:51:26 -0500 Subject: [PATCH 16/50] Fix default color and icon for state in CustomerGroupRelationManager (#2330) Add support for when postgres casts booleans to php bool, thereby casting `false` to an empty string for the match case Please describe your Pull Request as best as possible, explaining what it provides and why it is of value, referencing any related Issues. Try to include the following in your Pull Request, where applicable... - Documentation updates - Automated tests --------- Co-authored-by: Glenn Jacobs --- .../RelationManagers/CustomerGroupRelationManager.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php b/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php index c348dfe6c2..2c30fd3a13 100644 --- a/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php +++ b/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php @@ -81,13 +81,8 @@ public function getDefaultTable(Table $table): Table return Tables\Columns\IconColumn::make($column)->label( __("lunarpanel::relationmanagers.customer_groups.table.{$column}.label") ) - ->color(fn (string $state): string => match ($state) { - '1' => 'success', - '0' => 'warning', - })->icon(fn (string $state): string => match ($state) { - '0' => 'heroicon-o-x-circle', - '1' => 'heroicon-o-check-circle', - }); + ->color(fn ($state): string => $state ? 'success' : 'warning') + ->icon(fn ($state): string => $state ? 'heroicon-o-check-circle' : 'heroicon-o-x-circle'); })->toArray(); return $table From 3de54d0aa5a902fd2d97477e0e98e4a343220b56 Mon Sep 17 00:00:00 2001 From: wychoong <67364036+wychoong@users.noreply.github.com> Date: Wed, 12 Nov 2025 00:20:08 +0800 Subject: [PATCH 17/50] Fix typo in extend method name for order summary infolist (#2326) could be a breaking change Docs: https://github.com/lunarphp/docs/pull/1 --- docs/core/upgrading.md | 5 +++++ .../OrderResource/Concerns/DisplaysOrderSummary.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/core/upgrading.md b/docs/core/upgrading.md index 8b933ae173..4bfeb36070 100644 --- a/docs/core/upgrading.md +++ b/docs/core/upgrading.md @@ -14,6 +14,11 @@ Run any migrations php artisan migrate ``` +## [Unreleased] + +#### Rename Order resource OrderSummaryInfolist Extension hook +rename `exendOrderSummaryInfolist` to `extendOrderSummaryInfolist` + ## 1.0.0 (stable) ### Medium Impact diff --git a/packages/admin/src/Filament/Resources/OrderResource/Concerns/DisplaysOrderSummary.php b/packages/admin/src/Filament/Resources/OrderResource/Concerns/DisplaysOrderSummary.php index aacfa14a98..98474fed4f 100644 --- a/packages/admin/src/Filament/Resources/OrderResource/Concerns/DisplaysOrderSummary.php +++ b/packages/admin/src/Filament/Resources/OrderResource/Concerns/DisplaysOrderSummary.php @@ -131,6 +131,6 @@ public static function getDefaultOrderSummaryInfolist(): Infolists\Components\Se public static function getOrderSummaryInfolist(): Infolists\Components\Section { - return self::callStaticLunarHook('exendOrderSummaryInfolist', static::getDefaultOrderSummaryInfolist()); + return self::callStaticLunarHook('extendOrderSummaryInfolist', static::getDefaultOrderSummaryInfolist()); } } From cd8328bf90c614190daba0ceeb5b34421ac2f393 Mon Sep 17 00:00:00 2001 From: SecurID Date: Tue, 11 Nov 2025 17:24:16 +0100 Subject: [PATCH 18/50] Fix vendor publish command for pdfs (#2281) This will fix issue #2265 I am not sure about the naming of the publish tag. I think lunar.pdf fits better as this will only target all pdfs currently (and in the future) in lunar. If accepted we need to edit the docs aswell. Docs: https://github.com/lunarphp/docs/pull/3 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Glenn Jacobs --- docs/core/reference/orders.md | 6 +++--- packages/admin/src/LunarPanelProvider.php | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/core/reference/orders.md b/docs/core/reference/orders.md index 2631fc5bf1..59e05db4b4 100644 --- a/docs/core/reference/orders.md +++ b/docs/core/reference/orders.md @@ -436,11 +436,11 @@ Here's an example of what the template could look like: ## Order Invoice PDF -By default when you click "Download PDF" in the hub when viewing an order, you will get a basic PDF generated for you to -download. You can publish the view that powers this to create your own PDF template. +By default when you click "Download PDF" in the admin panel when viewing an order, you will get a basic PDF generated +for you to download. You can publish the view that powers this to create your own PDF template. ```bash -php artisan vendor:publish --tag=lunar.hub.views +php artisan vendor:publish --tag=lunarpanel.pdf ``` This will create a view called `resources/vendor/lunarpanel/pdf/order.blade.php`, where you will be able to freely diff --git a/packages/admin/src/LunarPanelProvider.php b/packages/admin/src/LunarPanelProvider.php index d7fdc06157..b97f6dc5b3 100644 --- a/packages/admin/src/LunarPanelProvider.php +++ b/packages/admin/src/LunarPanelProvider.php @@ -74,6 +74,10 @@ public function boot(): void __DIR__.'/../resources/lang' => $this->app->langPath('vendor/lunarpanel'), ]); + $this->publishes([ + __DIR__.'/../resources/views/pdf' => resource_path('views/vendor/lunarpanel/pdf'), + ], 'lunarpanel.pdf'); + collect($this->configFiles)->each(function ($config) { $this->mergeConfigFrom("{$this->root}/config/$config.php", "lunar.$config"); }); From 9e40f2e8dafa844a8b8ca4a5f2eb65cb5f32b05c Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 11 Nov 2025 16:28:40 +0000 Subject: [PATCH 19/50] Remove docs (#2343) Removes docs from monorepo in favour of our new Mintlify docs stored at https://github.com/lunarphp/docs --- docs/.gitignore | 3 - docs/.vitepress/config.js | 196 ----- docs/.vitepress/theme/Layout.vue | 19 - docs/.vitepress/theme/custom.css | 69 -- docs/.vitepress/theme/index.js | 10 - docs/admin/extending/access-control.md | 55 -- docs/admin/extending/addons.md | 21 - docs/admin/extending/attributes.md | 105 --- docs/admin/extending/dashboard.md | 64 -- docs/admin/extending/order-management.md | 62 -- docs/admin/extending/overview.md | 82 -- docs/admin/extending/pages.md | 401 --------- docs/admin/extending/panel.md | 57 -- docs/admin/extending/relation-managers.md | 32 - docs/admin/extending/resources.md | 62 -- docs/admin/overview.md | 31 - docs/core/configuration.md | 117 --- docs/core/contributing.md | 95 -- docs/core/extending/carts.md | 134 --- docs/core/extending/discounts.md | 96 -- docs/core/extending/models.md | 147 ---- docs/core/extending/orders.md | 63 -- docs/core/extending/payments.md | 278 ------ docs/core/extending/search.md | 139 --- docs/core/extending/shipping.md | 84 -- docs/core/extending/taxation.md | 103 --- docs/core/installation.md | 101 --- docs/core/local-development.md | 60 -- docs/core/overview.md | 28 - docs/core/reference/activity-log.md | 13 - docs/core/reference/addresses.md | 84 -- docs/core/reference/associations.md | 154 ---- docs/core/reference/attributes.md | 213 ----- docs/core/reference/carts.md | 521 ----------- docs/core/reference/channels.md | 55 -- docs/core/reference/collections.md | 92 -- docs/core/reference/currencies.md | 47 - docs/core/reference/customers.md | 271 ------ docs/core/reference/discounts.md | 143 --- docs/core/reference/languages.md | 20 - docs/core/reference/media.md | 203 ----- docs/core/reference/orders.md | 447 ---------- docs/core/reference/payments.md | 356 -------- docs/core/reference/pricing.md | 223 ----- docs/core/reference/products.md | 832 ------------------ docs/core/reference/search.md | 63 -- docs/core/reference/tags.md | 48 - docs/core/reference/taxation.md | 161 ---- docs/core/reference/urls.md | 157 ---- docs/core/securing-your-site.md | 50 -- docs/core/starter-kits.md | 82 -- .../storefront-utils/storefront-session.md | 122 --- docs/core/upgrading.md | 413 --------- docs/index.md | 30 - docs/package.json | 11 - docs/public/icon-dark.svg | 4 - docs/public/icon.svg | 4 - docs/public/images/products/dr-martens.png | Bin 295878 -> 0 bytes docs/public/logo.svg | 5 - docs/support.md | 92 -- docs/vercel.json | 14 - 61 files changed, 7644 deletions(-) delete mode 100644 docs/.gitignore delete mode 100644 docs/.vitepress/config.js delete mode 100644 docs/.vitepress/theme/Layout.vue delete mode 100644 docs/.vitepress/theme/custom.css delete mode 100644 docs/.vitepress/theme/index.js delete mode 100644 docs/admin/extending/access-control.md delete mode 100644 docs/admin/extending/addons.md delete mode 100644 docs/admin/extending/attributes.md delete mode 100644 docs/admin/extending/dashboard.md delete mode 100644 docs/admin/extending/order-management.md delete mode 100644 docs/admin/extending/overview.md delete mode 100644 docs/admin/extending/pages.md delete mode 100644 docs/admin/extending/panel.md delete mode 100644 docs/admin/extending/relation-managers.md delete mode 100644 docs/admin/extending/resources.md delete mode 100644 docs/admin/overview.md delete mode 100644 docs/core/configuration.md delete mode 100644 docs/core/contributing.md delete mode 100644 docs/core/extending/carts.md delete mode 100644 docs/core/extending/discounts.md delete mode 100644 docs/core/extending/models.md delete mode 100644 docs/core/extending/orders.md delete mode 100644 docs/core/extending/payments.md delete mode 100644 docs/core/extending/search.md delete mode 100644 docs/core/extending/shipping.md delete mode 100644 docs/core/extending/taxation.md delete mode 100644 docs/core/installation.md delete mode 100644 docs/core/local-development.md delete mode 100644 docs/core/overview.md delete mode 100644 docs/core/reference/activity-log.md delete mode 100644 docs/core/reference/addresses.md delete mode 100644 docs/core/reference/associations.md delete mode 100644 docs/core/reference/attributes.md delete mode 100644 docs/core/reference/carts.md delete mode 100644 docs/core/reference/channels.md delete mode 100644 docs/core/reference/collections.md delete mode 100644 docs/core/reference/currencies.md delete mode 100644 docs/core/reference/customers.md delete mode 100644 docs/core/reference/discounts.md delete mode 100644 docs/core/reference/languages.md delete mode 100644 docs/core/reference/media.md delete mode 100644 docs/core/reference/orders.md delete mode 100644 docs/core/reference/payments.md delete mode 100644 docs/core/reference/pricing.md delete mode 100644 docs/core/reference/products.md delete mode 100644 docs/core/reference/search.md delete mode 100644 docs/core/reference/tags.md delete mode 100644 docs/core/reference/taxation.md delete mode 100644 docs/core/reference/urls.md delete mode 100644 docs/core/securing-your-site.md delete mode 100644 docs/core/starter-kits.md delete mode 100644 docs/core/storefront-utils/storefront-session.md delete mode 100644 docs/core/upgrading.md delete mode 100644 docs/index.md delete mode 100644 docs/package.json delete mode 100644 docs/public/icon-dark.svg delete mode 100644 docs/public/icon.svg delete mode 100644 docs/public/images/products/dr-martens.png delete mode 100644 docs/public/logo.svg delete mode 100644 docs/support.md delete mode 100644 docs/vercel.json diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 5506568240..0000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.vitepress/cache -.vitepress/dist diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js deleted file mode 100644 index 92b1545854..0000000000 --- a/docs/.vitepress/config.js +++ /dev/null @@ -1,196 +0,0 @@ -import {defineConfig} from 'vitepress' - -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: "Lunar", - description: "Laravel Headless E-Commerce", - head: [ - [ - 'link', - {rel: 'icon', href: '/icon.svg', type: 'image/svg'} - ], - [ - 'link', - {rel: 'preconnect', href: 'https://fonts.googleapis.com'} - ], - [ - 'link', - {rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: ''} - ], - [ - 'link', - { - rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700;900&display=swap' - } - ], - [ - 'script', - { - src: 'https://cdn.usefathom.com/script.js', - 'data-spa': 'auto', - 'data-site': 'KMZGQTYE', - defer: '' - } - ], - [ - 'script', - { - async: '', - src: 'https://tally.so/widgets/embed.js' - } - ] - // would render: - ], - - appearance: 'dark', - - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - logo: { - light: '/icon.svg', - dark: '/icon-dark.svg', - }, - - nav: [ - { - text: 'Core', - link: '/core/overview', - activeMatch: '/core/' - }, - { - text: 'Admin Panel', - link: '/admin/overview', - activeMatch: '/admin/' - }, - { - text: 'Support', - link: '/support', - activeMatch: '/support' - }, - { - text: 'Resources', - items: [ - {text: 'Add-ons', link: 'https://github.com/lunarphp/awesome'}, - {text: 'Discord', link: 'https://discord.gg/v6qVWaf'}, - {text: 'Discussions', link: 'https://github.com/lunarphp/lunar/discussions'} - ] - }, - { - text: '1.x', - items: [ - {text: 'Changelog', link: '/core/upgrading'}, - {text: 'Contributing', link: '/core/contributing'}, - {text: 'Roadmap', link: 'https://github.com/orgs/lunarphp/projects/8'}, - {text: '0.x Docs', link: 'https://v0.lunarphp.io/'}, - ] - } - ], - - sidebar: { - // This sidebar gets displayed when a user - // is on `guide` directory. - '/core/': [ - { - text: 'Getting Started', - collapsed: false, - items: [ - {text: 'Overview', link: '/core/overview'}, - {text: 'Installation', link: '/core/installation'}, - {text: 'Starter Kits', link: '/core/starter-kits'}, - {text: 'Configuration', link: '/core/configuration'}, - {text: 'Upgrade Guide', link: '/core/upgrading'}, - {text: 'Security', link: '/core/securing-your-site'}, - {text: 'Contributing', link: '/core/contributing'} - ] - }, - { - text: 'Reference', - collapsed: false, - items: [ - {text: 'Activity Log', link: '/core/reference/activity-log'}, - {text: 'Addresses', link: '/core/reference/addresses'}, - {text: 'Associations', link: '/core/reference/associations'}, - {text: 'Attributes', link: '/core/reference/attributes'}, - {text: 'Carts', link: '/core/reference/carts'}, - {text: 'Channels', link: '/core/reference/channels'}, - {text: 'Collections', link: '/core/reference/collections'}, - {text: 'Currencies', link: '/core/reference/currencies'}, - {text: 'Customers', link: '/core/reference/customers'}, - {text: 'Discounts', link: '/core/reference/discounts'}, - {text: 'Languages', link: '/core/reference/languages'}, - {text: 'Media', link: '/core/reference/media'}, - {text: 'Orders', link: '/core/reference/orders'}, - {text: 'Payments', link: '/core/reference/payments'}, - {text: 'Pricing', link: '/core/reference/pricing'}, - {text: 'Products', link: '/core/reference/products'}, - {text: 'Search', link: '/core/reference/search'}, - {text: 'Tags', link: '/core/reference/tags'}, - {text: 'Taxation', link: '/core/reference/taxation'}, - {text: 'URLs', link: '/core/reference/urls'} - ] - }, - { - text: 'Storefront', - collapsed: false, - items: [ - {text: 'Storefront Session', link: '/core/storefront-utils/storefront-session'}, - ] - }, - { - text: 'Extending', - collapsed: false, - items: [ - {text: 'Carts', link: '/core/extending/carts'}, - {text: 'Discounts', link: '/core/extending/discounts'}, - {text: 'Models', link: '/core/extending/models'}, - {text: 'Orders', link: '/core/extending/orders'}, - {text: 'Payments', link: '/core/extending/payments'}, - {text: 'Search', link: '/core/extending/search'}, - {text: 'Shipping', link: '/core/extending/shipping'}, - {text: 'Taxation', link: '/core/extending/taxation'} - ] - } - ], - - '/admin/': [ - { - text: 'Getting Started', - collapsed: false, - items: [ - {text: 'Overview', link: '/admin/overview'}, - ] - }, - { - text: 'Extending', - collapsed: false, - items: [ - {text: 'Overview', link: '/admin/extending/overview'}, - {text: 'Access Control', link: '/admin/extending/access-control'}, - {text: 'Add-ons', link: '/admin/extending/addons'}, - {text: 'Attributes', link: '/admin/extending/attributes'}, - {text: 'Panel', link: '/admin/extending/panel'}, - {text: 'Dashboard', link: '/admin/extending/dashboard'}, - {text: 'Pages', link: '/admin/extending/pages'}, - {text: 'Resources', link: '/admin/extending/resources'}, - {text: 'Relation Managers', link: '/admin/extending/relation-managers'}, - {text: 'Order Management', link: '/admin/extending/order-management'} - ] - } - ], - - }, - - socialLinks: [ - {icon: 'github', link: 'https://github.com/lunarphp/lunar'}, - {icon: 'twitter', link: 'https://twitter.com/lunarphp'}, - {icon: 'discord', link: 'https://discord.gg/v6qVWaf'}, - ], - - algolia: { - appId: 'ZHX0K72823', - apiKey: '42f3d86ed75f289e5cb75e9d7c6f43f9', - indexName: 'lunarphp' - }, - } -}) diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue deleted file mode 100644 index 350febec01..0000000000 --- a/docs/.vitepress/theme/Layout.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css deleted file mode 100644 index 73aed0ab6d..0000000000 --- a/docs/.vitepress/theme/custom.css +++ /dev/null @@ -1,69 +0,0 @@ -:root { - --vp-c-brand: #22baf7; - --vp-c-brand-light: #08a1dd; - - --vp-c-text-light-1: #122036; - --vp-c-text-light-2: #263957; - --vp-c-text-light-3: #435777; - - --vp-font-family-base: 'Poppins', sans-serif; -} - -.dark { - --vp-c-bg: #122036; - - --vp-c-bg-elv: #0A172D; - --vp-c-bg-elv-up: #313136; - --vp-c-bg-elv-down: #1e1e20; - --vp-c-bg-elv-mute: #313136; - - --vp-c-bg-soft: #0A172D; - --vp-c-bg-soft-up: #0A172D; - --vp-c-bg-soft-down: #0A172D; - --vp-c-bg-soft-mute: #0A172D; - - --vp-c-bg-alt: #0A172D; - - --vp-c-mute: #384B68; -} - -.vp-doc .header-anchor { - margin-left: -1.1em; -} - -.btn { - display: inline-block; - padding: 10px 20px; - background: #22baf7; - color: white; - text-decoration: none; - border-radius: 5px; - font-weight: bold; -} -.btn:hover { - background: #08a1dd; -} - -.banner { - background-color: var(--vp-c-bg-soft); - padding: 15px; - font-size: 14px; - border-radius: 5px; -} - -.banner .banner-title { - font-weight: bold; -} - -.banner .banner-btn { - background-color: var(--vp-c-bg); - padding: 10px; - text-align: center; - font-size: 14px; - border-radius: 5px; - margin-top: 10px; -} - -.banner .banner-btn:hover { - background-color: var(--vp-c-mute); -} diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js deleted file mode 100644 index c191ceedb5..0000000000 --- a/docs/.vitepress/theme/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import DefaultTheme from 'vitepress/theme' -import './custom.css' -import Layout from "./Layout.vue"; - -export default { - extends: DefaultTheme, - // override the Layout with a wrapper component that - // injects the slots - Layout: Layout -} diff --git a/docs/admin/extending/access-control.md b/docs/admin/extending/access-control.md deleted file mode 100644 index bd258b2015..0000000000 --- a/docs/admin/extending/access-control.md +++ /dev/null @@ -1,55 +0,0 @@ -# Staff Members - -Your staff members are essentially users who can log in to the admin panel and have permissions assigned to them. Staff -members are not to be confused with users in the `users` table, the Lunar uses a different table for authenticating -users in the admin panel. This is a design choice to ensure that your customers can never accidentally be given access. - -# Roles and permissions - -In Lunar panel, we are utilizing roles and permission for authorization. This give you the ability to assign multiple -permissions to a role and assign it to the staff without assigning permission one by one to the staff. - -::: tip -The admin panel is using `spatie/laravel-permission` package -::: - -### Roles -Out of the box Lunar provided `admin` and `staff` roles. You can create new role using our Access Control page in Staff -menu. -After installation, the panel will have one admin. You can assign more but non admins cannot assign other admins. - -### Permissions -Permissions can be assigned to roles or directly to staff and this dictates what they can do or see in the panel. If a -user does not have a certain permission to view a page or perform an action they will get an Unauthorized HTTP error. -They will also potentially see a reduced amount of menu items throughout the admin panel. - -To enable permissions on a staff member, simply edit them via the staff page and assign the permissions you want them -to have. - -##### Adding permissions -While the panel provided a page to create role and assign permissions. It's deliberated that permission are not created -from the panel as the authorization are required to be implemented in code. It might change in the future but the -recommended way to create roles and permission would be Lunar migration state or Laravel's migration. So you can deploy -it to other environment easily. - -## Authorisation -First party permission provided by Lunar are used to authorise repective section of the panel. You should still -implement authorisation checking respectively for your new permissions and custom pages. - -Example: -`middleware` 'can:permission-handle' -`in-code` Auth::user()->can('permission-handle') -::: - -### Two-Factor Authentication - -You can choose whether to enforce Two-Factor Authentication or disable it entirely. - -```php - -public function register() -{ - \Lunar\Admin\Support\Facades\LunarPanel::enforceTwoFactorAuth()->register(); - \Lunar\Admin\Support\Facades\LunarPanel::disableTwoFactorAuth()->register(); -} -``` diff --git a/docs/admin/extending/addons.md b/docs/admin/extending/addons.md deleted file mode 100644 index e3458ab659..0000000000 --- a/docs/admin/extending/addons.md +++ /dev/null @@ -1,21 +0,0 @@ -# Developing Add-ons - -When creating add-on packages for Lunar you may wish to add new screens and functionality to the Filament panel. - -To achieve this you will want to create a Filament plugin in your package. With Filament plugins you can add additional -resources, pages and widgets. See https://filamentphp.com/docs/3.x/panels/plugins for more information. - -## Registering Filament plugins - -Add-on packages should not try to register a Filament plugin automatically in the Lunar panel. Instead, installation -instructions should be provided. - -Below is an example of how a plugin should be registered to the Lunar admin panel, typically in your Laravel app -service provider. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; - -LunarPanel::panel(fn($panel) => $panel->plugin(new ReviewsPlugin())) - ->register(); -``` diff --git a/docs/admin/extending/attributes.md b/docs/admin/extending/attributes.md deleted file mode 100644 index b4ed06d02e..0000000000 --- a/docs/admin/extending/attributes.md +++ /dev/null @@ -1,105 +0,0 @@ -# Extending Attributes - -You can add your own attribute field types to Lunar and control how they are rendered in the panel. - -In order to render the form component from the attribute, it needs to be converted into a Filament form component and a suitable Livewire Synthesizer associated so it can be hydrated/dehydrated properly. - -## Create the field - -```php -use Lunar\FieldTypes\Text; - -class CustomField extends Text -{ - // ... -} -``` - -## Create the field type - -```php -use Lunar\Admin\Support\FieldTypes\BaseFieldType; - -class CustomFieldType extends BaseFieldType -{ - protected static string $synthesizer = CustomFieldSynth::class; - - public static function getFilamentComponent(Attribute $attribute): Component - { - return TextInput::make($attribute->handle); - } -} -``` - -## Adding settings - -There may be additional settings you want your field to have, for example the Number field has `min` and `max` settings. -To add these fields, you will need to tell Filament how to render the inputs. - -```php -class CustomFieldType extends BaseFieldType -{ - // ... - - public static function getConfigurationFields(): array - { - return [ - Grid::make(2)->schema([ - \Filament\Forms\Components\TextInput::make('min_length'), - \Filament\Forms\Components\TextInput::make('max_length'), - ]), - ]; - } -} -``` - -These will when be stored in the `configuration` JSON column for the attribute. Which you can then access when you -render the field in the panel. - -```php -use Lunar\Admin\Support\FieldTypes\BaseFieldType; - -class CustomFieldType extends BaseFieldType -{ - // ... - - public static function getFilamentComponent(Attribute $attribute): Component - { - $min = (int) $attribute->configuration->get('min_length'); - $max = (int) $attribute->configuration->get('max_length'); - - return TextInput::make($attribute->handle)->min($min)->max($max); - } -} -``` - -## Create the Livewire Synthesizer - -So Livewire knows how to hydrate/dehydrate the values provided to the field type when editing, we need to add a -Synthesizer. You can read more about Livewire Synthesizers and what they do -here: [https://livewire.laravel.com/docs/synthesizers](https://livewire.laravel.com/docs/synthesizers) - -```php - App\Filament\Extensions\DashboardExtension::class, - ]); -} -``` diff --git a/docs/admin/extending/order-management.md b/docs/admin/extending/order-management.md deleted file mode 100644 index 79698181ae..0000000000 --- a/docs/admin/extending/order-management.md +++ /dev/null @@ -1,62 +0,0 @@ -## Extending Order Management - -Although orders have access to the same customisation as [Pages](/admin/extending/pages) there are some additional methods available and an additional class to allow order lines to be customised. - -To register your extension: - -```php -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\OrderResource\Pages\ManageOrder::class => MyManageOrderExtension::class, -]); -``` - -You then have access to these methods in your class to override area's of the order view screen. - -- `extendInfolistSchema(): array` - -- `extendInfolistAsideSchema(): array` - - `extendCustomerEntry(): Infolists\Components\Component` - - `extendTagsSection(): Infolists\Components\Component` - - `extendAdditionalInfoSection(): Infolists\Components\Component` - - `extendShippingAddressInfolist(): Infolists\Components\Component` - - `extendBillingAddressInfolist(): Infolists\Components\Component` - - `extendAddressEditSchema(): array` - -- `exendOrderSummaryInfolist(): Infolists\Components\Section` -- `extendOrderSummarySchema(): array` - - `extendOrderSummaryNewCustomerEntry(): Infolists\Components\Entry` - - `extendOrderSummaryStatusEntry(): Infolists\Components\Entry` - - `extendOrderSummaryReferenceEntry(): Infolists\Components\Entry` - - `extendOrderSummaryCustomerReferenceEntry(): Infolists\Components\Entry` - - `extendOrderSummaryChannelEntry(): Infolists\Components\Entry` - - `extendOrderSummaryCreatedAtEntry(): Infolists\Components\Entry` - - `extendOrderSummaryPlacedAtEntry(): Infolists\Components\Entry` -- `extendTimelineInfolist(): Infolists\Components\Component` -- `extendOrderTotalsAsideSchema(): array` - - `extendDeliveryInstructionsEntry(): Infolists\Components\TextEntry` - - `extendOrderNotesEntry(): Infolists\Components\TextEntry` -- `extendOrderTotalsInfolist(): Infolists\Components\Section` -- `extendOrderTotalsSchema(): array` - - `extendSubTotalEntry(): Infolists\Components\TextEntry` - - `extendDiscountTotalEntry(): Infolists\Components\TextEntry` - - `extendShippingBreakdownGroup(): Infolists\Components\Group` - - `extendTaxBreakdownGroup(): Infolists\Components\Group` - - `extendTotalEntry(): Infolists\Components\TextEntry` - - `extendPaidEntry(): Infolists\Components\TextEntry` - - `extendRefundEntry(): Infolists\Components\TextEntry` - -- `extendShippingInfolist(): Infolists\Components\Section` -- `extendTransactionsInfolist(): Infolists\Components\Component` -- `extendTransactionsRepeatableEntry(): Infolists\Components\RepeatableEntry` - -## Extending `OrderItemsTable` - -```php -\Lunar\Facades\LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\OrderResource\Pages\Components\OrderItemsTable::class => OrderItemsTableExtension::class -]); -``` -### Available Methods - -- `extendOrderLinesTableColumns(): array` -- `extendTable(): Table` diff --git a/docs/admin/extending/overview.md b/docs/admin/extending/overview.md deleted file mode 100644 index 311dbe899f..0000000000 --- a/docs/admin/extending/overview.md +++ /dev/null @@ -1,82 +0,0 @@ - -# Overview - -The Lunar Panel is highly customizable, you can add and change the behaviour of existing Filament resources. This might be useful if you wish to add a button for -additional custom functionality. - -## Extending Pages - -To extend a page you need to create and register an extension. - -### Extending edit resource - -For example, the code below will register a custom extension called `MyEditExtension` for the `EditProduct` Filament page. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; -use Lunar\Panel\Filament\Resources\ProductResource\Pages\EditProduct; -use App\Admin\Filament\Resources\Pages\MyEditExtension; - -LunarPanel::extensions([ - EditProduct::class => MyEditExtension::class, -]); -``` - -### Extending list resource - -For example, the code below will register a custom extension called `MyListExtension` for the `ListProduct` Filament page. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; -use Lunar\Panel\Filament\Resources\ProductResource\Pages\ListProduct; -use App\Admin\Filament\Resources\Pages\MyEditExtension; - -LunarPanel::extensions([ - ListProduct::class => MyListExtension::class, -]); - -``` - -## Extending Resources -Much like extending pages, to extend a resource you need to create and register an extension. - -For example, the code below will register a custom extension called `MyProductResourceExtension` for the `ProductResource` Filament resource. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; -use Lunar\Panel\Filament\Resources\ProductResource; -use App\Admin\Filament\Resources\MyProductResourceExtension; - -LunarPanel::extensions([ - ProductResource::class => MyProductResourceExtension::class, -]); - -``` - -## Extendable resources - -All Lunar panel resources are extendable. This means you can now add your own functionality or change out existing behaviour. - -```php -use Lunar\Panel\Filament\Resources\ActivityResource; -use Lunar\Panel\Filament\Resources\AttributeGroupResource; -use Lunar\Panel\Filament\Resources\BrandResource; -use Lunar\Panel\Filament\Resources\ChannelResource; -use Lunar\Panel\Filament\Resources\CollectionGroupResource; -use Lunar\Panel\Filament\Resources\CollectionResource; -use Lunar\Panel\Filament\Resources\CurrencyResource; -use Lunar\Panel\Filament\Resources\CustomerGroupResource; -use Lunar\Panel\Filament\Resources\CustomerResource; -use Lunar\Panel\Filament\Resources\DiscountResource; -use Lunar\Panel\Filament\Resources\LanguageReousrce; -use Lunar\Panel\Filament\Resources\OrderResource; -use Lunar\Panel\Filament\Resources\ProductOptionrResource; -use Lunar\Panel\Filament\Resources\ProductResource; -use Lunar\Panel\Filament\Resources\ProductResource; -use Lunar\Panel\Filament\Resources\ProductTypeResource; -use Lunar\Panel\Filament\Resources\ProductVariantResource; -use Lunar\Panel\Filament\Resources\StaffResource; -use Lunar\Panel\Filament\Resources\TagResource; -use Lunar\Panel\Filament\Resources\TaxClassResource; -use Lunar\Panel\Filament\Resources\TaxZoneResource; -``` diff --git a/docs/admin/extending/pages.md b/docs/admin/extending/pages.md deleted file mode 100644 index 8ba953278f..0000000000 --- a/docs/admin/extending/pages.md +++ /dev/null @@ -1,401 +0,0 @@ -# Extending Pages - -## Writing Extensions - -There are three extension types Lunar provides, these are for Create, Edit and Listing pages. - -You will want to place the extension class in your application. A sensible location might be `App\Lunar\MyCreateExtension`. - -Once created you will need to register the extension, typically in your app service provider. - - -## CreatePageExtension - -An example of extending a create page. - -```php -use Filament\Actions; -use Lunar\Admin\Support\Extending\CreatePageExtension; -use Lunar\Admin\Filament\Widgets; - -class MyCreateExtension extends CreatePageExtension -{ - public function heading($title): string - { - return $title . ' - Example'; - } - - public function subheading($title): string - { - return $title . ' - Example'; - } - - public function getTabs(array $tabs): array - { - return [ - ...$tabs, - 'review' => Tab::make('Review') - ->modifyQueryUsing(fn (Builder $query) => $query->where('status', 'review')), - ]; - } - - public function headerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\OrderStatsOverview::make(), - ]; - - return $widgets; - } - - public function headerActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\Action::make('Cancel'), - ]; - - return $actions; - } - - public function formActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\Action::make('Create and Edit'), - ]; - - return $actions; - } - - public function footerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\LatestOrdersTable::make(), - ]; - - return $widgets; - } - - public function beforeCreate(array $data): array - { - $data['model_code'] .= 'ABC'; - - return $data; - } - - public function beforeCreation(array $data): array - { - return $data; - } - - public function afterCreation(Model $record, array $data): Model - { - return $record; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\CustomerGroupResource\Pages\CreateCustomerGroup::class => MyCreateExtension::class, -]); -``` - -## EditPageExtension - -An example of extending an edit page. - -```php -use Filament\Actions; -use Lunar\Admin\Support\Extending\EditPageExtension; -use Lunar\Admin\Filament\Widgets; - -class MyEditExtension extends EditPageExtension -{ - public function heading($title): string - { - return $title . ' - Example'; - } - - public function subheading($title): string - { - return $title . ' - Example'; - } - - public function headerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\OrderStatsOverview::make(), - ]; - - return $widgets; - } - - public function headerActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\ActionGroup::make([ - Actions\Action::make('View on Storefront'), - Actions\Action::make('Copy Link'), - Actions\Action::make('Duplicate'), - ]) - ]; - - return $actions; - } - - public function formActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\Action::make('Update and Edit'), - ]; - - return $actions; - } - - public function footerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\LatestOrdersTable::make(), - ]; - - return $widgets; - } - - public function beforeFill(array $data): array - { - $data['model_code'] .= 'ABC'; - - return $data; - } - - public function beforeSave(array $data): array - { - return $data; - } - - public function beforeUpdate(array $data, Model $record): array - { - return $data; - } - - public function afterUpdate(Model $record, array $data): Model - { - return $record; - } - - public function relationManagers(array $managers): array - { - return $managers; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\ProductResource\Pages\EditProduct::class => MyEditExtension::class, -]); -``` - -## ListPageExtension - -An example of extending a list page. - -```php -use Filament\Actions; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Contracts\Pagination\Paginator; -use Lunar\Admin\Support\Extending\ListPageExtension; -use Lunar\Admin\Filament\Widgets; - -class MyListExtension extends ListPageExtension -{ - public function heading($title): string - { - return $title . ' - Example'; - } - - public function subheading($title): string - { - return $title . ' - Example'; - } - - public function headerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\OrderStatsOverview::make(), - ]; - - return $widgets; - } - - public function headerActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\ActionGroup::make([ - Actions\Action::make('View on Storefront'), - Actions\Action::make('Copy Link'), - Actions\Action::make('Duplicate'), - ]), - ]; - - return $actions; - } - - public function paginateTableQuery(Builder $query, int $perPage = 25): Paginator - { - return $query->paginate($perPage); - } - - public function footerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\LatestOrdersTable::make(), - ]; - - return $widgets; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\ProductResource\Pages\ListProducts::class => MyListExtension::class, -]); -``` - -## ViewPageExtension - -An example of extending a view page. - -```php -use Filament\Actions; - -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Table; -use Filament\Infolists\Infolist; -use Filament\Infolists\Components\TextEntry; -use Lunar\Admin\Support\Extending\ViewPageExtension; -use Lunar\Admin\Filament\Widgets; - -class MyViewExtension extends ViewPageExtension -{ - public function headerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\OrderStatsOverview::make(), - ]; - - return $widgets; - } - - public function heading($title): string - { - return $title . ' - Example'; - } - - public function subheading($title): string - { - return $title . ' - Example'; - } - - public function headerActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\ActionGroup::make([ - Actions\Action::make('Download PDF') - ]) - ]; - - return $actions; - } - - public function extendsInfolist(Infolist $infolist): Infolist - { - return $infolist->schema([ - ...$infolist->getComponents(true), - TextEntry::make('custom_title'), - ]); - } - - public function footerWidgets(array $widgets): array - { - $widgets = [ - ...$widgets, - Widgets\Dashboard\Orders\LatestOrdersTable::make(), - ]; - - return $widgets; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\OrderResource\Pages\ManageOrder::class => MyViewExtension::class, -]); -``` - -## RelationPageExtension - -An example of extending a relation page. - -```php -use Filament\Actions; -use Lunar\Admin\Support\Extending\RelationPageExtension; - -class MyRelationExtension extends RelationPageExtension -{ - public function heading($title): string - { - return $title . ' - Example'; - } - - public function subheading($title): string - { - return $title . ' - Example'; - } - - public function headerActions(array $actions): array - { - $actions = [ - ...$actions, - Actions\ActionGroup::make([ - Actions\Action::make('Download PDF') - ]) - ]; - - return $actions; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\ProductResource\Pages\ManageProductMedia::class => MyRelationExtension::class, -]); -``` - -## Extending Pages In Addons - -If you are building an addon for Lunar, you may need to take a slightly different approach when modifying forms, etc. - -For example, you cannot assume the contents of a form, so you may need to take an approach such as this... - -```php - public function extendForm(Form $form): Form - { - $form->schema([ - ...$form->getComponents(true), // Gets the currently registered components - TextInput::make('model_code'), - ]); - return $form; - } -``` diff --git a/docs/admin/extending/panel.md b/docs/admin/extending/panel.md deleted file mode 100644 index 000004edc4..0000000000 --- a/docs/admin/extending/panel.md +++ /dev/null @@ -1,57 +0,0 @@ -# Extending The Panel - -You may customise the Filament panel when registering it in your app service provider. - -We provide a handy function which gives you direct access to the panel to change its properties. - -For example, the following would change the panel's URL to `/admin` rather than the default `/lunar`. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; - -LunarPanel::panel(fn($panel) => $panel->path('admin')) - ->extensions([ - // ... - ]) - ->register(); -``` - -## Adding to the panel - -The [Filament](https://filamentphp.com/) panel allows you to add further screens in the form of Pages or Resources, and -indeed customise any available panel option. - -Below is an example of how you can use the panel object. - -```php -LunarPanel::panel(function ($panel) { - return $panel - ->pages([ - // Register standalone Filament Pages - SalesReport::class, - RevenueReport::class, - ]) - ->resources([ - // Register new Filament Resources - OpeningTimeResource::class, - BannerResource::class, - ]) - ->livewireComponents([ - // Register Livewire components - OrdersSalesChart::class, - ])->plugin( - // Register a Filament plugin - new ShippingPlugin(), - ) - ->navigationGroups([ - // Set the navigation groups - 'Catalog', - 'Sales', - 'CMS', - 'Reports', - 'Shipping', - 'Settings', - ]); -})->register(); -``` -For further information please consult the [Filament documentation](https://filamentphp.com/docs). diff --git a/docs/admin/extending/relation-managers.md b/docs/admin/extending/relation-managers.md deleted file mode 100644 index 98675f8414..0000000000 --- a/docs/admin/extending/relation-managers.md +++ /dev/null @@ -1,32 +0,0 @@ -# Extending Relation Managers - -## MyCustomerGroupPricingRelationManagerExtension - -An example of extending the CustomerGroupPricingRelationManager - -```php -class MyCustomerGroupPricingRelationManagerExtension extends \Lunar\Admin\Support\Extending\RelationManagerExtension -{ - public function extendForm(\Filament\Forms\Form $form): \Filament\Forms\Form - { - return $form->schema([ - ...$form->getComponents(withHidden: true), - - \Filament\Forms\Components\TextInput::make('custom_column') - ]); - } - - public function extendTable(\Filament\Tables\Table $table): \Filament\Tables\Table - { - return $table->columns([ - ...$table->getColumns(), - \Filament\Tables\Columns\TextColumn::make('product_code') - ]); - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\ProductResource\RelationManagers\CustomerGroupPricingRelationManager::class => MyCustomerGroupPricingRelationManagerExtension::class, -]); -``` diff --git a/docs/admin/extending/resources.md b/docs/admin/extending/resources.md deleted file mode 100644 index 417bd6f69f..0000000000 --- a/docs/admin/extending/resources.md +++ /dev/null @@ -1,62 +0,0 @@ -# Extending Resources - -## MyProductResourceExtension - -An example of extending the ProductResource - -```php -class MyProductResourceExtension extends \Lunar\Admin\Support\Extending\ResourceExtension -{ - public function extendForm(\Filament\Forms\Form $form): \Filament\Forms\Form - { - return $form->schema([ - ...$form->getComponents(withHidden: true), - - \Filament\Forms\Components\TextInput::make('custom_column') - ]); - } - - public function extendTable(\Filament\Tables\Table $table): \Filament\Tables\Table - { - return $table->columns([ - ...$table->getColumns(), - \Filament\Tables\Columns\TextColumn::make('product_code') - ]); - } - - public function getRelations(array $managers) : array - { - return [ - ...$managers, - // This is just a standard Filament relation manager. - // see https://filamentphp.com/docs/3.x/panels/resources/relation-managers#creating-a-relation-manager - MyCustomProductRelationManager::class, - ]; - } - - public function extendPages(array $pages) : array - { - return [ - ...$pages, - // This is just a standard Filament page - // see https://filamentphp.com/docs/3.x/panels/pages#creating-a-page - 'my-page-route-name' => MyPage::route('/{record}/my-page'), - ]; - } - - public function extendSubNavigation(array $nav) : array - { - return [ - ...$nav, - // This is just a standard Filament page - // see https://filamentphp.com/docs/3.x/panels/pages#creating-a-page - MyPage::class, - ]; - } -} - -// Typically placed in your AppServiceProvider file... -LunarPanel::extensions([ - \Lunar\Admin\Filament\Resources\ProductResource::class => MyProductResourceExtension::class, -]); -``` diff --git a/docs/admin/overview.md b/docs/admin/overview.md deleted file mode 100644 index c8a5bf4ef5..0000000000 --- a/docs/admin/overview.md +++ /dev/null @@ -1,31 +0,0 @@ -# Introduction - -Lunar's admin panel is powered by **Filament v3**. It allows you to easily extend the admin panel to suit your project. - -With the panel you can administer your products, collections, orders, customers, discounts, settings and much more. - -## Registering - -If you followed the core installation instructions or have installed a starter kit, you will likely already have this in place. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; - -class AppServiceProvider extends ServiceProvider -{ - public function register(): void - { - LunarPanel::register(); - } -``` - -## Contributing - -If you wish to contribute to the project, please review the roadmap at https://github.com/orgs/lunarphp/projects/8/views/8 - -You can request to contribute on an issue in the backlog, or you can propose a new issue. - -::: tip -Here's a guide on how to set-up your development environment ready for contributing to Lunar. -[Setting Up Lunar For Local Development](/core/local-development) -::: diff --git a/docs/core/configuration.md b/docs/core/configuration.md deleted file mode 100644 index a843ddcc47..0000000000 --- a/docs/core/configuration.md +++ /dev/null @@ -1,117 +0,0 @@ -# Configuration - -## Overview - -Configuration for Lunar is separated into individual files under `config/lunar`. -You can either override the different config options adhoc or you can publish all the configuration options and tweak -as you see fit. - -```bash -php artisan vendor:publish --tag=lunar -``` - -### Database Table Prefix - -`lunar/database.php` - -So that Lunar tables do not conflict with your existing application database tables, you can specify a prefix to use. If you change this after installation, you are on your own - happy renaming! - -```php - 'table_prefix' => 'lunar_', -``` - -### Database Connection - -`lunar/database.php` - - By default, the package uses the default database connection defined in Laravel. Here specify a custom database connection for Lunar. - -```php -'connection' => 'some_custom_connection', -``` - -If you are using a custom database connection that is not the default connection in your Laravel configuration, you need to specify it in the .env file as well. - -``` -ACTIVITY_LOGGER_DB_CONNECTION=some_custom_connection -``` - -In our package, we utilize Spatie's [laravel-activitylog](https://spatie.be/docs/laravel-activitylog) for logging. The mentioned configuration allows the activity logger to use a different database connection instead of the default database connection. - -### Orders - -`lunar/orders.php` - -Here you can set up the statuses you wish to use for your orders. - -```php -'draft_status' => 'awaiting-payment', -'statuses' => [ - 'awaiting-payment' => [ - 'label' => 'Awaiting Payment', - 'color' => '#848a8c', - ], - 'payment-received' => [ - 'label' => 'Payment Received', - 'color' => '#6a67ce', - ], -], -``` - -### Media - -`lunar/media.php` - -Transformations for all uploaded images. - -```php -'transformations' => [ - 'zoom' => [ - 'width' => 500, - 'height' => 500, - ], - 'large' => [ - // ... - ], - 'medium' => [ - // ... - ], - 'small' => [ - // ... - ], -], -``` - -### Products - -`lunar-hub/products.php` - -```php -'disable_variants' => false, -'sku' => [ - 'required' => true, - 'unique' => true, -], -'gtin' => [ - 'required' => false, - 'unique' => false, -], -'mpn' => [ - 'required' => false, - 'unique' => false, -], -'ean' => [ - 'required' => false, - 'unique' => false, -], -``` - -### Pricing - -`lunar/pricing.php` - -If you want to store pricing inclusive of tax then set this config value to `true`. - -```php -'stored_inclusive_of_tax' => false, -``` diff --git a/docs/core/contributing.md b/docs/core/contributing.md deleted file mode 100644 index ce5a7c5b1c..0000000000 --- a/docs/core/contributing.md +++ /dev/null @@ -1,95 +0,0 @@ -# Contributing - -## Overview - -Lunar is an open source project, and so by its very nature, welcomes contributions. - -You can contribute to the project in many different ways. Reporting bugs, fixing bugs, helping with the documentation, -making suggestions and submitting improvements to the software. - -## Monorepo - -Lunar uses a monorepo [lunarphp/lunar](https://github.com/lunarphp/lunar) approach to maintaining its codebase. [Monorepos](https://en.wikipedia.org/wiki/Monorepo) are quite common, -but may not be familiar to some. The monorepo helps us to organise the code for ease of development. - -## Contributing Code - -The basic process for contributing to Lunar is as follows... - -1. Fork the monorepo -2. Clone your fork locally -3. Make your changes -4. Ensure the tests run and complete successfully -5. Submit a pull request - -However, if you're not used to working with monorepo's and setting them up inside a test Laravel application, no problem! - -::: tip -Here's a guide on how to set-up your development environment ready for contributing to Lunar. -[Setting Up Lunar For Local Development](/core/local-development) -::: - -## Found a Bug? - -If you find a bug in the software please raise a GitHub Issue on the -[lunarphp/lunar](https://github.com/lunarphp/lunar/issues) repository. Please ensure that your issue includes the -following: - -**Minimum** - -- Clear title and description of the issue -- Steps on how to reproduce the issue - -**Ideal** - -- An accompanying Pull Request with a test to demonstrate the issue. - -Lunar is an open source project and as such we want contribution to be as accessible as possible and to enable -contributors to actively collaborate on features and issues. By making sure you provide as much information as -possible you are giving your issue the best chance to get the attention it needs. - -:::info -Be aware that creating an issue does not mean it will get activity straight away, please be patient and understand we -will do our best to look into it as soon as possible. - -Open source code belongs to all of us, and it's all of our responsibility to push it forward. -::: - -## Proposing a Feature - -Before you start coding away on the next awesome feature, we highly recommend starting a -[discussion](https://github.com/lunarphp/lunar/discussions) to check that your contribution will be welcomed. We would -hate for you to spend valuable time on something that won't be merged into Lunar. - -However, you're more than welcome to code away on your idea if you think it will help the discussion. - -## Making a Pull Request - -When making a [pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request), -there should be a suitable template for you to follow to ensure the bug or feature can be reviewed in a timely manner. -If the pull request is missing information or is unclear about what it offers or solves, it could delay progress or be -closed. -A PR should be able to include the following: - -- The title should be relevant and quickly explain what to expect inside -- A clear description of the feature or fix -- References to any issues the PR resolves -- Label as either a `bug`, `enhancement`, `feature` or `documentation` -- Any relevant documentation updates -- Automated tests with adequate code coverage - -## Documentation Updates - -If you would like to contribute to the documentation you can do easily by following these instructions... - -1. Fork the monorepo `lunarphp/lunar` -2. Clone your fork locally -3. In your terminal change to the `/docs` directory -4. Run `npm install` -5. Run `npm run docs:dev` to preview the documentation locally -6. Make your changes -7. Submit a pull request - -Lunar uses [VitePress](https://vitepress.dev/) for our documentation site which uses -[Markdown](https://www.markdownguide.org/basic-syntax/) files to store the content. You'll find these Markdown files in -the `/docs` directory. diff --git a/docs/core/extending/carts.md b/docs/core/extending/carts.md deleted file mode 100644 index 4c4a6491fd..0000000000 --- a/docs/core/extending/carts.md +++ /dev/null @@ -1,134 +0,0 @@ -# Cart Extending - -## Overview - -Carts are a central part of any E-Commerce storefront. We have designed Carts to be easily extended, so you can add any logic you need for your storefront throughout its lifetime. - -## Pipelines - -### Adding a Cart Pipeline - -All pipelines are defined in `config/lunar/cart.php` - -```php -'pipelines' => [ - /* - |-------------------------------------------------------------------------- - | Run these pipelines when the cart is calculating. - |-------------------------------------------------------------------------- - */ - 'cart' => [ - \Lunar\Pipelines\Cart\CalculateLines::class, - \Lunar\Pipelines\Cart\ApplyShipping::class, - \Lunar\Pipelines\Cart\Calculate::class, - ], - /* - |-------------------------------------------------------------------------- - | Run these pipelines when the cart lines are being calculated. - |-------------------------------------------------------------------------- - */ - 'cart_lines' => [ - \Lunar\Pipelines\CartLine\GetUnitPrice::class, - ], -], -``` - -You can add your own pipelines to the configuration, they might look something like: - -```php - [ - // ... - App\Pipelines\Cart\CustomCartPipeline::class, -], -``` - -::: tip -Pipelines will run from top to bottom -::: - -## Actions - -During the lifecycle of a Cart, various actions are taken. While generally what Lunar provides will be fine for most storefronts, there are times where you may want something done slightly differently. For this reason we have made all actions configurable, so you can swap them out as you see fit. - -Actions are defined in `config/lunar/carts` and if you need to replace an action, check the class of the action you want to change to see what it is expecting. - -## Action validation - -You may wish to provide some validation against actions before they run. Your own validation may look something like: - - -```php -parameters['quantity'] ?? 0; - - // ... - - if (!$condition) { - return $this->fail('cart', 'Something went wrong'); - } - - - return $this->pass(); - } -} - -``` - -You can then register this class against the corresponding action in `config/lunar/cart.php`: - -```php -'validators' => [ - 'add_to_cart' => [ - // ... - \App\Validation\CartLine\CartLineQuantity::class, - ], - // ... -], -``` - -If validation fails, a `Lunar\Exceptions\CartException` will be thrown. You will be able to access errors like you can on Laravel's own validation responses. - -```php -try { - $cart->setShippingOption($option); -} catch (CartException $e) { - $e->errors()->all(); -} -``` diff --git a/docs/core/extending/discounts.md b/docs/core/extending/discounts.md deleted file mode 100644 index 3e98b70239..0000000000 --- a/docs/core/extending/discounts.md +++ /dev/null @@ -1,96 +0,0 @@ - -# Discounts - -## Overview - -If you want to add additional functionality to Discounts, you can register your own custom discount types. - -## Registering a discount type. - -```php -use Lunar\Facades\Discounts; - -Discounts::addType(MyCustomDiscountType::class); -``` - - -```php -label('My label') - ->required(), - ]; - } - - /** - * Mutate the model data before displaying it in the admin form. - */ - public function lunarPanelOnFill(array $data): array - { - // optionally do something with $data - return $data; - } - - /** - * Mutate the form data before saving it to the discount model. - */ - public function lunarPanelOnSave(array $data): array - { - // optionally do something with $data - return $data; - } -} -``` diff --git a/docs/core/extending/models.md b/docs/core/extending/models.md deleted file mode 100644 index 8ce9220ef6..0000000000 --- a/docs/core/extending/models.md +++ /dev/null @@ -1,147 +0,0 @@ -# Models - -## Overview - -Lunar provides a number of Eloquent Models and quite often in custom applications you will want to add your own relationships and functionality to these models. - -::: warning -We highly suggest using your own Eloquent Models to add additional data, rather than trying to change fields on the core Lunar models. -::: - -## Replaceable Models -All Lunar models are replaceable, this means you can instruct Lunar to use your own custom model, throughout the ecosystem, using dependency injection. - - -### Registration -We recommend registering your own models for your application within the boot method of your Service Provider. - -When registering your models, you will need to set the Lunar model's contract as the first argument then your own model implementation for the second. - - -```php -/** - * Bootstrap any application services. - * - * @return void - */ -public function boot() -{ - \Lunar\Facades\ModelManifest::replace( - \Lunar\Models\Contracts\Product::class, - \App\Model\Product::class, - ); -} -``` - -#### Registering multiple Lunar models. - -If you have multiple models you want to replace, instead of manually replacing them one by one, you can specify a directory for Lunar to look in for Lunar models to use. -This assumes that each model extends its counterpart model i.e. `App\Models\Product` extends `Lunar\Models\Product`. - -```php -/** - * Bootstrap any application services. - * - * @return void - */ -public function boot() -{ - \Lunar\Facades\ModelManifest::addDirectory( - __DIR__.'/../Models' - ); -} -``` - -### Route binding - -Route binding is supported for your own routes and simply requires the relevant contract class to be injected. - -```php -Route::get('products/{id}', function (\Lunar\Models\Contracts\Product $product) { - $product; // App\Models\Product -}); -``` - -### Relationship support - -If you replace a model which is used in a relationship, you can easily get your own model back via relationship methods. Assuming we want to use our own instance of `App\Models\ProductVariant`. - -```php -// In our service provider. -public function boot() -{ - \Lunar\Facades\ModelManifest::replace( - \Lunar\Models\Contracts\ProductVariant::class, - \App\Model\ProductVariant::class, - ); -} - -// Somewhere else in your code... - -$product = \Lunar\Models\Product::first(); -$product->variants->first(); // App\Models\ProductVariant -``` - -### Static call forwarding - -If you have custom methods in your own model, you can call those functions directly from the Lunar model instance. - -Assuming we want to provide a new function to a product variant model. - -```php -belongsTo(Ticket::class, 'ticket_id'); -}); -``` - -See [https://laravel.com/docs/eloquent-relationships#dynamic-relationships](https://laravel.com/docs/eloquent-relationships#dynamic-relationships) for more information. diff --git a/docs/core/extending/orders.md b/docs/core/extending/orders.md deleted file mode 100644 index 0709a9ad9c..0000000000 --- a/docs/core/extending/orders.md +++ /dev/null @@ -1,63 +0,0 @@ -# Order Extending - -## Overview - -If you want to add additional functionality to the Order creation process, you can do so using pipelines. - -## Pipelines - -### Adding an Order Pipeline - -All pipelines are defined in `config/lunar/orders.php` - -```php -'pipelines' => [ - 'creation' => [ - Lunar\Pipelines\Order\Creation\FillOrderFromCart::class, - Lunar\Pipelines\Order\Creation\CreateOrderLines::class, - Lunar\Pipelines\Order\Creation\CreateOrderAddresses::class, - Lunar\Pipelines\Order\Creation\CreateShippingLine::class, - Lunar\Pipelines\Order\Creation\CleanUpOrderLines::class, - Lunar\Pipelines\Order\Creation\MapDiscountBreakdown::class, - // ... - ], -], -``` - -You can add your own pipelines to the configuration, they might look something like: - -```php - [ - 'creation' => [ - // ... - App\Pipelines\Orders\CustomOrderPipeline::class, - ], -], -``` - -::: tip -Pipelines will run from top to bottom -::: \ No newline at end of file diff --git a/docs/core/extending/payments.md b/docs/core/extending/payments.md deleted file mode 100644 index e3792b382b..0000000000 --- a/docs/core/extending/payments.md +++ /dev/null @@ -1,278 +0,0 @@ -# Payments - -## Overview - -Lunar provides an easy way for you to add your own payment drivers, by default, there is a basic `OfflinePayment` driver -that ships with Lunar, additional providers should be added to your Storefront via addons. - -Below is a list of available payment drivers. - -## Available drivers - -### First party - -- [Stripe](https://github.com/lunarphp/stripe) - -### Community - -> Made your own driver you want listing here? Get in touch on our discord channel and we'll get it added. - -## Building your own - -A payment driver should take into account 2 fundamentals: - -* Capturing a payment (whether straight away, or at a later date) -* Refunding an existing payment - -### Registering your driver - -```php -use Lunar\Facades\Payments; - -Payments::extend('custom', function ($app) { - return $app->make(CustomPayment::class); -}); -``` - -### The payment driver class - -First, we'll show you the complete class and then break it down to see what's going on. - -```php -order) { - if (!$this->order = $this->cart->order) { - $this->order = $this->cart->createOrder(); - } - } - - // ... - - $response = new PaymentAuthorize( - success: true, - message: 'The payment was successful', - orderId: $this->order->id, - paymentType: 'custom-type' - ); - - PaymentAttemptEvent::dispatch($response) - - return $response; - } - - /** - * {@inheritDoc} - */ - public function refund(Transaction $transaction, int $amount = 0, $notes = null): PaymentRefund - { - // ... - return new PaymentRefund(true); - } - - /** - * {@inheritDoc} - */ - public function capture(Transaction $transaction, $amount = 0): PaymentCapture - { - // ... - return new PaymentCapture(true); - } -} -``` - -This is the most basic implementation of a driver, you can see we are extending an `AbstractPayment`. This is a class -which is provided by Lunar and contains some useful helpers you can utilise in your own driver. - -[See available methods](#abstract-class-methods) - -#### Releasing payments - -```php -public function authorize(); -``` - -This is where you'd check the payment details which have been passed in, create any transactions for the order and -return the response. - -If you're not taking payment straight away, you should set any transactions to the type of `intent`. When you then later -capture the payment, we would recommend creating another transaction that is related to that via -the `parent_transaction_id`. - -#### Capturing payments - -```php -public function capture(Transaction $transaction, $amount = 0): PaymentCapture -``` - -When you have a transaction that has a type of `intent` the Staff member who is logged into the hub can then decide to -capture it so the card used gets charged the amount that has been authorised. - -You can pass an optional amount, but be cautious as you generally cannot capture an amount that exceeds the original -amount on the `intent` transaction. If you capture an amount less, services like Stripe will treat that as a partial -refund and no further captures can take place on that order. - -Here you should create an additional transaction against the order to show how much has been captured. - -#### Refunding payments - -```php -public function refund(Transaction $transaction, int $amount = 0, $notes = null): PaymentRefund -``` - -When refunding a transaction, you can only do so to one that's been captured. If you need to refund an order that hasn't -been captured you should instead capture an amount less to what's been authorised. - -You should only refund transactions with the type `capture`. - - - -## The AbstractPayment class - -### Available methods - -- [`cart`](#cart) -- [`order`](#order) -- [`withData`](#withData) -- [`setConfig`](#setconfig) - -#### `cart` - -```php -public function cart(Cart $cart): self -``` - -Sets the `$cart` property on the payment driver. When using the `release` method we recommend expecting a `$cart` -instance and checking for the existence of an order. - -#### `order` - -```php -public function order(Order $order): self -``` - -Sets the `$order` property on the payment driver. - -#### `withData` - -```php -public function withData(array $data): self -``` - -This method allows you to add any additional data to the payment driver, this can be anything that the payment driver -needs to function, for example. - -```php -Payments::driver('stripe')->withData([ - 'payment_intent' => $paymentIntentId -])->authorize(); -``` - -#### `setConfig` - -```php -public function setConfig(array $config): self -``` - -Here you can set up any additional config for this payment driver. By default, this will be called when you register -your payment driver and will take any values which are set in `config/lunar/payments.php` for that type. - -## Creating transactions - -Depending on how your driver works, you're likely going to need to create some transactions depending on different -scenarios. - -### Database Schema - -``` -Lunar\Models\Transaction -``` - -| Field | Description | Example | -|:----------------------|:------------------------------------------------|:--------------------------------------------------------------------------------------------| -| id | | -| parent_transaction_id | The ID of the related transaction, nullable | -| order_id | The ID of the order this transaction relates to | -| success | Whether or not the transaction was successful | 1 -| type | Whether `intent`,`capture` or `refund` | `intent` -| driver | The driver used i.e. `stripe` | `stripe` -| amount | The amount for the transaction in cents | `10000` -| reference | The reference for the driver to use | `STRIPE_123456` -| status | Usually populated from the payment provider | `success` -| notes | Any additional notes for the transaction | -| card_type | The card type | `visa` -| last_four | The last four digits of the card used | `1234` -| captured_at | The DateTime the transaction was captured | -| meta | Any additional meta info for the transaction | `{"cvc_check": "pass", "address_line1_check": "pass", "address_postal_code_check": "pass"}` -| created_at | | -| updated_at | | - -### Best Practices - -#### Releasing - -When releasing a payment, if you're not charging the card straight away, you should create a transaction with -type `intent`. This tells Lunar you intend to charge the card at a later date. - -```php -Transaction::create([ - //... - 'type' => 'intent', -]); -``` - -If you are charging the card straight away, set the type to `capture`. - -```php -Transaction::create([ - //... - 'type' => 'capture', -]); -``` - -#### Capturing - -:::tip -If you're already charging the card, you can skip this as you already have payment. 🥳 -::: - -When capturing a transaction, you should create an additional transaction with the amount that's been captured. Even if -this is the same amount as the `intent` transaction. - -```php -$intent = Transaction::whereType('intent')->first(); - -Transaction::create([ - //... - 'parent_transaction_id' => $intent->id, - 'type' => 'capture', - 'amount' => 2000, -]); -``` - -#### Refunding - -```php -$capture = Transaction::whereType('capture')->first(); - -Transaction::create([ - //... - 'parent_transaction_id' => $capture->id, - 'type' => 'refund', -]); -``` diff --git a/docs/core/extending/search.md b/docs/core/extending/search.md deleted file mode 100644 index 0ed3a5be93..0000000000 --- a/docs/core/extending/search.md +++ /dev/null @@ -1,139 +0,0 @@ -# Search - -## Overview - -Good search is the backbone of any storefront so Lunar aims to make this as extensible as possible so you can index what -you need for your front-end, without compromising on what we require our side in the hub. - -There are three things to consider when you want to extend the search: - -- Searchable fields -- Sortable fields -- Filterable fields - -## Default index values - -Eloquent models which use the `Lunar\Base\Traits\Searchable` trait will use an indexer class to tell Scout how each it -should be indexed, if an indexer isn't mapped in the config the default `EloquentIndexer` (provided by Lunar) will be -used. - -This class will map a basic set of fields to the search index: - -- The ID of the model -- Any `searchable` attributes. - -Some models require a bit more information to be indexed, such as SKU's, prices etc. For these scenarios, dedicated -indexers have been created and are mapped in the config already. - -#### `Lunar\Search\ProductIndexer` - -Fields which are indexed: - -- The ID of the model -- Any `searchable` attributes. -- The product `status` -- The product `product_type` -- The `brand` (if applicable) -- The ProductVariant `skus` related to the product. -- The `created_at` timestamp - -## Mapping custom indexers - -All indexers are mapped in `config/search.php` under `indexers`, if a model isn't mapped here then it will -simply use the default `ELoquentIndexer`. To change how each model is indexed, simply map it like so: - -```php -return [ - // ... - 'indexers' => [ - Lunar\Models\Product::class => App\Search\CustomProductIndexer::class, - ], -], -``` - -## Creating a custom indexer - -To create your own indexer, simply create a custom class like so: - -```php -with([ - 'thumbnail', - 'variants', - 'productType', - 'brand', - ]); - } - - // Scout method to get the ID used for indexing - public function getScoutKey(Model $model): mixed - { - return $model->getKey(); - } - - // Scout method to get the column used for the ID. - public function getScoutKeyName(Model $model): mixed - { - return $model->getKeyName(); - } - - // Simple array of any sortable fields. - public function getSortableFields(): array - { - return [ - 'created_at', - 'updated_at', - ]; - } - - // Simple array of any filterable fields. - public function getFilterableFields(): array - { - return [ - '__soft_deleted', - ]; - } - - // Return an array representing what should be sent to the search service i.e. Algolia - public function toSearchableArray(Model $model, string $engine): array - { - return array_merge([], $this->mapSearchableAttributes($model)); - } -} -``` - -The `EloquentIndexer` class implements the `Lunar\Search\Interfaces\ModelIndexerInterface` so if your class doesn't -extend the Eloquent one, you must implement this interface. - -There are some methods which are available just on the `EloquentIndexer` but not defined on the interface are: - -#### mapSearchableAttributes - -```php -mapSearchableAttributes(Model $model): array -``` - -This method will take all `searchable` attributes for the model attribute type and map them into the index, -this means when you add searchable attributes in the hub they will automatically be added to the index. \ No newline at end of file diff --git a/docs/core/extending/shipping.md b/docs/core/extending/shipping.md deleted file mode 100644 index a9a5a74172..0000000000 --- a/docs/core/extending/shipping.md +++ /dev/null @@ -1,84 +0,0 @@ -# Shipping - -## Overview - -On your checkout, if your customer has added an item that needs shipping, you're likely going to want to display some shipping options. Currently the best way to do this is to implement your own by adding a `ShippingModifier` and adding using that to determine what shipping options you want to make available and add them to the `ShippingManifest` class. - -## Adding a Shipping Modifier - -Create your own custom shipping provider: - -```php -namespace App\Modifiers; - -use Lunar\Base\ShippingModifier; -use Lunar\DataTypes\Price; -use Lunar\DataTypes\ShippingOption; -use Lunar\Facades\ShippingManifest; -use Lunar\Models\Cart; -use Lunar\Models\Currency; -use Lunar\Models\TaxClass; - -class CustomShippingModifier extends ShippingModifier -{ - public function handle(Cart $cart, \Closure $next) - { - // Get the tax class - $taxClass = TaxClass::first(); - - ShippingManifest::addOption( - new ShippingOption( - name: 'Basic Delivery', - description: 'A basic delivery option', - identifier: 'BASDEL', - price: new Price(500, $cart->currency, 1), - taxClass: $taxClass - ) - ); - - ShippingManifest::addOption( - new ShippingOption( - name: 'Pick up in store', - description: 'Pick your order up in store', - identifier: 'PICKUP', - price: new Price(0, $cart->currency, 1), - taxClass: $taxClass, - // This is for your reference, so you can check if a collection option has been selected. - collect: true - ) - ); - - // Or add multiple options, it's your responsibility to ensure the identifiers are unique - ShippingManifest::addOptions(collect([ - new ShippingOption( - name: 'Basic Delivery', - description: 'A basic delivery option', - identifier: 'BASDEL', - price: new Price(500, $cart->currency, 1), - taxClass: $taxClass - ), - new ShippingOption( - name: 'Express Delivery', - description: 'Express delivery option', - identifier: 'EXDEL', - price: new Price(1000, $cart->currency, 1), - taxClass: $taxClass - ) - ])); - - return $next($cart); - } -} - -``` - -In your service provider: - -```php -public function boot(\Lunar\Base\ShippingModifiers $shippingModifiers) -{ - $shippingModifiers->add( - CustomShippingModifier::class - ); -} -``` diff --git a/docs/core/extending/taxation.md b/docs/core/extending/taxation.md deleted file mode 100644 index eaa1bfb4df..0000000000 --- a/docs/core/extending/taxation.md +++ /dev/null @@ -1,103 +0,0 @@ -# Taxation - -## Overview - -Taxation is a tricky business and sometimes what Lunar offers simply won't be enough, and we completely understand. This why Taxation is driver based, so you can add your own logic if you need to. - -By default we have a `SystemTaxManager` which will use Lunar's internal models and database as outlined above. If you need to write our own implementation, or if you're creating an add on for Tax, you can change the driver in the `config/taxes.php` config file. - -```php - 'system', -]; -``` - -## Writing Your Own Driver - -To write your own driver you need to add a class which implements the `Lunar\Base\TaxManager` interface and has the following methods: - -```php -make(TaxJar::class); - }) -} -``` - -You can then set this as the driver in the taxes config. diff --git a/docs/core/installation.md b/docs/core/installation.md deleted file mode 100644 index a212b3a44f..0000000000 --- a/docs/core/installation.md +++ /dev/null @@ -1,101 +0,0 @@ -# Installation - -## Requirements - -- PHP >= 8.2 -- Laravel 11, 12 -- MySQL 8.0+ / PostgreSQL 9.4+ -- exif PHP extension (on most systems it will be installed by default) -- intl PHP extension (on most systems it will be installed by default) -- bcmath PHP extension (on most systems it will be installed by default) -- GD PHP extension (used for image manipulation) - -## Install Lunar - -### Composer Require Package - -```sh -composer require lunarphp/lunar:"^1.0" -W -``` - -### Add the LunarUser Trait - -Some parts of the core rely on the User model having certain relationships set up. We have bundled these into a trait -and an interface, which you must add to any models that represent users in your database. - -```php -use Lunar\Base\Traits\LunarUser; -use Lunar\Base\LunarUser as LunarUserInterface; -// ... - -class User extends Authenticatable implements LunarUserInterface -{ - use LunarUser; - // ... -} -``` - -### Publish Configuration -Before you run the Lunar installer command, you may wish to customise some of the set-up. - - -```sh -php artisan vendor:publish --tag=lunar -``` - -## Register the admin panel - -The admin panel needs registering in your app service provider before you can use it. - -```php -use Lunar\Admin\Support\Facades\LunarPanel; - -class AppServiceProvider extends ServiceProvider -{ - public function register(): void - { - LunarPanel::register(); - } -``` - -## Run the Artisan Installer - -```sh -php artisan lunar:install -``` - -This will take you through a set of questions to configure your Lunar install. The process includes... - -- Creating a default admin user (if required) -- Seeding initial data -- Inviting you to star our repo on GitHub ⭐ - -You should now be able to access the panel at `https:///lunar`. - -## Advanced Installation Options - -### Telemetry insights - -Lunar will phone home to send anonymous usage insights, the data we capture does not identify your store in any way, it -exists to help us gain an understanding of how Lunar is used. You can easily opt out of this by adding the following to -your service provider's boot method: - -```php -\Lunar\Facades\Telemetry::optOut(); -``` - -### Table Prefix - -Lunar uses table prefixes to avoid conflicts with your app's tables. You can change this in the [configuration](/core/configuration.html). - -### User ID Field Type - -Lunar assumes your User ID field is a "BIGINT". If you are using an "INT" or "UUID", you will want to update the configuration in `config/lunar/database.php` to set the correct field type before running the migrations. - -### Publish Migrations - -You can optionally publish Lunar's migrations so they're added to your Laravel app. - -```sh -php artisan vendor:publish --tag=lunar.migrations -``` diff --git a/docs/core/local-development.md b/docs/core/local-development.md deleted file mode 100644 index 658b528f27..0000000000 --- a/docs/core/local-development.md +++ /dev/null @@ -1,60 +0,0 @@ -# Setting Up Lunar For Local Development - -## Overview - -This guide is here to help you set-up Lunar locally so you can contribute to the core and admin hub. - -## Before your start - -You will need a Laravel application to run Lunar in. - -## Set-Up - -In the root folder of your Laravel application, create a "packages" folder. - -```sh -mkdir packages && cd packages -```` - -Add the "packages" folder to your `.gitignore` file so the folder is not committed to your Git repository. - -``` -... -/.idea -/.vscode -/packages -``` - -Fork and then clone the [monorepo](https://github.com/lunarphp/lunar) to the `packages` folder, e.g. `/packages/lunar/`. - -```sh -git clone https://github.com/YOUR-USERNAME/lunar -```` - -Update your `composer.json` file similar to the following. - -```json - "repositories": [{ - "type": "path", - "url": "packages/*", - "symlink": true - }], - - "require": { - "lunarphp/lunarmono": "*", - } -```` - -Ensure minimum stability is set for development -```json - "minimum-stability": "dev", -```` - -Run `composer update` from your Laravel application's root directory and fingers crossed you're all up and running,. - -```sh -composer update -```` - -## Done -You can now follow the Lunar installation process and start contributing. diff --git a/docs/core/overview.md b/docs/core/overview.md deleted file mode 100644 index 84d2db678b..0000000000 --- a/docs/core/overview.md +++ /dev/null @@ -1,28 +0,0 @@ -# Welcome to Lunar! - -We are delighted you are considering Lunar for your project. We've spent a lot of time developing this project to bring -headless e-commerce functionality to Laravel. - -## What is Lunar? - -Lunar is a [Laravel e-commerce package](https://lunarphp.io/) which brings functionality akin to Shopify and other e-commerce platforms -to Laravel. You have complete freedom to create your own storefront(s), but we've already done the hard work for you in -the backend. - -## Tech Stack - -Lunar is comprised of two packages; `lunarphp/core` which provides the e-commerce functionality and `lunarphp/lunar` -which provides an admin panel built on Filament. - -:::info -Although the admin panel uses Laravel Livewire, there is no requirement for your app to use Livewire itself. -::: - -## What are the future plans? - -Lunar as a company has grand plans to continue developing new functionality to help you build awesome e-commerce -websites. We want Lunar to be a true alternative to the likes of Magento, Shopify, WooCommerce, etc. - -## Get started - -Enough waffle! Let's help you [install Lunar...](/core/installation) diff --git a/docs/core/reference/activity-log.md b/docs/core/reference/activity-log.md deleted file mode 100644 index 666e017d0c..0000000000 --- a/docs/core/reference/activity-log.md +++ /dev/null @@ -1,13 +0,0 @@ -# Activity Log - -## Overview - -We've made a design choice to have activity logging throughout Lunar when it comes to changes happening on Eloquent models. We believe it's important to keep track of what updates are happening and who is making them. It allows us to provide you with an invaluable insight into what's happening in your store. - -## How it works - -For the actual logging, we have opted to use the incredible package by Spatie, [laravel-activitylog](https://spatie.be/docs/laravel-activitylog). This allows Lunar to keep track changes throughout the system so you can have a full history of what's going on. - -## Enabling on your own models - -If you want to enable logging on your own models you can simply [follow the guides on their website](https://spatie.be/docs/laravel-activitylog) diff --git a/docs/core/reference/addresses.md b/docs/core/reference/addresses.md deleted file mode 100644 index c35908b696..0000000000 --- a/docs/core/reference/addresses.md +++ /dev/null @@ -1,84 +0,0 @@ -# Addresses - -## Overview - -Customers may save addresses to make checking-out easier and quicker. - -## Addresses - -```php -Lunar\Models\Address -``` - -|Field|Description| -|:-|:-| -|`id`|| -|`customer_id`|| -|`title`|nullable| -|`first_name`|| -|`last_name`|| -|`company_name`|nullable| -|`tax_identifier`|nullable| -|`line_one`|| -|`line_two`|nullable| -|`line_three`|nullable| -|`city`|| -|`state`|nullable| -|`postcode`|nullable| -|`country_id`|| -|`delivery_instructions`|| -|`contact_email`|| -|`contact_phone`|| -|`last_used_at`|Timestamp for when the address was last used in an order.| -|`meta`|JSON| -|`shipping_default`|Boolean| -|`billing_default`|Boolean| -|`created_at`|| -|`updated_at`|| - -## Countries - -```php -Lunar\Models\Country -``` - -|Field|Description| -|:-|:-| -|`id`|| -|`name`|| -|`iso3`|| -|`iso2`|| -|`phonecode`|| -|`capital`|| -|`currency`|| -|`native`|| -|`emoji`|Flag| -|`emoji_u`|Flag| -|`created_at`|| -|`updated_at`|| - - -## States - -```php -Lunar\Models\State -``` - -|Field|Description| -|:-|:-| -|`id`|| -|`country_id`|| -|`name`|| -|`code`|| -|`created_at`|| -|`updated_at`|| - -## Address Data - -Data for Countries and States is provided by https://github.com/dr5hn/countries-states-cities-database. - -You can use the following command to import countries and states. - -```sh -php artisan lunar:import:address-data -``` diff --git a/docs/core/reference/associations.md b/docs/core/reference/associations.md deleted file mode 100644 index a4debeee7a..0000000000 --- a/docs/core/reference/associations.md +++ /dev/null @@ -1,154 +0,0 @@ -# Associations - -## Overview - -Associations allow you to relate products to each other. There are a few different ways you can associate two products and this type of relationship would define how they are presented on your storefront and also how Lunar sees them. - -## Loading associations - -```php -$product->associations -``` - -This will return a Laravel collection of `Lunar\Models\ProductAssociation` models. On each model you will have access to the following: - -```php -// Lunar\Models\ProductAssociation -$association->parent; // The owning product who has the associations -$association->target // The associated (cross-sell, up-sell, alternate) product. -$association->type // Whether it's cross-sell, up-sell or alternate. -``` - -## Types of association - -### Cross Sell - -Cross selling is the process of encouraging customers to purchase products or services in addition to the original items they intended to purchase. Oftentimes the cross-sold items are complementary to one another so customers have more of a reason to purchase both of them. - -For example, if you're selling a Phone on your store, you may want to present some headphones or a case that works with the phone which the customer may be interested in. - -**Adding a cross-sell association** - -```php -$product->associate( - \Lunar\Models\Product $crossSellProduct, - \Lunar\Models\ProductAssociation::CROSS_SELL -); - -$product->associate( - [$productA, $productB], - \Lunar\Models\ProductAssociation::CROSS_SELL -); -``` - -**Fetching cross-sell products** - -```php -// Via a relationship scope -$product->associations()->crossSell()->get(); - -// Via the type scope -$product->associations()->type(ProductAssociation::CROSS_SELL); -``` - -### Up Sell - -Upselling is the process of encouraging customers to upgrade or include add-ons to the product or service they’re buying. The product or service being promoted is typically a more expensive product or add ons which can increase the overall order value. - -Using the phone example from above, lets consider we have two phones. - -- Phone 16gb 5" Screen -- Phone 32gb 6" Screen - -When editing the 16gb Phone we would add the 32gb version as an up-sell association and could present this when a user is viewing the 16gb version. - -```php -$product->associate( - \Lunar\Models\Product $upSellProduct, - \Lunar\Models\ProductAssociation::UP_SELL -); - -$product->associate( - [$productA, $productB], - \Lunar\Models\ProductAssociation::UP_SELL -); -``` - -**Fetching up-sell products** - -```php -// Via a relationship scope -$product->associations()->upSell()->get(); - -// Via the type scope -$product->associations()->type(ProductAssociation::UP_SELL); -``` - -### Alternate - -Alternate products are what you could present the user as an alternative to the current product. This is helpful in situations where the product might be out of stock or not quite fit for purpose and you could show these. - -```php -$product->associate( - \Lunar\Models\Product $alternateProduct, - \Lunar\Models\ProductAssociation::ALTERNATE -); - -$product->associate( - [$productA, $productB], - \Lunar\Models\ProductAssociation::ALTERNATE -); -``` - -**Fetching alternate products** - -```php -// Via a relationship scope -$product->associations()->alternate()->get(); - -// Via the type scope -$product->associations()->type(ProductAssociation::ALTERNATE); -``` - -### Custom types - -Although Lunar comes preloaded with the associations above, you are free to add your own custom association types. - -```php -$product->associate( - \Lunar\Models\Product $alternateProduct, - 'my-custom-type' -); -``` - -You can then fetch all associated products like so: - -```php -$product->associations()->type('my-custom-type')->get(); -``` - -## Removing associations - -You can dissociate products with one simple method. If you only pass through the related models, or an array of models, all associations will be removed. If you wish to only remove associations for a certain type you can pass the type through as the second parameter. - -```php -// Remove this associated product from however many different association types it might have. -$product->dissociate($associatedProduct); - -// Will also accept an array or collection of products. -$product->dissociate([/* ... */]) - -// Only remove this products association if it has been associated as a cross-sell. -$product->dissociate($associatedProduct, Product::CROSS_SELL); -``` - -## Database Schema - -|Field|Description| -|:-|:-| -|`id`|| -|`product_id`|| -|`product_association_id`|| -|`type`|(cross-sell, up-sell, alternate)| -|`created_at`|| -|`updated_at`|| diff --git a/docs/core/reference/attributes.md b/docs/core/reference/attributes.md deleted file mode 100644 index 883a0aa53f..0000000000 --- a/docs/core/reference/attributes.md +++ /dev/null @@ -1,213 +0,0 @@ -# Attributes - -## Overview - -Attributes can be associated to Eloquent models to allow custom data to be stored. Typically, these will be used the most with Products where different information is needed to be stored and presented to visitors. - -For example, a television might have the following attributes assigned... - -* Screen Size -* Screen Technology -* Tuner -* Resolution - -## Attributes - -```php -Lunar\Models\Attribute -``` - -|Field| Description | -|:-|:----------------------------------------------------------------------------------| -|`attribute_type`| Morph map of the model type that can use attribute, e.g. `product` | -|`attribute_group_id`| The associated group | -|`position`| An integer used to define the sorting order of attributes within attribute groups | -|`name`| Laravel Collection of translations `{'en': 'Screen Size'}` | -|`handle`| Kebab-cased reference, e.g. `screen-size` | -|`section`| An optional name to define where an attribute should be used. | -|`type`| The field type to be used, e.g. `Lunar\FieldTypes\Number` | -|`required`| Boolean | -|`default_value`| | -|`configuration`| Meta data stored as a Laravel Collection | -|`system`| If set to true, indicates it should not be deleted | - -### Field Types - - -|Type|Config| -|:-|:-| -|`Lunar\FieldTypes\Number`|Integer or Decimal| -|`Lunar\FieldTypes\Text`|Single-line, Multi-line, Rich Text| -|`Lunar\FieldTypes\TranslatedText`|Single-line, Multi-line, Rich Text| -|`Lunar\FieldTypes\ListField`|An re-orderable list of text values| - -::: tip INFO -More field types will be coming soon. -::: - -### Models that use Attributes - -* Lunar\Models\Product -* Lunar\Models\ProductVariant -* Lunar\Models\Collection - -### Saving Attribute Data - -```php -$product->attribute_data = collect([ - 'meta_title' => new \Lunar\FieldTypes\Text('The best screwdriver you will ever buy!'), - 'pack_qty' => new \Lunar\FieldTypes\Number(2), - 'description' => new \Lunar\FieldTypes\TranslatedText(collect([ - 'en' => new \Lunar\FieldTypes\Text('Blue'), - 'fr' => new \Lunar\FieldTypes\Text('Bleu'), - ])), -]); -``` - - -### Adding attributes to your own model - -```php -use Lunar\Base\Casts\AsAttributeData; -use Lunar\Base\Traits\HasAttributes; - -class Collection extends Model -{ - use HasAttributes; - - /** - * Define which attributes should be cast. - * - * @var array - */ - protected $casts = [ - 'attribute_data' => AsAttributeData::class, - ]; - - //... -} - -``` - -Then ensure you have a [JSON field](https://laravel.com/docs/8.x/migrations#column-method-json) on your model's table called `attribute_data`. - - -::: tip -When loading models it is advised you eager load the attribute data required. -::: - -### Accessing Attribute Data. - -There will come times where you need to be able to retrieve the attribute data you have stored against a model. When you target the `attribute_data` property it will be cast as a collection and resolved into it's corresponding field type. - -```php -dump($product->attribute_data); - -Illuminate\Support\Collection {#1522 ▼ - #items: array:2 [▼ - "name" => Lunar\FieldTypes\TranslatedText {#1533 ▼ - #value: Illuminate\Support\Collection {#1505 ▼ - #items: array:3 [▼ - "de" => Lunar\FieldTypes\Text {#1506 ▼ - #value: "Leren laarzen" - } - "en" => Lunar\FieldTypes\Text {#1514 ▼ - #value: "Leather boots" - } - "fr" => Lunar\FieldTypes\Text {#1502 ▼ - #value: "Bottes en cuires" - } - ] - } - } - "description" => Lunar\FieldTypes\Text {#1537 ▼ - #value: "

I'm a description!

" - } - ] -} -``` - -If you need to just get the value for one field, you can use the `translateAttribute` method on the model: - -```php -// Leather boots -$product->translateAttribute('name'); - -// Bootes en cuires -$product->translateAttribute('name', 'fr'); - -// Leather boots -$product->translateAttribute('name', 'FOO'); -// We will default here to either the current system locale or the first value available. -``` - -### Advanced usage - -```php -use Lunar\Base\Traits\HasAttributes; - -class ProductType extends Model -{ - use HasAttributes; - - //... -} - -``` - -```php - -use Lunar\Base\Casts\AsAttributeData; - -class Product extends Model -{ - /** - * Define which attributes should be cast. - * - * @var array - */ - protected $casts = [ - 'attribute_data' => AsAttributeData::class, - ]; - - //... -} - -``` - -```php - -use Lunar\Base\Casts\AsAttributeData; - -class ProductVariant extends Model -{ - /** - * Define which attributes should be cast. - * - * @var array - */ - protected $casts = [ - 'attribute_data' => AsAttributeData::class, - ]; - - //... -} - -``` - - -## Attribute Groups - -Attribute Groups form a collection of attributes that are logically grouped together for display purposes. - -A good example might be an "SEO" attribute group which has attributes for "Meta Title" and "Meta Description". - -```php -Lunar\Models\AttributeGroup -``` - -|Field|Description| -|:-|:-| -|`name`|Laravel Collection of translations `{'en': 'SEO'}`| -|`handle`|Kebab-cased reference, e.g. `seo`| -|`position`|An integer used to define the sorting order of groups| diff --git a/docs/core/reference/carts.md b/docs/core/reference/carts.md deleted file mode 100644 index 8db0f047db..0000000000 --- a/docs/core/reference/carts.md +++ /dev/null @@ -1,521 +0,0 @@ -# Carts - -## Overview - -Carts are a collection of products (or other custom purchasable types) that you -would like to order. Carts belong to Users (which relate to Customers). - -::: tip -Cart prices are dynamically calculated and are not stored (unlike Orders). -::: - -## Carts - -```php -Lunar\Models\Cart -``` - -| Field | Description | -|:------------|:--------------------------------------------------------------------------------| -| id | Unique ID for the cart. | -| user_id | Can be `null` for guest users. | -| customer_id | Can be `null`. | -| merged_id | If a cart was merged with another cart, it defines the cart it was merged into. | -| currency_id | Carts can only be for a single currency. | -| channel_id | | -| coupon_code | Can be `null`, stores a promotional coupon code, e.g. `SALE20`. | -| created_at | | -| updated_at | When an order was created from the basket, via a checkout. | -| meta | JSON data for saving any custom information. | - -### Creating a cart - -```php -$cart = \Lunar\Models\Cart::create([ - 'currency_id' => 1, - 'channel_id' => 2, -]); -``` - -## Cart Lines - -```php -Lunar\Models\CartLine -``` - -| Field | Description | -|:-----------------|:---------------------------------------------| -| id | | -| cart_id | | -| purchasable_type | e.g. `product_variant` | -| purchasable_id | | -| quantity | | -| created_at | | -| updated_at | | -| meta | JSON data for saving any custom information. | - -```php -$purchasable = \Lunar\Models\ProductVariant::create([/** ... */]); -$cartLine = new \Lunar\Models\CartLine([ - 'cart_id' => 1, - 'purchasable_type' => $purchasable->getMorphClass(), - 'purchasable_id' => $purchasable->id, - 'quantity' => 2, - 'meta' => [ - 'personalization' => 'Love you mum xxx', - ] -]); - -// Or you can use the relationship on the cart. -$cart->lines()->create([/* .. */]); -``` - -### Validation - -When adding items to a cart there are a series of validation actions which are run, which are defined in the `config/lunar/cart.php` config file. - -These actions will throw a `Lunar\Exceptions\Carts\CartException`. - -```php -try { - $cart->add($purchasable, 500); -} catch (\Lunar\Exceptions\Carts\CartException $e) { - $error = $e->getMessage(); -} -``` - -Now you have a basic Cart up and running, it's time to show you how you would -use the cart to get all the calculated totals and tax. - -We've also tried to make Carts extendable as much as possible so, depending on -what your stores requirements are, you are free to chop and change things as -much as you need to. - -## Hydrating the cart totals - -```php -$cart->calculate(); -``` - -This will create a "hydrated" version of your cart with the following: - -::: tip -All values will return a `Lunar\Datatypes\Price` object. So you have -access to the following: `value`, `formatted`, `decimal` -::: - -```php -$cart->total; // The total price value for the cart -$cart->subTotal; // The cart sub total, excluding tax -$cart->subTotalDiscounted; // The cart sub total, minus the discount amount. -$cart->shippingTotal; // The monetary value for the shipping total. (if applicable) -$cart->taxTotal; // The monetary value for the amount of tax applied. -$cart->taxBreakdown; // This is a collection of all taxes applied across all lines. -$cart->discountTotal; // The monetary value for the discount total. -$cart->discountBreakdown; // This is a collection of how discounts were calculated -$cart->shippingSubTotal; // The shipping total, excluding tax. -$cart->shippingTotal; // The shipping total including tax. -$cart->shippingBreakdown; // This is a collection of the shipping breakdown for the cart. - -foreach ($cart->taxBreakdown as $taxRate) { - $taxRate->name - $taxRate->total->value -} - -foreach ($cart->shippingBreakdown->items as $shippingBreakdown) { - $shippingBreakdown->name; - $shippingBreakdown->identifier; - $shippingBreakdown->price->formatted(); -} - - -foreach ($cart->discountBreakdown as $discountBreakdown) { - $discountBreakdown->discount_id - foreach ($discountBreakdown->lines as $discountLine) { - $discountLine->quantity - $discountLine->line - } - $discountBreakdown->total->value -} - -foreach ($cart->lines as $cartLine) { - $cartLine->unitPrice; // The monetary value for a single item. - $cartLine->unitPriceInclTax; // The monetary value for a single item, including tax amount. - $cartLine->total; // The total price value for the cart - $cartLine->subTotal; // The sub total, excluding tax - $cartLine->subTotalDiscounted; // The sub total, minus the discount amount. - $cartLine->taxAmount; // The monetary value for the amount of tax applied. - $cartLine->taxBreakdown; // This is a collection of all taxes applied across all lines. - $cartLine->discountTotal; // The monetary value for the discount total. -} - -``` - -## Modifying Carts - -If you need to programmatically change the Cart values, e.g. custom discounts or -prices, you will want to extend the Cart. - -You can find out more in the Extending Lunar section for -[Cart Extending](/core/extending/carts). - -## Calculating Tax - -During the cart's lifetime, it's unlikely you will have access to any address -information, which can be a pain when you want to accurately display the amount -of tax applied to the basket. Moreover, some countries don't even show tax until -they reach the checkout. We've tried to make this as easy and extendable as -possible for you as the developer to build your store. - -When you calculate the cart totals, you will be able to set the billing and/or -shipping address on the cart, which will then be used when we calculate which -tax breakdowns should be applied. - -```php -$shippingAddress = [ - 'country_id' => null, - 'title' => null, - 'first_name' => null, - 'last_name' => null, - 'company_name' => null, - 'tax_identifier' => null, - 'line_one' => null, - 'line_two' => null, - 'line_three' => null, - 'city' => null, - 'state' => null, - 'postcode' => 'H0H 0H0', - 'delivery_instructions' => null, - 'contact_email' => null, - 'contact_phone' => null, - 'meta' => null, -]; - -$billingAddress = /** .. */; - -$cart->setShippingAddress($shippingAddress); -$cart->setBillingAddress($billingAddress); -``` - -You can also pass through a `\Lunar\Models\Address` model, or even another -`\Lunar\Models\CartAddress` - -```php -$shippingAddress = \Lunar\Models\Address::first(); - -$cart->setShippingAddress($shippingAddress); - -$cart->setBillingAddress( - $cart->shippingAddress -); -``` - -## Cart Session Manager - -::: tip -The cart session manager is useful if you're building a traditional -Laravel storefront which makes use of sessions. -::: - -When building a store, you're going to want an easy way to fetch the cart for -the current user (or guest user) by retrieving it from their current session. -Lunar provides an easy to use class to make this easier for you, so you don't -have to keep reinventing the wheel. - -### Available config - -Configuration for your cart is handled in `lunar/cart.php` - -| Field | Description | Default | -|:--------------|:---------------------------------------------------------------------------------------|:-------------| -| `auth_policy` | When a user logs in, how should we handle merging of the basket? | `merge` | -| `eager_load` | Which relationships should be eager loaded by default when calculating the cart totals | - -There is additional, separate, config specifically for when using the `CartSession` located in `lunar/cart_session.php`. - -| Field | Description | Default | -|:---------------------------------|:-------------------------------------------------------------------|:-------------| -| `session_key` | What key to use when storing the cart id in the session | `lunar_cart` | -| `auto_create` | If no current basket exists, should we create one in the database? | `false` | -| `allow_multiple_orders_per_cart` | Whether carts can have multiple orders associated to them. | `false` | - -### Getting the cart session instance - -You can either use the facade or inject the `CartSession` into your code. - -```php -$cart = \Lunar\Facades\CartSession::current(); - -public function __construct( - protected \Lunar\Base\CartSessionInterface $cartSession -) { - // ... -} -``` - -### Fetching the current cart - -```php -$cart = \Lunar\Facades\CartSession::current(); -``` - -When you call current, you have two options, you either return `null` if they -don't have a cart, or you want to create one straight away. By default, we do -not create them initially as this could lead to a ton of cart models being -created for no good reason. If you want to enable this functionality, you can -adjust the config in `lunar/cart_session.php` - -### Forgetting the cart -Forgetting the cart will remove it from the user session and also soft-delete -the cart in the database. - -```php -CartSession::forget(); -``` - -If you don't want to delete the cart, you can pass the following parameter. - -```php -CartSession::forget(delete: false); -``` - - -### Using a specific cart - -You may want to manually specify which cart should be used for the session. - -```php -$cart = \Lunar\Models\Cart::first(); -CartSessionManager::use($cart); -``` - -The other available methods are as follows: - -### Add a cart line - -```php -CartSession::add($purchasable, $quantity); -``` - -### Add multiple lines - -```php -CartSession::addLines([ - [ - 'purchasable' => \Lunar\Models\ProductVariant::find(123), - 'quantity' => 25, - 'meta' => ['foo' => 'bar'], - ], - // ... -]); -``` - -_Accepts a `collection` or an `array`_ - -### Update a single line - -```php -CartSession::updateLine($cartLineId, $quantity, $meta); -``` - -### Update multiple lines - -```php -CartSession::updateLines(collect([ - [ - 'id' => 1, - 'quantity' => 25, - 'meta' => ['foo' => 'bar'], - ], - // ... -])); -``` - -### Remove a line - -```php -CartSession::remove($cartLineId); -``` - -### Clear a cart - -This will remove all lines from the cart. - -```php -CartSession::clear(); -``` - -### Associating a cart to a user - -You can easily associate a cart to a user. - -```php -CartSession::associate($user, 'merge'); -``` - -### Associating a cart to a customer - -You can easily associate a cart to a customer. - -```php -CartSession::setCustomer($customer); -``` - - -## Adding shipping/billing address - -As outlined above, you can add shipping / billing addresses to the cart using -the following methods: - -```php -$cart->setShippingAddress([ - 'first_name' => null, - 'last_name' => null, - 'line_one' => null, - 'line_two' => null, - 'line_three' => null, - 'city' => null, - 'state' => null, - 'postcode' => null, - 'country_id' => null, -]); -$cart->setBillingAddress([ - 'first_name' => null, - 'last_name' => null, - 'line_one' => null, - 'line_two' => null, - 'line_three' => null, - 'city' => null, - 'state' => null, - 'postcode' => null, - 'country_id' => null, -]); -``` - -You can easily retrieve these addresses by accessing the appropriate property: - -```php -$cart->shippingAddress; -$cart->billingAddress; -``` - -### ShippingOption override - -In some cases you might want to present an estimated shipping cost without users having to fill out a full shipping address, this is where the `ShippingOptionOverride` comes in, if set on the cart it can be used to calculate shipping for a single request. - -```php -$shippingOption = $cart->getEstimatedShipping([ - 'postcode' => '123456', - 'state' => 'Essex', - 'country' => Country::first(), -]); -```` - -This will return an estimated (cheapest) shipping option for the cart, based on it's current totals. By default this will not be taken into account when calculating shipping in the cart pipelines, in order to enable that we need to pass an extra parameter. - -```php -$shippingOption = $cart->getEstimatedShipping([ - 'postcode' => '123456', - 'state' => 'Essex', - 'country' => Country::first(), -], setOverride: true); -```` - -Now when the pipelines are run, the option which was returned by `getEstimatedShipping` will be used when calculating shipping totals, bypassing any other logic, note this will only happen for that one request. - -If you are using the `CartSession` manager, you can easily set the parameters you want to estimate shipping so you don't need to pass them each time: - -```php -CartSession::estimateShippingUsing([ - 'postcode' => '123456', - 'state' => 'Essex', - 'country' => Country::first(), -]); -``` - -You can also manually set the shipping method override directly on the cart. - -```php -$cart->shippingOptionOverride = new \Lunar\DataTypes\ShippingOption(/* .. */); -``` - -Calling `CartSession::current()` by itself won't trigger the shipping override, but you can pass the `estimateShipping` parameter to enable it: - -```php -// Will not use the shipping override, default behaviour. -CartSession::current(); - -// Will use the shipping override, based on what is set using `estimateShippingUsing` -CartSession::current(estimateShipping: true); -``` - -## Handling User Login - -When a user logs in, you will likely want to check if they have a cart -associated to their account and use that, or if they have started a cart as a -guest and logged in, you will likely want to be able to handle this. Lunar takes -the pain out of this by listening to the authentication events and responding -automatically by associating any previous guest cart they may have had and, -depending on your `auth_policy` merge or override the basket on their account. - -## Determining cart changes - -Carts by nature are dynamic, which means anything can change at any moment. This means it can be quite challenging to -determine whether a cart has changed from the one currently loaded, for example, if the user goes to check out and -changes their cart on another tab, how does the checkout know there has been a change? - -To help this, a cart will have a fingerprint generated which you can check to determine whether there has been -any changes and if so, refresh the cart. - -```php -$cart->fingerprint(); - -try { - $cart->checkFingerprint('4dfAW33awd'); -} catch (\Lunar\Exceptions\FingerprintMismatchException $e) { - //... Refresh the cart. -} -``` - -### Changing the underlying class. - -The class which generates the fingerprint is referenced in `config/lunar/cart.php`. - -```php -return [ - // ... - 'fingerprint_generator' => Lunar\Actions\Carts\GenerateFingerprint::class, -]; -``` - -In most cases you won't need to change this. - -## Pruning cart data - -Over time you will experience a build up of carts in your database that you may want to regularly remove. - -You can enable automatic removal of these carts using the `lunar.carts.prune_tables.enabled` config. By setting this to `true` any carts without an order associated will be removed after 90 days. - -You can change the number of days carts are retained for using the `lunar.carts.prune_tables. prune_interval` config. - -If you have specific needs around pruning you can also change the `lunar.carts.prune_tables.pipelines` array to determine what carts should be removed. - - - -```php -return [ - // ... - 'prune_tables' => [ - - 'enabled' => false, - - 'pipelines' => [ - Lunar\Pipelines\CartPrune\PruneAfter::class, - Lunar\Pipelines\CartPrune\WithoutOrders::class, - ], - - 'prune_interval' => 90, // days - - ], -]; -``` diff --git a/docs/core/reference/channels.md b/docs/core/reference/channels.md deleted file mode 100644 index 827b58d8cc..0000000000 --- a/docs/core/reference/channels.md +++ /dev/null @@ -1,55 +0,0 @@ -# Channels - -## Overview - -Default `webstore`. - -## Assigning channels to models. - -You can assign Eloquent models to different channels and specify whether they are enabled permanently or whether they should be scheduled to be enabled. - -In order to add this kind of functionality to your model, you need to add the `HasChannels` trait. - -```php -scheduleChannel($channel, now()->addDays(14), now()->addDays(24)); - -// Schedule the product to be enabled straight away -$product->scheduleChannel($channel); - -// The schedule method will accept and array or collection of channels. -$product->scheduleChannel(Channel::get()); -``` - -There is also a channel scope available to models which use this trait: - -```php -// Limit to a single channel -Product::channel($channel)->get(); - -// Limit to multiple channels -Product::channel([$channelA, $channelB])->get(); - -// Limit to a channel available the next day -Product::channel($channelA, now()->addDay())->get(); - -// Limit to a channel within a date range. -Product::channel($channelA, now()->addDay(), now()->addDays(2))->get(); -``` diff --git a/docs/core/reference/collections.md b/docs/core/reference/collections.md deleted file mode 100644 index 940898254a..0000000000 --- a/docs/core/reference/collections.md +++ /dev/null @@ -1,92 +0,0 @@ -# Collections - -## Overview - -Collections, although not strictly the same, are akin to Categories. They serve to allow you to add products, either explicitly or via certain criteria, for use on your store. - -For example, you may have a Collection called "Red T-Shirts" and within that collection specify that any product which has the tag "Red" and "T-Shirt" to be included. - -A collection can also have other collections underneath it, forming a nested set hierarchy. - -A collection must also belong to a collection group, this allows you to have greater flexibility when building out things like Menus and Landing pages. - - -## Collection Groups - -Create a collection group - -```php -$group = \Lunar\Models\CollectionGroup::create([ - 'name' => 'Main Catalogue', - 'handle' => 'main-catalogue' // Will auto generate if omitted. -]); -``` - - -## Collections - -Collections are a hierarchy of models that have products associated to them, you can think of them as "Categories". Once you have added products you can then determine how they are sorted on display. - -### Create a collection - -```php -$collection = \Lunar\Models\Collection::create([ - 'attribute_data' => [ - 'name' => new \Lunar\FieldTypes\Text('Clearance'), - ], - 'collection_group_id' => $group->id, -]); -``` - - -### Add a child collection - -```php -$child = new \Lunar\Models\Collection::create([/*..*/]); - -$collection->appendNode($child); -``` - -This results in the following - -```bash -- Clearance - - Child -``` - -Lunar uses the [Laravel Nested Set](https://github.com/lazychaser/laravel-nestedset) package, so feel free to take a look at it to see what's possible. - -### Adding products - -Products are related using a `BelongsToMany` relationship with a pivot column for `position`. - -```php -$products = [ - 1 => [ - 'position' => 1, - ], - 2 => [ - 'position' => 2, - ] -]; - -$collection->products()->sync($products); -``` - -::: tip -The key in the `$products` array is the product id -::: - -### Sorting products - -Lunar comes with a handful of criteria out the box for sorting products in a collection: - -|Name|Description| -|:-|:-| -|`min_price:asc`|Sorts using the base price ascending| -|`min_price:desc`|Sorts using the base price descending| -|`sku:asc`|Sorts using the sku ascending| -|`sku:desc`|Sorts using the sku descending| -|`custom`|This will allow you to specify the order of each product manually| - -Depending on what you have as the sort time on the collection, Lunar will automatically sort the products for you when you update the products. diff --git a/docs/core/reference/currencies.md b/docs/core/reference/currencies.md deleted file mode 100644 index 0100c3aaf5..0000000000 --- a/docs/core/reference/currencies.md +++ /dev/null @@ -1,47 +0,0 @@ -# Currencies - -## Overview - -Currencies allow you to charge different amounts relative to the currency you're targeting. - -## Creating a currency - -```php -\Lunar\Models\Currency::create([ - 'code' => 'GBP', - 'name' => 'British Pound', - 'exchange_rate' => 1.0000, - 'decimal_places' => 2, - 'enabled' => 1, - 'default' => 1, -]); -``` - -|Field|Description| -|:-|:-| -|`code`|This should be the `ISO 4217` currency code. | -|`name`|A given name for the currency.| -|`exchange_rate`|This should be the exchange rate relative to the default currency (see below)| -|`decimal_places`|Specify the decimal places, e.g. 2| -|`enabled`|Whether the currency is enabled| -|`default`|Whether the currency is the default| - -## Exchange rates -These are relative to the default currency. For example assuming we have the following: - -```php -\Lunar\Models\Currency::create([ - 'code' => 'GBP', - 'name' => 'British Pound', - 'exchange_rate' => 1.0000, - 'decimal_places' => 2, - 'enabled' => 1, - 'default' => 1, -]); -``` - -We set the model to be the default and we set the exchange rate to be `1` as we're defining this as our base (default) currency. - -Now, say we wanted to add EUR (Euros). Currently the exchange rate from GBP to EUR is `1.17`. But we want this to be relative to our default record. So 1 / 1.17 = 0.8547. - -It's entirely up to you what you want to set the exchange rates as, it is also worth mentioning that this is independent of product pricing in the sense that you can specify the price per currency. The exchange rate serves as a helper when working with prices so you have something to go by. diff --git a/docs/core/reference/customers.md b/docs/core/reference/customers.md deleted file mode 100644 index 97885f9e60..0000000000 --- a/docs/core/reference/customers.md +++ /dev/null @@ -1,271 +0,0 @@ -# Customers - -## Overview - -We use Customers in Lunar to store the customer details, rather than Users. We do this for a few reasons. One, so that we leave your User models well alone and two, because it provides flexibility. - -## Customers - -```php -Lunar\Models\Customer -``` - -|Field|Description| -|:-|:-| -|`id`|| -|`title`|Mr, Mrs, Miss, etc| -|`first_name`|| -|`last_name`|| -|`company_name`|nullable| -|`tax_identifier`|nullable| -|`account_ref`|nullable| -|`attribute_data`|JSON| -|`meta`|JSON| -|`created_at`|| -|`updated_at`|| - -### Creating a customer - -```php -Lunar\Models\Customer::create([ - 'title' => 'Mr.', - 'first_name' => 'Tony', - 'last_name' => 'Stark', - 'company_name' => 'Stark Enterprises', - 'tax_identifier' => null, - 'meta' => [ - 'account_no' => 'TNYSTRK1234' - ], -]) -``` - -### Relationships - -- Customer Groups `customer_customer_group` -- Users - `customer_user` - -## Users - -Customers will typically be associated with a user, so they can place orders. But it is also possible to have multiple users associated with a customer. This can be useful on B2B e-commerce where a customer may have multiple buyers. - -### Attaching users to a customer - -```php -$customer = \Lunar\Models\Customer::create([/* ... */]); - -$customer->users()->attach($user); - -$customer->users()->sync([1,2,3]); -``` - -## Attaching a customer to a customer group - -```php -$customer = \Lunar\Models\Customer::create([/* ... */]); - -$customer->customerGroups()->attach($customerGroup); - -$customer->customerGroups()->sync([4,5,6]); -``` - -## Impersonating users - -When a customer needs help with their account, it's useful to be able to log in as that user so you can help diagnose the issue they're having. -Lunar allows you to specify your own method of how you want to impersonate users, usually this is in the form of a signed URL an admin can go to in order to log in as the user. - -### Creating the impersonate class - -```php -addMinutes(5), [ - 'user' => $authenticatable->getAuthIdentifier(), - ]); - } -} -``` - -Then you need to register this in `config/lunar-hub/customers.php`. - -```php -return [ - 'impersonate' => App\Auth\Impersonate::class, - // ... -]; -``` - -Once added you will see an option to impersonate the user when viewing a customer. This will then go to the URL specified in your class where you will be able to handle the impersonation logic. - -## Customer Groups - -Default `retail` - -Customer groups allow you to group your customers into logical segments which enables you to define different criteria on models based on what customer belongs to that group. - -These criteria include things like: - -### Pricing - -Specify different pricing per customer group, for example you may have certain prices for customers that are in the `trade` customer group. - -### Product Availability - -You can turn product visibility off depending on the customer group, this would mean only certain products would show depending on the group they belong to. This will also include scheduling availability so you can release products earlier or later to different groups. - ---- -You must have at least one customer group in your store and when you install Lunar you will be given a default one to get you started named `retail`. - -## Creating a customer group - -```php -$customerGroup = Lunar\Models\CustomerGroup::create([ - 'name' => 'Retail', - 'handle' => 'retail', // Must be unique - 'default' => false, -]); -``` - -::: tip -You can only have one default at a time, if you create a customer group and pass default to true, then the existing default will be set to `false`. -::: - -## Scheduling availability - -If you would like to add customer group availability to your own models, you can use the `HasCustomerGroups` trait. - -```php - -// ... -use Lunar\Base\Traits\HasCustomerGroups; - -class MyModel extends Model -{ - use HasCustomerGroups; -} -``` - -You will need to define the relationship for customer groups so that Lunar knows how to handle it. - -```php -public function customerGroups() -{ - return $this->hasMany(\Lunar\Models\CustomerGroup::class)->withTimestamps()->withPivot([/* .. */]); -} -``` -You will then have access to the following methods: - -### Scheduling customer groups - -```php -// Will schedule for this product to be enabled in 14 days for this customer group. -$myModel->scheduleCustomerGroup( - $customerGroup, - $startDate, - $endData, - $pivotData -); - -// Schedule the product to be enabled straight away -$myModel->scheduleCustomerGroup($customerGroup); - -// The schedule method will accept an array or collection of customer groups. -$myModel->scheduleCustomerGroup(CustomerGroup::get()); -``` - -### Unscheduling customer groups - -If you do not want a model to be enabled for a customer group, you can unschedule it. This will keep any previous `start` and `end` dates but will toggle the `enabled` column. - -```php -$myModel->unscheduleCustomerGroup($customerGroup, $pivotData); -``` - -### Parameters - -|Field|Description|Type|Default| -|:-|:-|:-|:-| -|`customerGroup`|A collection of CustomerGroup models or id's.|mixed| -|`startDate`|The date the customer group will be active from.|`DateTime`| -|`endDate`|The date the customer group will be active until.|`DateTime`| -|`pivotData`|Any additional pivot data you may have on your link table. (not including scheduling defaults)|`array`| - - -**Pivot Data** - -By default the following values are used for `$pivotData` - -- `enabled` - Whether the customer group is enabled, defaults to `true` when scheduling and `false` when unscheduling. - -You can override any of these yourself as they are merged behind the scenes. - -### Retrieving the relationship - -The `HasCustomerGroup` trait adds a `customerGroup` scope to the model. This lets you query based on availability for a specific or multiple customer groups. - -The scope will accept either a single ID or instance of `CustomerGroup` and will accept an array. - -```php -$results = MyModel::customerGroup(1, $startDate, $endDate)->paginate(); - -$results = MyModel::customerGroup([ - $groupA, - $groupB, -])->paginate(50); -``` - -The start and end dates should be `DateTime` objects with will query for the existence of a customer group association with the start and end dates between those given. These are optional and the following happens in certain situations: - -**Pass neither `startDate` or `endDate`** - -Will query for customer groups which are enabled and the `startDate` is after `now()` - -**Pass only `startDate`** - -Will query for customer groups which are enabled, the start date is after the given date and the end date is either null or before `now()` - -**Pass both `startDate` and `endDate`** - -Will query for customer groups which are enabled, the start date is after the given date and the end date is before the given date. - -**Pass `endDate` without `startDate`** - -Will query for customer groups which are enabled, the start date is after the `now()` and the end date is before the given date. - -If you omit the second parameter the scope will take the current date and time. - -::: tip -A model will only be returned if the `enabled` column is positive, regardless of whether the start and end dates match. -::: - -### Limit by customer group - -Eloquent models which use the `HasCustomerGroups` trait have a useful scope available: - -```php -// Limit products available to a single customer group -Product::customerGroup($customerGroup)->get(); - -// Limit products available to multiple customer groups -Product::customerGroup([$groupA, $groupB])->get(); - -// Limit to products which are available the next day -Product::customerGroup($groupA, now()->addDay())->get(); - -// Limit to products which are available within a date range. -Product::customerGroup($groupA, now()->addDay(), now()->addDays(2))->get(); -``` diff --git a/docs/core/reference/discounts.md b/docs/core/reference/discounts.md deleted file mode 100644 index ab2756891f..0000000000 --- a/docs/core/reference/discounts.md +++ /dev/null @@ -1,143 +0,0 @@ -# Discounts - -## Overview - -// ... - -## Discounts - -```php -Lunar\Models\Discount -``` - -| Field | Description | Example | -|:-------------|:-------------------------------------------------------------|:--------------------------------------| -| `id` | | | -| `name` | The given name for the discount | | -| `handle` | The unique handle for the discount | | -| `type` | The type of discount | `Lunar\DiscountTypes\BuyXGetY` | -| `data` | JSON | Any data to be used by the type class | -| `starts_at` | The datetime the discount starts (required) | | -| `ends_at` | The datetime the discount expires, if `NULL` it won't expire | | -| `uses` | How many uses the discount has had | | -| `max_uses` | The maximum times this discount can be applied storewide | | -| `priority` | The order of priority | | -| `stop` | Whether this discount will stop others after propagating | | -| `created_at` | | | -| `updated_at` | | | - -### Creating a discount - -```php -Lunar\Models\Discount::create([ - 'name' => '20% Coupon', - 'handle' => '20_coupon', - 'type' => 'Lunar\DiscountTypes\Coupon', - 'data' => [ - 'coupon' => '20OFF', - 'min_prices' => [ - 'USD' => 2000 // $20 - ], - ], - 'starts_at' => '2022-06-17 13:30:55', - 'ends_at' => null, - 'max_uses' => null, -]) -``` - -### Fetching a discount - -The following scopes are available: - -```php -/** -* Query for discounts using the `start_at` and `end_at` dates. - */ -Discount::active()->get(); - -/** -* Query for discounts where the `uses` column is less than the `max_uses` column or `max_uses` is null. - */ -Discount::usable()->get(); - -/** -* Query for discounts where the associated products are of a certain type, based on given product ids. - */ -Discount::products($productIds, $type = 'condition'); -``` - -### Resetting the discount cache - -For performance reasons the applicable discounts are cached per request. If you need to reset this cache (for example after adding a discount code) you should call `resetDiscounts()`: - -```php -Discount::resetDiscounts(); -``` - -## Discountables - -You can relate a purchasable, collection or variant to a discount via this model. Each has a type for whether it's a `condition` or `reward`. - -- `condition` - If your discount requires these models to be in the cart to activate -- `reward` - Once the conditions are met, discount one of more of these models. - -```php -Lunar\Models\Discountable -``` - -| Field | Description | Example | -|:--------------------|:------------------------|:------------------------------| -| `id` | | | -| `discount_id` | | | -| `discountable_type` | | `product_variant` | -| `discountable_id` | | | -| `type` | `condition` or `reward` | | -| `created_at` | | | -| `updated_at` | | | - -### Relationships - -- Discountable `discountables` -- Users - `customer_user` - -### Adding your own Discount type - -```php -addMedia($request->file('image'))->toMediaCollection('images'); -``` - -For more information on what's available, see [Associating files](https://spatie.be/docs/laravel-medialibrary/v9/basic-usage/associating-files) - -## Fetching images - -```php -$product = \Lunar\Models\Product::find(123); - -$product->getMedia('images'); -``` -For more information on what's available, see [Retrieving media](https://spatie.be/docs/laravel-medialibrary/v9/basic-usage/retrieving-media) - -## Fallback images -If your model does not contain any images, calling getFirstMediaUrl or getFirstMediaPath will return null. You can -provide a fallback path/url in the config `lunar/media` or `.env`. -```php -'fallback' => [ - 'url' => env('FALLBACK_IMAGE_URL', null), - 'path' => env('FALLBACK_IMAGE_PATH', null) -] -``` - -## Media Collections & Conversions - -Lunar provides a way to customise the media collections and conversions for each model that implements the `HasMedia` -trait. - -You will find the default settings in the config `lunar/media`. Here you can switch out the class that handles the -registration of the media definitions. - -### Custom Media Definitions - -To create custom media definitions you will want to create your own implementation along the lines of the code below. - -When registering media definitions you can define not only the name but many other options. -See [Spatie Media Library](https://spatie.be/docs/laravel-medialibrary/v10/working-with-media-collections/defining-media-collections) for more information. - -```php -use Lunar\Base\MediaDefinitionsInterface; -use Spatie\Image\Enums\Fit; -use Spatie\MediaLibrary\HasMedia; -use Spatie\MediaLibrary\MediaCollections\MediaCollection; -use Spatie\MediaLibrary\MediaCollections\Models\Media; - -class CustomMediaDefinitions implements MediaDefinitionsInterface -{ - public function registerMediaConversions(HasMedia $model, Media $media = null): void - { - // Add a conversion for the admin panel to use - $model->addMediaConversion('small') - ->fit(Fit::Fill, 300, 300) - ->sharpen(10) - ->keepOriginalImageFormat(); - } - - public function registerMediaCollections(HasMedia $model): void - { - $fallbackUrl = config('lunar.media.fallback.url'); - $fallbackPath = config('lunar.media.fallback.path'); - - // Reset to avoid duplication - $model->mediaCollections = []; - - $collection = $model->addMediaCollection('images'); - - if ($fallbackUrl) { - $collection = $collection->useFallbackUrl($fallbackUrl); - } - - if ($fallbackPath) { - $collection = $collection->useFallbackPath($fallbackPath); - } - - $this->registerCollectionConversions($collection, $model); - } - - protected function registerCollectionConversions(MediaCollection $collection, HasMedia $model): void - { - $conversions = [ - 'zoom' => [ - 'width' => 500, - 'height' => 500, - ], - 'large' => [ - 'width' => 800, - 'height' => 800, - ], - 'medium' => [ - 'width' => 500, - 'height' => 500, - ], - ]; - - $collection->registerMediaConversions(function (Media $media) use ($model, $conversions) { - foreach ($conversions as $key => $conversion) { - $model->addMediaConversion($key) - ->fit( - Fit::Fill, - $conversion['width'], - $conversion['height'] - )->keepOriginalImageFormat(); - } - }); - } - - public function getMediaCollectionTitles(): array - { - return [ - 'images' => 'Images', - ]; - } - - public function getMediaCollectionDescriptions(): array - { - return [ - 'images' => '', - ]; - } -} -``` -Then register your new class against the model(s) you wish to use it. - -```php -return [ - - 'definitions' => [ - Lunar\Models\Product::class => CustomMediaDefinitions::class, - //.. - ], - - //.. -``` - -#### Generate Media Conversions - -To regenerate conversions, e.g. if you have changed the configuration, you can run the following command. - -```sh -php artisan media-library:regenerate -``` - -This will create queue jobs for each media entry to be re-processed. More information can be found on the -[medialibrary website](https://spatie.be/docs/laravel-medialibrary/v9/converting-images/regenerating-images) - - -## Extending - -You can extend your own models to use media, either by using our implementation or by implementing medialibrary -directly. It's totally up to you and your requirements. If you want to use medialibrary directly, -[just follow their guides](https://spatie.be/docs/laravel-medialibrary/v9/basic-usage/preparing-your-model) and you'll be all set. - -::: warning -If you decide to use medialibrary directly, you will not have access to our transformations or any other Lunar features -we add. -::: - -### Extending with Lunar - -To enable image transformations on your models within Lunar, simply add the `HasMedia` trait. - -```php -createOrder( - allowMultipleOrders: false, - orderIdToUpdate: null, -); -``` - -- `allowMultipleOrders` - Generally carts will only have one draft order associated, however if you want to allow carts to - have multiple, you can pass `true` here. -- `orderIdToUpdate` - You can optionally pass the ID of an order to update instead of attempting to create a new order, this must be a draft order i.e. a null `placed_at` and related to the cart. - -The underlying class for creating an order is `Lunar\Actions\Carts\CreateOrder`, you are free to override this in the -config file `config/cart.php` - -```php -return [ - // ... - 'actions' => [ - // ... - 'order_create' => CustomCreateOrder::class - ] -] -``` - -At minimum your class should look like the following: - -```php -final class CreateOrder extends Lunar\Actions\AbstractAction -{ - /** - * Execute the action. - */ - public function execute( - Cart $cart, - bool $allowMultipleOrders = false, - int $orderIdToUpdate = null - ): self { - return $this; - } -} -``` - -### Validating a cart before creation. - -If you also want to check before you attempt this if the cart is ready to create an order, you can call the helper -method: - -```php -$cart->canCreateOrder(); -``` - -Under the hood this will use the `ValidateCartForOrderCreation` class which lunar provides and throw any validation -exceptions with helpful messages if the cart isn't ready to create an order. - -You can specify your own class to handle this in `config/cart.php`. - -```php -return [ - // ... - 'validators' => [ - 'order_create' => MyCustomValidator::class, - ] -] -``` - -Which may look something like: - -```php -parameters['cart']; - - if ($somethingWentWrong) { - return $this->fail('There was an issue'); - } - - return $this->pass(); - } -} -``` - -## Order Reference Generation - -By default Lunar will generate a new order reference for you when you create an order from a cart. The format for this -is: - -``` -{prefix?}{0..0}{orderId} -``` - -`{0..0}` indicates the order id will be padded until the length is 8 digits (not including the prefix) -The prefix is optional and defined in the `lunar/orders.php` config file, which gives access to a number of settings you can change. - -### Custom Generators - -If your store has a specific requirement for how references are generated, you can easily swap out the Lunar one for -your own: - -`config/lunar/orders.php` - -```php -return [ - 'reference_generator' => App\Generators\MyCustomGenerator::class, -]; -``` - -Or, if you don't want references at all (not recommended) you can simply set it to `null` - -Here's the underlying class for the custom generator: - -```php -namespace App\Generators; - -use Lunar\Models\Order; - -class MyCustomGenerator implements OrderReferenceGeneratorInterface -{ - /** - * {@inheritDoc} - */ - public function generate(Order $order): string - { - // ... - return 'my-custom-reference'; - } -} -``` - -## Modifying Orders - -If you need to programmatically change the Order values or add in new behaviour, you will want to extend the Order -system. - -You can find out more in the Extending Lunar section for [Order Modifiers](/core/extending/orders). - -## Order Lines - -```php -Lunar\Models\OrderLine -``` - -| Field | Description | -|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| -| id | | -| order_id | | -| purchasable_type | Morph reference for the purchasable item e.g. `product_variant` | -| purchasable_id | -| type | Whether `digital`,`physical` etc -| description | A description of the line item -| option | If this was a variant, the option info is here -| identifier | Something to identify the purchasable item, usually an `sku` -| unit_price | The unit price of the line -| unit_quantity | The line unit quantity, usually this is 1 -| quantity | The amount of this item purchased -| sub_total | The sub total minus any discounts, excl. tax -| discount_total | Any discount amount excl. tax -| tax_breakdown | A json field for the tax breakdown e.g. `[{"description": "VAT", "identifier" : "vat", "value": 123, "percentage": 20, "currency_code": "GBP"}]` -| tax_total | The total amount of tax applied -| total | The grand total with tax -| notes | Any additional order notes -| meta | Any additional meta info you wish to store -| created_at | | -| updated_at | | - - -### Create an order line - -::: tip -If you are using the `createOrder` method on a cart, this is all handled for you automatically. -::: - -```php -\Lunar\Models\OrderLine::create([ - // ... -]); -``` - -Or via the relationship - -```php -$order->lines()->create([ - // ... -]); -``` - -## Order Addresses - -An order can have many addresses, typically you would just have one for billing and one for shipping. - -::: tip -If you are using the `createOrder` method on a cart, this is all handled for you automatically. -::: - -```php -\Lunar\Models\OrderAddress::create([ - 'order_id' => 1, - 'country_id' => 1, - 'title' => null, - 'first_name' => 'Jacob', - 'last_name' => null, - 'company_name' => null, - 'tax_identifier' => null, - 'line_one' => '123 Foo Street', - 'line_two' => null, - 'line_three' => null, - 'city' => 'London', - 'state' => null, - 'postcode' => 'NW1 1WN', - 'delivery_instructions' => null, - 'contact_email' => null, - 'contact_phone' => null, - 'type' => 'shipping', // billing/shipping - 'shipping_option' => null, // A unique code for you to identify shipping -]); - -// Or via the relationship. -$order->addresses()->create([ - // ... -]); -``` - -You can then use some relationship helpers to fetch the address you need: - -```php -$order->shippingAddress; -$order->billingAddress; -``` - -## Shipping Options - -::: tip -A Shipping Tables addon is planned to make setting up shipping in the admin hub easy for most scenarios. -::: - -To add Shipping Options you will need to [extend Lunar](/core/extending/shipping) to add in your own logic. - -Then in your checkout, or where ever you want, you can fetch these options: - -```php -\Lunar\Facades\ShippingManifest::getOptions(\Lunar\Models\Cart $cart); -``` - -This will return a collection of `Lunar\DataTypes\ShippingOption` objects. - -### Adding the shipping option to the cart - -Once the user has selected the shipping option they want, you will need to add this to the cart so it can calculate the -new totals. - -```php -$cart->setShippingOption(\Lunar\DataTypes\ShippingOption $option); -``` - -## Transactions - -```php -Lunar\Models\Transaction -``` - -| Field | Description | -|:-----------|:----------------------------------------------------------------------------------------------| -| id | | -| success | Whether the transaction was successful | -| refund | `true` if this was a refund | -| driver | The payment driver used e.g. `stripe` | -| amount | An integer amount | -| reference | The reference returned from the payment Provider. Used to identify the transaction with them. -| status | A string representation of the status, unlinked to Lunar e.g. `settled` | -| notes | Any relevant notes for the transaction -| card_type | e.g. `visa` -| last_four | Last 4 digits of the card -| meta | Any additional meta info you wish to store -| created_at | | -| updated_at | | - -### Create a transaction - -::: tip -Just because an order has a transaction does not mean it has been placed. Lunar determines whether an order is -considered placed when the `placed_at` column has a datetime, regardless if any transactions exist or not. -::: - -Most stores will likely want to store a transaction against the order, this helps determining how much has been paid, -how it was paid and give a clue on the best way to issue a refund if needed. - -```php -\Lunar\Models\Transaction::create([ - //... -]); - -// Or via the order -$order->transactions()->create([ - //.. -]); -``` - -These can then be returned via the relationship. - -```php -$order->transactions; // Get all transactions. - -$order->charges; // Get all transactions that are charges. - -$order->refunds; // Get all transactions that are refunds. -``` - -## Payments - -We will be looking to add support for the most popular payment providers, so keep an eye out here as we will list them -all out. - -In the meantime, you can absolutely still get a storefront working, at the end of the day Lunar doesn't really mind what payment provider you use or plan to use. - -In terms of an order, all it's worried about is whether or not the `placed_at` column is populated on the orders table, -the rest is completely up to you how you want to handle that. We have some helper utilities to make such things easier -for you as laid out above. - -And as always, if you have any questions you can reach out on our Discord! - -## Order Status - -The `placed_at` field determines whether an Order is considered draft or placed. The Order model has two helper methods -to determine the status of an Order. - -```php -$order->isDraft(); -$order->isPlaced(); -``` - -## Order Notifications - -Lunar allows you to specify what Laravel mailers/notifications should be available for sending when you update an -order's status. These are configured in the `lunar/orders` config file and are defined like so: - -```php -'statuses' => [ - 'awaiting-payment' => [ - 'label' => 'Awaiting Payment', - 'color' => '#848a8c', - 'mailers' => [ - App\Mail\MyMailer::class, - App\Mail\MyOtherMailer::class, - ], - 'notifications' => [], - ], - // ... -], -``` - -Now when you update an order's status in the hub, you will have these mailers available if the new status -is `awaiting-payment`. You can then choose the email addresses which the email should be sent to and also add an -additional email address if required. - -Once updated, Lunar will keep a render of the email sent out in the activity log so you have a clear history of what's -been sent out. - -:::tip -These email notifications do not get sent out automatically if you update the status outside of the hub. -::: - -### Mailer template - -When building out the template for your mailer, you should assume you have access to the `$order` model. When the status -is updated this is passed through to the view data for the mailer, along with any additional content entered. -Since you may not always have additional content when sending out the mailer, you should check the existence first. - -Here's an example of what the template could look like: - -```blade -

It's on the way!

- -

Your order with reference {{ $order->reference }} has been dispatched!

- -

{{ $order->total->formatted() }}

- -@if($content ?? null) -

Additional notes

-

{{ $content }}

-@endif - -@foreach($order->lines as $line) - -@endforeach -``` - -## Order Invoice PDF - -By default when you click "Download PDF" in the admin panel when viewing an order, you will get a basic PDF generated -for you to download. You can publish the view that powers this to create your own PDF template. - -```bash -php artisan vendor:publish --tag=lunarpanel.pdf -``` - -This will create a view called `resources/vendor/lunarpanel/pdf/order.blade.php`, where you will be able to freely -customise the PDF you want displayed on download. diff --git a/docs/core/reference/payments.md b/docs/core/reference/payments.md deleted file mode 100644 index 5141d98fcf..0000000000 --- a/docs/core/reference/payments.md +++ /dev/null @@ -1,356 +0,0 @@ -# Payments - -:::tip -If you're looking for a guide on how to create your own Payment Driver, or for a more in-depth look at how they work, -head over to the [extending section](/core/extending/payments). -::: - -## Overview - -Lunar takes a driver based approach with Payments, meaning you are free to use either add ons to support the provider -you wish to use, or you can create your own to meet your exact needs. - -## Configuration - -All configuration for payments is located in `config/lunar/payments.php`. Here you can specify different types of -payments and the driver each one should use. - -```php - env('PAYMENTS_TYPE', 'offline'), - - 'types' => [ - 'cash-in-hand' => [ - 'driver' => 'offline', - 'released' => 'payment-offline', - ], - 'card' => [ - 'driver' => 'stripe', - 'released' => 'payment-received', - ], - ], -]; -``` - -## Usage - -To use a payment driver, you need to pass the type of payment you wish to use, this will then return an instance of the -driver. - -```php -$driver = \Lunar\Facades\Payments::driver('card'); -``` - -We can then set the cart. - -```php -$driver->cart(\Lunar\Models\Cart $cart); -``` - -Set any additional data that the driver may need. - -```php -$driver->withData([ - 'payment_token' => $token, -]); -``` - -Finally, we can authorize the payment. - -```php -$driver->authorize(); -``` - - diff --git a/docs/core/reference/pricing.md b/docs/core/reference/pricing.md deleted file mode 100644 index db07dfb176..0000000000 --- a/docs/core/reference/pricing.md +++ /dev/null @@ -1,223 +0,0 @@ -# Pricing - -## Overview - -When you display prices on your storefront, you want to be sure the customer is seeing the correct format relative to -the currency they are purchasing in. - -Every storefront is different. We understand as a developer you might want to do this your own way or have very specific -requirements, so we have made price formatting easy to swap out with your own implementation, but also we provide a -suitable default that will suit most use cases. - -## Price formatting - -The class which handles price formatting is referenced in the `config/pricing.php` file: - -```php -return [ - // ... - 'formatter' => \Lunar\Pricing\DefaultPriceFormatter::class, -]; -``` - -When you retrieve a `Lunar\Models\Price` model, you will have access to the `->price` attribute which will return -a `Lunar\DataTypes\Price` object. This is what we will use to get our formatted values. - -The `Lunar\DataTypes\Price` class is not limited to database columns and can be found throughout the Lunar core when -dealing with prices, other examples include: - -### `Lunar\Models\Order` - -- `subTotal` -- `total` -- `taxTotal` -- `discount_total` -- `shipping_total` - -### `Lunar\Models\OrderLine` - -- `unit_price` -- `sub_total` -- `tax_total` -- `discount_total` -- `total` - -### `Lunar\Models\Transaction` - -- `amount` - -### `DefaultPriceFormatter` - -The default price formatter ships with Lunar and will handle most use cases for formatting a price, lets go through -them, first we'll create a standard price model. - -```php -$priceModel = \Lunar\Models\Price::create([ - // ... - 'price' => 1000, // Price is an int and should be in the lowest common denominator - 'min_quantity' => 1, -]); - -// Lunar\DataTypes\Price -$priceDataType = $priceModel->price; -``` - -Return the raw value, as it's stored in the database. - -```php -echo $priceDataType->value; // 1000 -``` - -Return the decimal representation for the price.The decimal value takes into account how many decimal places you have -set for the currency. So in this example if the -decimal places was 3 you would get 10.000 - -```php -echo $priceDataType->decimal(rounding: true); // 10.00 -echo $priceDataType->unitDecimal(rounding: true); // 10.00 -``` - -You may have noticed these two values are the same, so what's happening? Well the unit decimal will take into account -the unit quantity of the purchasable we have the price for. Let's show another example: - -```php -$productVariant = \Lunar\Models\ProductVariant::create([ - // ... - 'unit_quantity' => 10, -]); -``` - -By setting `unit_quantity` to 10 we're telling Lunar that 10 individual units make up this product at this price point, -this is useful if you're selling something that by itself would be under 1 cent i.e. 0.001EUR, which isn't a valid -price. - -```php -$priceModel = $productVariant->prices()->create([ - 'price' => 10, // 0.10 EUR -]); - -// Lunar\DataTypes\Price -$priceDataType = $priceModel->price; -``` - -Now lets try again: - -```php -echo $priceDataType->decimal(rounding: true); // 0.10 -echo $priceDataType->unitDecimal(rounding: true); // 0.01 -``` - -You can see the `unitDecimal` method has taken into account that `10` units make up the price so this gives a unit cost -of `0.01`. - -##### Formatting to a currency string - -The formatted price uses the native PHP [NumberFormatter](https://www.php.net/manual/en/class.numberformatter.php). If -you wish to specify a locale or formatting style you can, see the examples below. - -```php -$priceDataType->price->formatted('fr') // 10,00 £GB -$priceDataType->price->formatted('en-gb', \NumberFormatter::SPELLOUT) // ten point zero zero. -$priceDataType->price->formattedUnit('en-gb') // £10.00 -``` - -## Full reference for `DefaultPriceFormatter` - -```php -$priceDataType->decimal( - rounding: false -); - -$priceDataType->decimalUnit( - rounding: false -); - -$priceDataType->formatted( - locale: null, - formatterStyle: NumberFormatter::CURRENCY, - decimalPlaces: null, - trimTrailingZeros: true -); - -$priceDataType->unitFormatted( - locale: null, - formatterStyle: NumberFormatter::CURRENCY, - decimalPlaces: null, - trimTrailingZeros: true -); -``` - -## Creating a custom formatter - -Your formatter should implement the `PriceFormatterInterface` and have a constructor was accepts and sets -the `$value`, `$currency` and `$unitQty` properties. - -```php -currency) { - $this->currency = Currency::getDefault(); - } - } - - public function decimal(): float - { - // ... - } - - public function unitDecimal(): float - { - // ... - } - - public function formatted(): mixed - { - // ... - } - - public function unitFormatted(): mixed - { - // ... - } -} -``` - -The methods you implement can accept any number of arguments you want to support, you are not bound to what -the `DefaultPriceFormatter` accepts. - -Once you have implemented the required methods, simply swap it out in `config/lunar/pricing.php`: - -```php -return [ - // ... - 'formatter' => \App\Pricing\CustomPriceFormatter::class, -]; -``` - -## Model Casting - -If you have your own models which you want to use price formatting for, Lunar has a cast class you can use. The only -requirement is the column returns an `integer`. - -```php -class MyModel extends Model -{ - protected $casts = [ - //... - 'price' => \Lunar\Base\Casts\Price::class - ]; -} -``` \ No newline at end of file diff --git a/docs/core/reference/products.md b/docs/core/reference/products.md deleted file mode 100644 index 4fc39c7e58..0000000000 --- a/docs/core/reference/products.md +++ /dev/null @@ -1,832 +0,0 @@ -# Products - -## Overview - -Products are generally what you will be selling in your store. You define all your attributes against the product and -products can also have variations. In Lunar a product will always have at least one variation. From a UX point of view, -it will just look like you're editing a single product, but behind the scenes you'll be editing one variant. - -Products also belong to a `ProductType` and aside from the attributes, which you are free to define yourself, will have -a base SKU and a brand name. - -## Creating a product - -```php -Lunar\Models\Product::create([ - 'product_type_id' => $productTypeId, - 'status' => 'published', - 'brand_id' => $brandId, - 'attribute_data' => [ - 'name' => new TranslatedText(collect([ - 'en' => new Text('FooBar'), - ])), - 'description' => new Text('This is a Foobar product.'), - ], -]); -``` - -## Customer Groups - -You can assign customer groups to a product, this allows you to either always have that product enabled for the customer -group, or you can state which dates they should be active for (as long as the customer group is enabled). - -### Attaching customer groups - -```php - -// Will schedule for this product to be enabled in 14 days for this customer group. -$product->scheduleCustomerGroup($customerGroup, now()->addDays(14)); - -// Schedule the product to be enabled straight away -$product->scheduleCustomerGroup($customerGroup); - -// The schedule method will accept an array or collection of customer groups. -$product->scheduleCustomerGroup(CustomerGroup::get()); -``` - -### Retrieving products for a customer group - -If you want to get all products related to a customer group, you can use the `customerGroup` scope. This scope can -either take a single customer group (or customer group id) or a collection/array of ids/customer group models. - -```php -// Can be an array of ids -$products = \Lunar\Models\Product::customerGroup(1)->paginate(50); - -$products = \Lunar\Models\Product::customerGroup([ - $groupA, - $groupB, -])->paginate(50); -``` - -## Product Types - -e.g. Television, T-Shirt, Book, Phone, etc. - -Assigns the appropriate attributes for the product type. - -### Creating a product type - -```php -Lunar\Models\ProductType::create([ - 'name' => 'Boots', -]); -``` - -Product Types also have [Attributes](/core/reference/attributes) associated to them. These associated attributes will -determine what fields are available to products when editing. For example if you had an attribute of `Screen Type` -associated to a `TVs` product type, any products with that product type would have access to that attribute when -editing. - -You can associate attributes to a product type like so (it's just a straight -forward [Polymorphic relationship](https://laravel.com/docs/8.x/eloquent-relationships#many-to-many-polymorphic-relations)). - -```php -$productType->mappedAttributes()->attach([ /* attribute ids ... */ ]); -``` - -You can associate both `Product` and `ProductVariant` attributes to a product type which will then display on either the -product or product variant when editing. - -::: warning -If you decide to delete an attribute, this will cause the association to be dropped and you could lose data. -::: - -### Retrieving the product type relationship - -If you have a product, you can fetch its product type like so: - -```php -$product->productType; - -$product->load(['productType']); -``` - -## Product Identifiers - -You can choose to add product identifiers to each product variant. These are fields which, as the name suggests, allow -you to identify a product and its variants for use in your internal systems. You can choose whether these are required -and unique in the hub whilst editing. - -### Available fields - -**SKU** - -SKU stands for “stock keeping unit” and — as the name suggests — it is a number (usually eight alphanumeric digits) that -you can assign to products to keep track of stock levels internally. If a product has different colors and sizes, each -variation may use a unique SKU number. - -**GTIN** - -A Global Trade Item Number (GTIN) is a unique and internationally recognized identifier for a product. These usually -accompany a barcode and are useful when using services such as Google to help them classify the product. - -**MPN** - -MPN (Manufacturer Part Number) is the product identifier used to differentiate a product among other (similar) products -from the same brand/manufacturer, making it easier for customers to find and buy your products and protecting them from -counterfeit. - -**EAN** - -European Article numbering code (EAN) is a series of letters and numbers in a unique order that helps identify specific -products within your own inventory. - -### Validation - -Depending on your storefront needs, you might not need any of these fields to be required or unique. For this reason you -can change this behaviour at a validation level. - -`config/lunar-hub/products.php` - -```php - 'sku' => [ - 'required' => true, - 'unique' => false, - ], - 'gtin' => [ - 'required' => false, - 'unique' => false, - ], - 'mpn' => [ - 'required' => false, - 'unique' => false, - ], - 'ean' => [ - 'required' => false, - 'unique' => false, - ], -``` - -## Product Options - -These are what you use to define the different options a product has available to it. These are directly related to the -different variants a product might have. Each `ProductOption` will have a set of `ProductOptionValue` models related to -it. For example: - -You could have a `ProductOption` called "Colour" and then multiple `ProductionOptionValue` models for each colour you -would like to offer. - -::: tip -Product options and Product option values are defined at a system level and are translatable. -::: - -### Creating a `ProductOption` - -```php -$option = Lunar\Models\ProductOption::create([ - 'name' => [ - 'en' => 'Colour', - 'fr' => 'Couleur', - ], - 'label' => [ - 'en' => 'Colour', - 'fr' => 'Couleur', - ], -]); -``` - -We can then create values for this option: - -```php -// Lunar\Models\ProductOptionValue -$option->values()->createMany([ - [ - 'name' => [ - 'en' => 'Blue', - 'fr' => 'Bleu', - ], - ], - [ - 'name' => [ - 'en' => 'Red', - 'fr' => 'Rouge', - ], - ], -]); -``` - -This Product option and it's values are now ready to be used to with Product Variants. - -# Product Shipping - -By default Lunar will mark all product variants as `shippable`. If you don't need to ship a certain variant then you can -simply set this to false. - -```php -$variant->update([ - 'shippable' => false, -]); -``` - -## Product dimensions - -Product's can have different dimensions assigned to them, the values we have available are - -- Length -- Width -- Height -- Weight -- Volume - -For handling conversions, we use the [Cartalyst Converter](https://github.com/cartalyst/converter/tree/master) package. -This supports a wide range of UOM and can convert those values to different measurements. - -Each UOM has a corresponding `_value` and `_unit` column in the database. For example: - -```php -length_value -length_unit -width_value -width_unit -``` - -### Configuring measurements - -You can configure the available UOM's in the `lunar/shipping.php` config file. Here is an example of what Lunar provides -by default: - -**Length** - -- m -- mm -- cm -- ft -- in - -**Weight** - -- kg -- g -- lbs - -**Volume** - -- l -- ml -- gal -- floz - -### Getting and converting measurement values - -You are free to access to the `*_value` and `*_unit` values for the variant and use them in their raw form, however we -do offer an accessor for each unit that you can use: - -```php -$variant->length->to('length.ft')->convert(); -``` - -#### Volume calculation - -Volume is calculated different to the rest of the measurements. You can either have it automatically figure out the -volume or manually set it yourself: - -```php -$variant->update([ - 'length_value' => 50, - 'length_unit' => 'mm', - 'height_value' => 50, - 'height_unit' => 'mm', - 'width_value' => 50, - 'width_unit' => 'mm', -]) - -// By default will return ml -$variant->volume->getValue(); // 125 - -// Use any UOM you want for volume -$variant->volume->to('volume.l')->convert()->getValue(); // 12.5 - -// Set the volume manually and it'll always use that. -$variant->update([ - 'volume_unit' => 'floz', - 'volume_value' => 100 -]); - -$variant->volume->getValue(); // 100 - -$variant->volume->to('volume.l')->convert()->getValue(); // 2.95735... -``` - -**Get a formatted value** - -If you want to display a formatted value, you can! - -```php -$variant->length->format(); // 50cm -``` - -## Variants - -Variants allow you to specify different variations of a product. Think things like Small Blue T-shirt, or Size 8 and -Size 9 Leather Boots. Your product is the main parent and your variants are based off that product to create multiple -permutations. - -Variants are also responsible for storing data such as Pricing, Inventory/Stock information, Shipping information etc. -For that reason a product will always have at least one variant. - -When you decide you want to offer more than one variant for a product, upon generation, Lunar will take the first -variant and use that as a base for all other variants in terms of pricing, inventory etc. So you won't lose any data you -may have already saved against the existing product. - -## Creating variants - -### New Product - -If you have a new product, you would create your variants in bulk based on an array of `ProductOptionValue` IDs. - -You will need to determine what variants you need to create and assign the correct option values to that variant. - -For example, lets say we have an option "Colour" and we want to create "Blue" and "Red" variants. - -::: tip -A product variant will require a product, currency and a tax class. -If you do not have these entities created, you will need to do so before continuing. -::: - -If needed, Create the product and tax class. - -```php -$product = Product::create([...]); -$taxClass = TaxClass::create([...]); -$currency = Currency::create([...]); -``` - -Otherwise, fetch them. - -```php -$product = Product::where(...)->first(); -$taxClass = TaxClass::where(...)->first(); -$currency = Currency::where(...)->first(); -``` - -You can also specify a status to limit results to using the provided scope. - -```php -Product::status('published')->get(); -``` - -Then we need to create our base option and it's values. - -```php -$option = \Lunar\Models\ProductOption::create([ - 'name' => [ - 'en' => 'Colour', - ]; - 'label' => [ - 'en' => 'Colour', - ]; -]); - -$blueOption = $option->values()->create([ - 'name' => [ - 'en' => 'Blue', - ], -]); - -$redOption = $option->values()->create([ - 'name' => [ - 'en' => 'Red', - ], -]); -``` - -From here we create our variant, associate it with a product and a tax class, then attach the option id. - -```php -$blueVariant = ProductVariant::create([ - 'sku' => 'blue-product', -]); - -$blueVariant->values()->attach($blueOption); - -$redVariant = ProductVariant::create([ - 'product_id' => $product->id, - 'tax_class_id' => $taxClass->id, - 'sku' => 'red-product', -]); - -$redVariant->values()->attach($redOption); -``` - -Now, we need to create a price for our variant. This is where we use our *currency* created or fetched earlier. - -```php -$variant->prices()->create([ - 'price' => 199, - 'currency_id' => $currency->id, -]); -``` - -### Exceptions - -When creating variants there are some exceptions that will be thrown if certain conditions are met. - -| Exception | Conditions | -|:-------------------------------------------------|:---------------------------------------------------------------------------------------| -| `Illuminate\Validation\ValidationException` | Thrown if validation fails on the value options array. | - -## Pricing - -### Overview - -Prices are stored in the database as integers. When retrieving a `Price` model the `price` and `compare_price` -attributes are cast to a `Price` datatype. This casting gives you some useful helpers when dealing with prices on your -front end. - -| Field | Description | Default | Required | -|:--------------------|:-------------------------------------------------------------------------------------|:--------|:---------| -| `price` | A integer value for the price | `null` | yes | -| `compare_price` | For display purposes, allows you to show a comparison price, e.g. RRP. | `null` | no | -| `currency_id` | The ID of the related currency | `null` | yes | -| `min_quantity` | The minimum quantity required to get this price. | `1` | no | -| `customer_group_id` | The customer group this price relates to, leaving as `null` means any customer group | `null` | no | -| `priceable_type` | This is the class reference to the related model which owns the price | `null` | yes | -| `priceable_id` | This is the id of the related model which owns the price | `null` | yes | - -```php -$priceable = \Lunar\Models\ProductVariant::create([/** ... */]); -$price = \Lunar\Models\Price::create([ - 'price' => 199, - 'compare_price' => 299, - 'currency_id' => 1, - 'min_quantity' => 1, - 'customer_group_id' => null, - 'priceable_type' => $priceable->getMorphClass(), - 'priceable_id' => $priceable->id, -]); -``` - -## Price formatting - -For the full reference on how to format prices for your storefront see the [Pricing Reference](/core/reference/pricing) - -::: tip -The same methods apply to the compare_price attribute -::: - -### Base Pricing - -Pricing is defined on a variant level, meaning you will have a different price for each variant and also for each -currency in the system. In order to add pricing to a variant, you can either create the model directly or use the -relationship method. - -```php -$priceable = \Lunar\Models\ProductVariant::create([/** ... */]); -\Lunar\Models\Price::create([ - 'price' => 199, - 'compare_price' => 299, - 'currency_id' => 1, - 'min_quantity' => 1, - 'customer_group_id' => null, - 'priceable_type' => $priceable->getMorphClass(), - 'priceable_id' => $priceable->id, -]); -``` - -```php -$variant->prices()->create([/* .. */]); -``` - -### Customer group pricing - -You can specify which customer group the price applies to by setting the `customer_group_id` column. If left as `null` -the price will apply to all customer groups. This is useful if you want to have different pricing for certain customer -groups and also different price quantity breaks per customer group. - -### Price Break Pricing - -Price Break pricing is a concept in which when you buy in bulk, the cost per item will change (usually go down). With Pricing -on Lunar, this is determined by the `min_quantity` column when creating prices. For example: - -```php -Price::create([ - // ... - 'price' => 199, - 'compare_price' => 399, - 'min_quantity' => 1, -]); - -Price::create([ - // ... - 'price' => 150, - 'compare_price' => 399, - 'min_quantity' => 10, -]); -``` - -In the above example if you order between 1 and 9 items you will pay `1.99` per item. But if you order at least 10 you -will pay `1.50` per item. - -## Fetching the price - -Once you've got your pricing all set up, you're likely going to want to display it on your storefront. We've created -a `PricingManager` which is available via a facade to make this process as painless as possible. - -To get the pricing for a product you can simply use the following helpers: - -#### Minimum example - -A quantity of 1 is implied when not passed. - -```php -$pricing = \Lunar\Facades\Pricing::for($variant)->get(); -``` - -#### With Quantities - -```php -$pricing = \Lunar\Facades\Pricing::qty(5)->for($variant)->get(); -``` - -#### With Customer Groups - -If you don't pass in a customer group, Lunar will use the default, including any pricing that isn't specific to a -customer group. - -```php -$pricing = \Lunar\Facades\Pricing::customerGroups($groups)->for($variant)->get(); - -// Or a single customer group -$pricing = \Lunar\Facades\Pricing::customerGroup($group)->for($variant)->get(); -``` - -#### Specific to a user - -The PricingManager assumes you want the price for the current authenticated user. - -If you want to always return the guest price, you may use... - -```php -$pricing = \Lunar\Facades\Pricing::guest()->for($variant)->get(); -``` - -Or to specify a different user... - -```php -$pricing = \Lunar\Facades\Pricing::user($user)->for($variant)->get(); -``` - -#### With a specific currency - -If you don't pass in a currency, the default is implied. - -```php -$pricing = \Lunar\Facades\Pricing::currency($currency)->for($variant)->get(); -``` - -#### For a model - -Assuming you have a model that implements the `hasPrices` trait, such as a `ProductVariant`, you can use the following -to retrieve pricing. - -```php -$pricing = $variant->pricing()->qty(5)->get(); -``` - -::: danger Be aware -If you try and fetch a price for a currency that doesn't exist, a ` Lunar\Exceptions\MissingCurrencyPriceException` -exception will be thrown. -::: - ---- - -This will return a `PricingResponse` object which you can interact with to display the correct prices. Unless it's a -collection, each property will return a `Lunar\Models\Price` object. - -```php -/** - * The price that was matched given the criteria. - */ -$pricing->matched; - -/** - * The base price associated to the variant. - */ -$pricing->base; - -/** - * A collection of all the price quantity breaks available for the given criteria. - */ -$pricing->priceBreaks; - -/** - * All customer group pricing available for the given criteria. - */ -$pricing->customerGroupPrices; -``` - -Sometimes you might want to simply get all prices a product has from the variants, instead of loading up all a products -variants fetching the prices that way, you can use the `prices` relationship on the product. - -```php -$product->prices -``` - -This will return a collection of `Price` models. - -### Storing Prices Inclusive of Tax - -Lunar allows you to store pricing inclusive of tax if you need to. This is helpful if you need to show charm pricing, at -$9.99 for example, which may not be possible if pricing is stored exclusive of tax due to rounding. - -To start you will need to set the `stored_inclusive_of_tax` config value in `lunar/pricing` to `true`. Then you will -need to ensure your default Tax Zone is set up correctly with the correct tax rates. - -Once set, the cart will automatically calculate the tax for you. - -If you need to show both ex. and inc. tax pricing on your product pages, you can use the following methods which are -available on the `Lunar\Models\Price` model. - -```php -$price->priceIncTax(); -$price->priceExTax(); -$price->comparePriceIncTax(); // to retrieve the tax inclusive compare price -``` - -### Customising Prices with Pipelines - -All pipelines are defined in `config/lunar/pricing.php` - -```php -'pipelines' => [ - //, -], -``` - -You can add your own pipelines to the configuration, they might look something like: - -```php -pricing->matched; - - $matchedPrice->price->value = 200; - - $pricingManager->pricing->matched = $matchedPrice; - - return $next($pricingManager); - } -} -``` - -```php -'pipelines' => [ - // ... - App\Pipelines\Pricing\CustomPricingPipeline::class, -], -``` - -::: tip -Pipelines will run from top to bottom -::: - -## Full Example - -For this example, we're going to be creating some Dr. Martens boots. Below is a screenshot of what we're aiming for: - -![](/images/products/dr-martens.png) - -Here are the steps we're going to take: - -- Create our product type -- Create the initial product -- Create the product options and their values -- Generate the variants based on those values - -### Set up the product type. - -```php -$productType = Lunar\Models\ProductType::create([ - 'name' => 'Boots', -]); -``` - -::: tip Note -This example assumes we already have Attributes set up for name and description and that they're assigned to the product -type. -::: - -### Create the initial product - -```php -Lunar\Models\Product::create([ - 'product_type_id' => $productType->id, - 'status' => 'published', - 'brand_id' => $brandId, - 'sku' => 'DRBOOT', - 'attribute_data' => [ - 'name' => new TranslatedText(collect([ - 'en' => new Text('1460 PATENT LEATHER BOOTS'), - ])), - 'description' => new Text('Even more shades from our archive...'), - ], -]); -``` - -### Product Options - -Based on the example above we're going to need 2 options, Size and Colour. - -```php -$colour = Lunar\Models\ProductOption::create([ - 'name' => [ - 'en' => 'Colour', - ], - 'label' => [ - 'en' => 'Colour', - ], -]); - -$size = Lunar\Models\ProductOption::create([ - 'name' => [ - 'en' => 'Size', - ], - 'label' => [ - 'en' => 'Size', - ], -]); -``` - -### Product Option Values - -From here we now need to create our option values like so: - -```php -$colour->values()->createMany([ - [ - 'name' => [ - 'en' => 'Black', - ], - ], - [ - 'name' => [ - 'en' => 'White', - ], - ], - [ - 'name' => [ - 'en' => 'Pale Pink', - ], - ], - [ - 'name' => [ - 'en' => 'Mid Blue', - ], - ], -]); - -// We won't do all the sizes here, just enough to get the idea... -$size->values()->createMany([ - [ - 'name' => [ - 'en' => '3', - ], - ], - [ - 'name' => [ - 'en' => '6', - ], - ], -]); -``` - -### Generate the variants - -First we just need to grab the values we want to use to generate the variants. Since we're generating them for -everything, we just grab all of them. - -```php -$optionValueIds = $size->values->merge($colour->values)->pluck('id'); - -\Lunar\Hub\Jobs\Products\GenerateVariants::dispatch($product, $optionValueIds); -``` - -::: tip -When generating variants, the sku will be derived from the Product's base SKU, in this case `DRBOOT` and will be -suffixed with `-{count}`. -::: - -The resulting generation is as follows: - -| SKU | Colour | Size | -|:---------|:----------|:-----| -| DRBOOT-1 | Black | 3 | -| DRBOOT-2 | Black | 6 | -| DRBOOT-3 | White | 3 | -| DRBOOT-4 | White | 6 | -| DRBOOT-5 | Pale Pink | 3 | -| DRBOOT-6 | Pale Pink | 6 | -| DRBOOT-7 | Mid Blue | 3 | -| DRBOOT-8 | Mid Blue | 6 | - -You are then free to change the SKU's as you see fit, update the pricing for each variant etc before publishing. diff --git a/docs/core/reference/search.md b/docs/core/reference/search.md deleted file mode 100644 index 705ccf870e..0000000000 --- a/docs/core/reference/search.md +++ /dev/null @@ -1,63 +0,0 @@ -# Search - -## Overview - -Search is configured using the [Laravel Scout](https://laravel.com/docs/scout) package. - -Using Scout allows us to provide search out the box but also make it easy for you as the developer to customise and tailor searching to your needs. - -## Initial set up - -The database driver provides basic search to get you up and running but you will likely find you want to implement something with a bit more power, such as [Meilisearch](https://www.meilisearch.com/) or [Algolia](https://www.algolia.com/). - -## Configuration - -By default, scout has the setting `soft_delete` set to `false`. You need to make sure this is set to `true` otherwise you will see soft deleted models appear in your search results. - -If you want to use other models or your own models in the search engine, you can add the reference for them on the config file. - -```php -'models' => [ - // These models are required by the system, do not change them. - \Lunar\Models\Collection::class, - \Lunar\Models\Product::class, - \Lunar\Models\ProductOption::class, - \Lunar\Models\Order::class, - \Lunar\Models\Customer::class, - // Below you can add your own models for indexing - ] -``` - -## Index records - -If you installed the Lunar package in an existing project and you would like to use the database records with the search engine, or you just need to do some maintenance on the indexes, you can use the index command. - -```sh -php artisan lunar:search:index -``` - -The command will import the records of the models listed in the `lunar/search.php` configuration file. Type `--help` to see the available options. - -## Meilisearch - -If you used the Meilisearch package you would like to use the command to create filterable and searchable attributes on Meilisearch indexes. - -```sh -php artisan lunar:meilisearch:setup -``` - -## Engine Mapping - -By default, Scout will use the driver defined in your .env file as `SCOUT_DRIVER`. So if that's set to `meilisearch`, all your models will be indexed via the Meilisearch driver. This can present some issues, if you wanted to use a service like Algolia for Products, you wouldn't want all your Orders being indexed there since it will ramp up the record count and the cost. - -In Lunar we've made it possible to define what driver you would like to use per model. It's all defined in the `config/lunar/search.php` config file and looks like this: - -```php -'engine_map' => [ - \Lunar\Models\Product::class => 'algolia', - \Lunar\Models\Order::class => 'meilisearch', - \Lunar\Models\Collection::class => 'meilisearch', -], -``` - -It's quite self explanatory, if a model class isn't added to the config, it will take on the Scout default. diff --git a/docs/core/reference/tags.md b/docs/core/reference/tags.md deleted file mode 100644 index bcdc1dd22c..0000000000 --- a/docs/core/reference/tags.md +++ /dev/null @@ -1,48 +0,0 @@ -# Tags - -## Overview - -Tags serve a simple function in Lunar, you can add tags to models. This is useful for relating otherwise unrelated models in the system. They will also impact other parts of the system such as Dynamic collections. - -For example, you could have two products "Blue T-Shirt" and "Blue Shoes", which in their nature are unrelated, but you could add a `BLUE` tag to each product and then create a Dynamic Collection to include any products with a `BLUE` tag and they will be returned. - -::: tip -Heads up! Tags are converted uppercase as they are saved. -::: - -## Enabling tags - -In order to enable tagging on a model, simply add the `HasTags` trait. - -```php -syncTags($tags); - -$model->tags; - -// ['TAG ONE', 'TAG TWO', 'TAG THREE'] -``` - -If a tag exists already by name it will use it, otherwise they will be created. The process runs via a job so will run in the background if you have that set up. diff --git a/docs/core/reference/taxation.md b/docs/core/reference/taxation.md deleted file mode 100644 index e93596149c..0000000000 --- a/docs/core/reference/taxation.md +++ /dev/null @@ -1,161 +0,0 @@ -# Taxation - -## Overview - -No one likes taxes! But we have to deal with them... Lunar provides manual tax rules to implement the correct sales tax for each order. For complex taxation (e.g. US States) we suggest integrating with a service such as [TaxJar](https://www.taxjar.com/). - - -## Tax Classes - -Tax Classes are assigned to Products and allow us to classify products to certain taxable groups that may have differing tax rates. - -```php -Lunar\Models\TaxClass -``` - -|Field|Description| -|:-|:-| -|id|| -|name|e.g. `Clothing`| -|created_at|| -|updated_at|| - -```php -$taxClass = TaxClass::create([ - 'name' => 'Clothing', -]); -``` - -## Tax Zones - -These specify a geographic zone for tax rates to be applied. Tax Zones can be based upon countries, states or zip/post codes. - -```php -Lunar\Models\TaxZone -``` - -|Field| Description | -|:-|:------------------------------------| -|id| | -|name| e.g. `UK` | -|zone_type| `country`, `states`, or `postcodes` | -|price_display| `tax_inclusive` or `tax_exclusive` | -|active| true/false | -|default| true/false | -|created_at| | -|updated_at| | - -```php -$taxZone = TaxZone::create([ - 'name' => 'UK', - 'zone_type' => 'country', - 'price_display' => 'tax_inclusive', - 'active' => true, - 'default' => true, -]); -``` - -```php -Lunar\Models\TaxZoneCountry -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_zone_id|| -|country_id|| -|created_at|| -|updated_at|| - - -```php -$taxZone->countries()->create([ - 'country_id' => \Lunar\Models\Country::first()->id, -]); -``` - -```php -Lunar\Models\TaxZoneState -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_zone_id|| -|state_id|| -|created_at|| -|updated_at|| - -```php -$taxZone->states()->create([ - 'state_id' => \Lunar\Models\State::first()->id, -]); -``` - -```php -Lunar\Models\TaxZonePostcode -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_zone_id|| -|country_id|| -|postcode|wildcard, e.g. `9021*`| -|created_at|| -|updated_at|| - -```php -Lunar\Models\TaxZoneCustomerGroup -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_zone_id|| -|customer_group_id|| -|created_at|| -|updated_at|| - - -## Tax Rates - -Tax Zones have one or many tax rates. E.g. you might have a tax rate for the State and also the City, which would collectively make up the total tax amount. - -```php -Lunar\Models\TaxRate -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_zone_id|| -|name|e.g. `UK`| -|created_at|| -|updated_at|| - -```php -Lunar\Models\TaxRateAmount -``` - -|Field|Description| -|:-|:-| -|id|| -|tax_rate_id|| -|tax_class_id|| -|percentage|e.g. `6` for 6%| -|created_at|| -|updated_at|| - - -## Settings -- Shipping and other specific costs are assigned to tax classes in the settings. -- Calculate tax based upon Shipping or Billing address? -- Default Tax Zone - -## Extending - -Sometimes the standard tax calculations aren't enough, and you may want to implement your own logic, perhaps connecting to a Tax service such as TaxJar. - -Lunar allows you to implement your own tax driver, check the [Extending Lunar](/core/extending/taxation) section for more information. - diff --git a/docs/core/reference/urls.md b/docs/core/reference/urls.md deleted file mode 100644 index dd51447382..0000000000 --- a/docs/core/reference/urls.md +++ /dev/null @@ -1,157 +0,0 @@ -# URLs - -## Overview - -URLs are not to be confused with Routes in Laravel. You can create routes for a number of resources, but most commonly they would be created for a Product. - -They allow you to add a way to identify/query for a resource without having to use the ID of that resource. Notably these would be useful for vanity URLs so instead of something like: - -```bash -/products/1 -``` - -On your storefront, you could have: - -```bash -/products/apple-iphone -``` - -`apple-iphone` is the slug for a URL which would correspond to a product and would allow you to fetch it easily without having to expose IDs or do any weird round trips to your API. - -::: tip -A URL cannot share the same `slug` and `language_id` columns. You can also only have one `default` URL per language for that resource. -::: - -## Creating a URL - -```php -\Lunar\Models\Url::create([ - 'slug' => 'apple-iphone', - 'language_id' => $language->id, - 'default' => true, -]); -``` - -::: tip -If you add a new default URL for a language which already has one, the new URL will override and become the new default. -::: - -```php -$urlA = \Lunar\Models\Url::create([ - 'slug' => 'apple-iphone', - 'language_id' => 1, - 'default' => true, -]); - -$urlA->default // true - -$urlB = \Lunar\Models\Url::create([ - 'slug' => 'apple-iphone-26', - 'language_id' => 1, - 'default' => true, -]); - -$urlA->default // false -$urlB->default // true - -/** - * Since this is a different language, no other URLs will be changed. - **/ -$urlC = \Lunar\Models\Url::create([ - 'slug' => 'apple-iphone-french', - 'language_id' => 2, - 'default' => true, -]); - - -$urlA->default // false -$urlB->default // true -$urlC->default // true -``` - -## Deleting a URL - -When you delete a URL, if it was the default then Lunar will look for a non default of the same language and assign that instead. - - -```php -$urlA = \Lunar\Models\Url::create([ - 'slug' => 'apple-iphone', - 'language_id' => 1, - 'default' => true, -]); - -$urlB = \Lunar\Models\Url::create([ - 'slug' => 'apple-iphone-26', - 'language_id' => 1, - 'default' => false, -]); - -$urlB->default // false - -$urlA->delete(); - -$urlB->default // true -``` - - -## Adding URL support to Models - -Out the box Lunar has a few pre-configured models which have URLs - -- Products -- Collections - -You are free to add URLs to your own models. - -```php -urls; // Collection -``` - -## Automatically generating URLs - -You can tell Lunar to generate URLs for models that use the `HasUrls` trait automatically by setting the `generator` config option in `config/lunar/urls.php`. - -By default this is set to `Lunar\Generators\UrlGenerator::class` which means URLs will be generated. To disable this, set the config like below: - -```php -return [ - 'generator' => null -]; -``` - -By default this will use the default language and take the `name` attribute as the slug, you are of course free to use your own class for this. You just need to make sure there is a `handle` method which accepts a `Model`. - -```php -= 8.2 -- MySQL 8.0+ / PostgreSQL 9.2+ -- exif PHP extension (on most systems it will be installed by default) -- intl PHP extension (on most systems it will be installed by default) -- bcmath PHP extension (on most systems it will be installed by default) -- GD PHP extension (used for image manipulation) - -## Installation - -::: tip -We assume you have a suitable local development environment in which to run Lunar. We would highly suggest [Laravel Herd](https://herd.laravel.com) for this purpose. -::: - -### Create a New Project - -```bash -composer create-project --stability dev lunarphp/livewire-starter-kit my-store - -cd my-store -``` - -or using the latest Laravel Installer you can do... - -```bash -laravel new my-store --using=lunarphp/livewire-starter-kit - -cd my-store -``` - - -### Configure the Laravel app - -Copy the `.env.example` file to `.env` and make sure the details match to your install. - -```bash -cp .env.example .env -``` - -All the relevant configuration files should be present in the repo. - -### Migrate and Seed - -Install Lunar - -```bash -php artisan lunar:install -``` - -Seed the demo data. - -```bash -php artisan db:seed -``` - -Link the storage directory - -```bash -php artisan storage:link -``` - -## Finished 🚀 - -You are now installed! - -- You can access the storefront at `http://` -- You can access the admin hub at `http:///lunar` - -You can review the source code at the GitHub Repository: [https://github.com/lunarphp/livewire-starter-kit](https://github.com/lunarphp/livewire-starter-kit). diff --git a/docs/core/storefront-utils/storefront-session.md b/docs/core/storefront-utils/storefront-session.md deleted file mode 100644 index 4b06b2e463..0000000000 --- a/docs/core/storefront-utils/storefront-session.md +++ /dev/null @@ -1,122 +0,0 @@ -# Storefront Session - -## Overview - -The storefront session facade is a provided to help keep certain resources your storefront needs set, such as channel, customer group etc. - -```php -use Lunar\Facades\StorefrontSession; -``` - -## Channels - -### Initialise the Channel - -This will set the Channel based on what's been previously set, otherwise it will use the default. - -```php -StorefrontSession::initChannel(); -``` - -:::tip This is automatically called when using the facade. -::: - -### Set the Channel - -```php -$channel = new Channel([ - 'name' => 'Webstore', - 'handle' => 'webstore', -]); - -StorefrontSession::setChannel($channel); -StorefrontSession::setChannel('webstore'); -``` - -### Get the Channel - -```php -StorefrontSession::getChannel(); -``` - -## Customer Groups - -### Initialise the Customer Groups - -This will set the Customer Groups based on what's been previously set (from the session), otherwise it will use the default record. - -```php -StorefrontSession::initCustomerGroups(); -``` - -:::tip This is automatically called when using the facade. -::: - -### Set the Customer Groups - -```php -$customerGroup = new CustomerGroup([ - 'name' => 'Retail', - 'handle' => 'retail', -]); - -// Set multiple customer groups -StorefrontSession::setCustomerGroups(collect($customerGroup)); - -// Set a single customer group, under the hood this will just call `setCustomerGroups`. -StorefrontSession::setCustomerGroup($customerGroup); -``` - -### Get the Customer Groups - -```php -StorefrontSession::getCustomerGroups(); -``` - -## Customer - -### Initialise the Customer - -This will set the Customer based on what's been previously set (from the session), otherwise it will retrieve the latest customer attached with the logged in user. - -```php -StorefrontSession::initCustomer(); -``` - -:::tip This is automatically called when using the facade. -::: - -### Set the Customer - -```php -$customer = /** your store logic to determine current customer */ ; - -StorefrontSession::setCustomer($customer); -``` - -### Get the Customer - -```php -StorefrontSession::getCustomer(); -``` - -## Currencies - -### Set the Currency - -```php -$currency = new Currency([ - 'name' => 'US Dollars', - 'code' => 'USD', - // ... -]); - -StorefrontSession::setCurrency($currency); -StorefrontSession::setCurrency('USD'); -``` - -### Get the Currency - -```php -StorefrontSession::getCurrency(); -``` diff --git a/docs/core/upgrading.md b/docs/core/upgrading.md deleted file mode 100644 index 4bfeb36070..0000000000 --- a/docs/core/upgrading.md +++ /dev/null @@ -1,413 +0,0 @@ -# Upgrading - -## General Upgrade Instructions - -Update the package - -```sh -composer update lunarphp/lunar -``` - -Run any migrations - -```sh -php artisan migrate -``` - -## [Unreleased] - -#### Rename Order resource OrderSummaryInfolist Extension hook -rename `exendOrderSummaryInfolist` to `extendOrderSummaryInfolist` - -## 1.0.0 (stable) - -### Medium Impact - -#### Scout no longer enabled by default for admin panel -With the recent panel search improvements and the fact most users initially will not be using Scout, the default -configuration for the panel is now to *not* enable Scout for search. - -If you are using Scout, you may need to update your `panel.php` config. - -```php -'scout_enabled' => true, -``` - - -### Low Impact - -#### Telemetry -This release introduces anonymous usage insights, which are sent via a deferred API call to Lunar. The reason for this addition -is to allow us to have an idea of how Lunar is being used and at what capacity. We do not send or use any identifying information whatsoever. - -This is completely optional, however, it is turned on by default. To opt-out add the following to your service provider's boot method: - -```php -\Lunar\Facades\Telemetry::optOut(); -``` - -## 1.0.0-beta.24 - -### Medium Impact - -#### Customer `vat_no` field renamed -The field on the `customers` table has been renamed to `tax_identifier`. This is to align with the new field of the same -name on `addresses`, `cart_addresses` and `order_addresses`. - -### Low Impact - -#### Buy X Get Y Discount conditions and rewards - -Buy X Get Y discounts can now use collections and variants as conditions, and variants as rewards. As part of this change the `discount_purchasables` table has been renamed `discountables` and has its own `Discountable` model. If you have been using `discount_purchasables` directly, or the `purchasables` relation on the discount model, you will need to update your code. - -## 1.0.0-beta.22 - -### High Impact - -#### Removed Laravel 10 Support - -Laravel 10 support has been removed as it was becoming harder to support. You will want to upgrade your projects to -Laravel 11+ for this release. You may consider [Laravel Shift](https://laravelshift.com/) to assist you. - -#### Lunar Panel Discount Interface - -The `LunarPanelDiscountInterface` now requires a `lunarPanelRelationManagers` method that returns an array of relation managers you want to show in the admin panel when the discount type is used. You will need to update any custom discount types you have created to include this method. - -## 1.0.0-beta.21 - -### High Impact - -#### Order reference generation changes - -The current order reference generator uses the format `YYYY-MM-{X}` which has been implemented since the early days of when Lunar was called GetCandy. - -This approach to formatting is not great for order references and can lead to anomalies when attempting to determine the next reference in the sequence. - -The new format uses the Order ID and adds leading zeros and an optional prefix i.e. - -Assuming order ID is 1965 -``` -// Old -2025-04-00250 -// New -00001965 -``` -The length of the reference, plus the prefix can now be defined in the `lunar/orders.php` config file: - -```php - 'reference_format' => [ - /** - * Optional prefix for the order reference - */ - 'prefix' => null, - - /** - * STR_PAD_LEFT: 00001965 - * STR_PAD_RIGHT: 19650000 - * STR_PAD_BOTH: 00196500 - */ - 'padding_direction' => STR_PAD_LEFT, - - /** - * 00001965 - * AAAA1965 - */ - 'padding_character' => '0', - - /** - * If the length specified below is smaller than the length - * of the Order ID, then no padding will take place. - */ - 'length' => 8, - ], -``` - -If you wish to keep using the current action used to generate references, you can [copy the existing class](https://github.com/lunarphp/lunar/blob/1.0.0-beta20/packages/core/src/Base/OrderReferenceGenerator.php) into your app and update the `reference_generator` path in config. - -### Medium Impact - -#### Two-Factor Authentication has been added - -To continue improving security for the Lunar panel, Staff members now have the ability to set up Two-Factor Authentication. Currently this is opt-in, however you can enforce all Staff members to set up 2FA: - -```php -public function register() -{ - \Lunar\Admin\Support\Facades\LunarPanel::enforceTwoFactorAuth()->register(); -} -``` - -If you do not wish to use Two-Factor Authentication at all, you can disable it and the option to set it up won't show. - -```php -public function register() -{ - \Lunar\Admin\Support\Facades\LunarPanel::disableTwoFactorAuth()->register(); -} -``` - -## 1.0.0-beta.1 - -### High Impact - -#### Model Extending - -Model extending has been completely rewritten and will require changes to your Laravel app if you have previously extended Lunar models. - -The biggest difference is now Lunar models implement a contract (interface) and support dependency injection across your storefront and the Lunar panel. - -You will need to update how you register models in Lunar. - -##### Old -```php - ModelManifest::register(collect([ - Product::class => \App\Models\Product::class, - // ... - ])); -``` - -##### New -```php -ModelManifest::replace( - \Lunar\Models\Contracts\Product::class, - \App\Models\Product::class -); -``` - -::: tip -If your models are not extending their Lunar counterpart, you must implement the relevant contract in `Lunar\Models\Contracts` -::: - -Look at the [model extending](/core/extending/models) section for all available functionality. - -#### Polymorphic relationships - -In order to support model extending better, all polymorphic relationships now use an alias instead of the fully qualified class name, this allows relationships to resolve to your custom model when interacting with Eloquent. - -There is an additional config setting in `config/lunar/database.php`, where you can set whether these polymorph mappings should be prefixed in Lunar's context. - -```php -'morph_prefix' => null, -``` - -By default, this is set as `null` so the mapping for a product would just be `product`. - -There is a migration which will handle this change over for Lunar tables and some third party tables, however you may need to add your own migrations to other tables or to switch any custom models you may have. - -#### Shipping methods availability - -Shipping methods are now associated to Customer Groups. If you are using the shipping addon then you should ensure that all your shipping methods are associated to the correct customer groups. - -#### Stripe Addon - -The Stripe addon will now attempt to update an order's billing and shipping address based on what has been stored against the Payment Intent. This is due to Stripe not always returning this information during their express checkout flows. This can be disabled by setting the `lunar.stripe.sync_addresses` config value to `false`. - -##### PaymentIntent storage and reference to carts/orders -Currently, PaymentIntent information is stored in the Cart model's meta, which is then transferred to the order when created. - -Whilst this works okay it causes for limitations and also means that if the carts meta is ever updated elsewhere, or the intent information is removed, then it will cause unrecoverable loss. - -We have now looked to move away from the payment_intent key in the meta to a StripePaymentIntent model, this allows us more flexibility in how payment intents are handled and reacted on. A StripePaymentIntent will be associated to both a cart and an order. - -The information we store is now: - -- `intent_id` - This is the PaymentIntent ID which is provided by Stripe -- `status` - The PaymentIntent status -- `event_id` - If the PaymentIntent was placed via the webhook, this will be populated with the ID of that event -- `processing_at` - When a request to place the order is made, this is populated -- `processed_at` - Once the order is placed, this will be populated with the current timestamp - -##### Preventing overlap -Currently, we delay sending the job to place the order to the queue by 20 seconds, this is less than ideal, now the payment type will check whether we are already processing this order and if so, not do anything further. This should prevent overlaps regardless of how they are triggered. - -## 1.0.0-alpha.34 - -### Medium Impact - -#### Stripe Addon - -The Stripe driver will now check whether an order has a value for `placed_at` against an order and if so, no further processing will take place. - -Additionally, the logic in the webhook has been moved to the job queue, which is dispatched with a delay of 20 seconds, this is to allow storefronts to manually process a payment intent, in addition to the webhook, without having to worry about overlap. - -The Stripe webhook ENV entry has been changed from `STRIPE_WEBHOOK_PAYMENT_INTENT` to `LUNAR_STRIPE_WEBHOOK_SECRET`. - -The stripe config Lunar looks for in `config/services.php` has changed and should now look like: - -```php -'stripe' => [ - 'key' => env('STRIPE_SECRET'), - 'public_key' => env('STRIPE_PK'), - 'webhooks' => [ - 'lunar' => env('LUNAR_STRIPE_WEBHOOK_SECRET'), - ], -], -``` - -## 1.0.0-alpha.32 - -### High Impact - -There is now a new `LunarUser` interface you will need to implement on your `User` model. - -```php -// ... -class User extends Authenticatable implements \Lunar\Base\LunarUser -{ - use \Lunar\Base\Traits\LunarUser; -} -``` - -## 1.0.0-alpha.31 - -### High Impact - -Certain parts of `config/cart.php` which are more specific to when you are interacting with carts via the session have been relocated to a new `config/cart_session.php` file. - -```php -// Move to config/cart_session.php -'session_key' => 'lunar_cart', -'auto_create' => false, -``` - -You should also check this file for any new config values you may need to add. - -## 1.0.0-alpha.29 - -### High Impact - -#### Cart calculate function will no longer recalculate - -If you have been using the `$cart->calculate()` function it has previously always run the calculations regardless of -whether the cart has already been calculated. Now the calculate function will only run if we don't have cart totals. -To allow for recalculation we have now introduced `$cart->recalculate()` to force the cart to recalculate. - -#### Unique index for Collection Group handle - -Collection Group now have unique index on the column `handle`. -If you are creating Collection Group from the admin panel, there is no changes required. - -### Medium Impact - -#### Update custom shipping modifiers signature - -The `\Lunar\Base\ShippingModifier` `handle` method now correctly passes a closure as the second parameter. You will need to update any custom shipping modifiers that extend this as follows: - -```php -public function handle(\Lunar\Models\Cart $cart, \Closure $next) -{ - //.. - - return $next($cart); -} -``` - -## 1.0.0-alpha.26 - -### Medium Impact - -If you are using your own classes that implement the `Purchasable` interface, you will need to add the following additional methods: - -```php -public function canBeFulfilledAtQuantity(int $quantity): bool; -public function getTotalInventory(): int; -``` - -If you are checking the `ProductVariant` `purchasable` attribute in your code, you should update the following check: - -```php -// Old -$variant->purchasable == 'backorder'; -// New -$variant->purchasable == 'in_stock_or_on_backorder'; - -``` - -## 1.0.0-alpha.22 - -### Medium Impact - -Carts now use soft deletes and a cart will be deleted when `CartSession::forget()` is called. -If you don't want to delete the cart when you call `forget` you can pass `delete: false` as a parameter: - -```php -\Lunar\Facades\CartSession::forget(delete: false); -``` - -## 1.0.0-alpha.20 - -### High Impact - -#### Stripe addon facade change - -If you are using the Stripe addon, you need to update the facade as the name has changed. - -```php -// Old -\Lunar\Stripe\Facades\StripeFacade; - -// New -\Lunar\Stripe\Facades\Stripe; -``` - -## 1.0.0-alpha - -:::warning -If upgrading from `0.x` please ensure you are on `0.8` before upgrading to `1.x`. -::: - -### High Impact - -#### Change to Staff model namespace - -The Staff model has changed location from `Lunar\Hub\Models\Staff` to `Lunar\Admin\Models\Staff` so this will need to be updated within -your codebase and any polymorphic relations. - -#### Spatie Media Library -This package has been upgrade to version 11, which introduces some breaking changes. -See here for more information https://github.com/spatie/laravel-medialibrary/blob/main/UPGRADING.md - -#### Media Conversions -The `lunar.media.conversions` configuration has been removed, in favour of registering custom media definitionss instead. -Media definition classes allow you to register media collections, conversions and much more. See [Media Collections](/core/reference/media.html#media-collections) -for further information. - -#### Product Options -The `position` field has been removed from the `product_options` table and is now found on the `product_product_option` -pivot table. Any position data will be automatically adjusted when running migrations. - -#### Tiers renamed to Price Breaks - -The `tier` column on pricing has been renamed to `min_quantity`, any references in code to `tiers` needs to be updated. - -##### Price Model - -```php -// Old -$priceModel->tier -// New -$priceModel->min_quantity - -// Old -$priceModel->tiers -// New -$priceModel->priceBreaks -``` - -##### Lunar\Base\DataTransferObjects\PricingResponse - -```php -// Old -public Collection $tiered, -// New -public Collection $priceBreaks, -``` - -##### Lunar\Base\DataTransferObjects\PaymentAuthorize - -Two new properties have been added to the constructor for this DTO. - -```php -public ?int $orderId = null, -public ?string $paymentType = null -``` diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index fff488175e..0000000000 --- a/docs/index.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "Lunar Documentation" -description: "Learn to create your next online store in Laravel with Lunar E-Commerce." - -# https://vitepress.dev/reference/default-theme-home-page -layout: home - -hero: - name: "Documentation" - text: "Lunar Headless E‑Commerce" - tagline: Explore our guides and examples to integrate Lunar into your Laravel application. - actions: - - theme: brand - text: Get Started - link: /core/overview - - theme: alt - text: Join us on Discord - link: https://discord.gg/v6qVWaf - -features: - - title: E-Commerce Core - details: Based around Eloquent models, quickly build your online store in any Laravel stack. - link: /core/overview - - title: Admin Panel - details: Filament-powered admin panel to administer your product catalog, customers, orders and much more. - link: /admin/overview - - title: Support - details: Get community help or expert support—whatever you need for your Lunar-powered project. - link: /support ---- diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 9306c527d3..0000000000 --- a/docs/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "devDependencies": { - "vitepress": "^1.6.3" - }, - "scripts": { - "docs:dev": "vitepress dev", - "docs:build": "vitepress build", - "docs:preview": "vitepress preview" - }, - "type": "module" -} diff --git a/docs/public/icon-dark.svg b/docs/public/icon-dark.svg deleted file mode 100644 index f6a5308509..0000000000 --- a/docs/public/icon-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icon.svg b/docs/public/icon.svg deleted file mode 100644 index a856d3109f..0000000000 --- a/docs/public/icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/images/products/dr-martens.png b/docs/public/images/products/dr-martens.png deleted file mode 100644 index a0ddb4546cc7e415a3f4c54268fa4a8f5cd97028..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295878 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 z z68*|WR1XvAw_ocPctC2Ip?H$P^)hv&dw9Y`= zAV82CnRv)U$C(jazx1%=X}|Fkf-lu^KVW+ORjQe=SRpj1(PR;C@k@eMY@+4|gKUl@ zLkI(q^lj5`iyFc`3kqgPnkb?Jv%Fa;1h2f52*1|N?7HF5X;V6l$s?jG_Si=K9;15a zYo+RY53GcbmO-yFLE}f$vvBj^W%?E($&OF`ho>J7^Pm~kdiH7dUW!oO1g+xhslTbO z#<1DU!9D_nZWEKzeWV5VS`QSMVm_IlrDoU^P9QZWS9Zrd-a1eOhp)_1O{K4~N01B5 zGbJVA1!iWXWXcc|QcraM}KGPkfRcm%px77m7&49~VgNx#lmTkOJaT zNcZEv9SCSSjj0j6md=tk$st24fA@pmTAFGwx zHWqQummckvB#gAJ$510Iwy=9yUkKb16a=(P~%{D>fRv zBE~s?#HYoOZT1gy{6J?e714s~XmFy#Z~NNIDP|(N1f=IRkaGICeX!{M1l_nt7;oHa zef{S*v-)uU`XZerD)i0%yuXcj9LjbBurDzt-O+NznOU4as3u->k+)xJ$_ynws*7gw zhlgE@A9{{!fdD;w$%ud!IQs>nU);)u)nEKK0&a`=neA%+D~SAelu`SR_U#a8Z8F^_ zlwu1QE%zLEbh89I^t>GR5aV1TbLB`Dxck7_T22ily% zg{)VC^CG=4>5=8?Sc%3`*NR=)8Vi4vR3uj?yne7T&rBZ_BuI8p(6{Jq%fDry2ld+O z$!-3OECeT_`3LEugv@<583KVopZ)5ywK?l;(DL#)@n&jfPDIg!UvbrtJF65a#44#4 zX9RFS_?ARY);I4q2C$XuHCi(r(rg$TOYiK(u_3|=Bfc3OW}(Snwa)uB%lJ&OjO_rN zPPnJ*#}?Eiqy*vKSrLC~x?0tD(DaOgl~6Jwi1-J>r5E!s_H<4em-;o z2A%S#(x0o0>-o&aG(j|(KP>h{J-2_4@M@uC9UFmWREGj?+V)$UC^ACd4|N}`D%Wm) z9G$G?J?&?&RZX)Y8s{Inw;`l}8I-#{7(7y=54H8a*sraLgk6er`2kO-x6wIQz{@1h zt4`cR{6rVjKd?W)FcqnYe{3|Fn+NV_k5T!Oyn3yP;d*VACx{{Pz;Ui2!@2Hd0O;TA9>0@ zH{@vRFHx$PDC$)1w%ILA?sl+otpFyARX#jE03qica~nOFduX9JHxVZ-BntB6EIR3g zf8KvAOKvI_I{KhcJ97AS+l)OFjzzKrP82P^iiuID|MTCH6}0Q#af1E9kSO{*vW$|z zX|f79qYU4}UtjmwtHjad%~8X$qXm=AlC5UE^<_*dw_ZzC`)M_(a6+~CjqnfyyQ!TS zDNu%gKL-OBz3*a4$$Q-+n@iQFCMIT@nG|N!=c7JRg@tnl7oH$Kjef_x>y+F}!_yl%P3Efmr#0(jTZ&mwz4_SG_{d-`#-ClIJZThzY(QDX9iP}U zaiginw`Z$6PphJnx0ie~yLLinS%cl1byEU0%E!p8BxdzmT~4{vA6LY=WF&H2t0DOQ z1nSza2^k+A1(qi^*If~wF{7eCrPrW0z)%%>OkM-qtMdd-bcpRjZ22G}(4qp4YM}j( zy3cDQ0rUMtRcOyfGoW@+F+)Ha?>Kd{l(T-WTP)3lLf3v)-*+jwN9x0SsJ4!cuq9fG zAt_`OG??=;^R&1{HlLKd0x<3;>+BsJpN3!n+8TkIo;42vIa z$ZD8uU0%p&I>htgmv zr{2JzIfZ~h2F1RxwPbTMzc)wp|4~>iqWs77z;c)5|Mz9k>1pt;$>_1kT>4?0XNq

dRMRaV3e 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 diff --git a/docs/public/logo.svg b/docs/public/logo.svg deleted file mode 100644 index b0615ef642..0000000000 --- a/docs/public/logo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/docs/support.md b/docs/support.md deleted file mode 100644 index 274b518fe2..0000000000 --- a/docs/support.md +++ /dev/null @@ -1,92 +0,0 @@ -# Support - -We’re here to help you get the most out of Lunar. Whether you need quick answers from the community or dedicated -assistance from our team, we offer multiple ways to get support for your Lunar-powered project. - -## Community Support - -Lunar is an open-source project, and we have an active community of developers who are happy to help. If you’re looking for general advice or troubleshooting tips, try the following: - -🗣️ [Discord Community](https://discord.gg/v6qVWaf) – Join the discussion, ask questions, and collaborate with other Lunar users. - -🔍 [GitHub Discussions](https://github.com/lunarphp/lunar/discussions) – Browse existing topics or start a new discussion about features and issues. - -🐛 [Bug Reports](https://github.com/lunarphp/lunar/issues) – Found a bug? Report it on our GitHub repository so we can improve Lunar. - -Community support is a great way to get help, but response times may vary. If you need priority support, consider our Premium Support options. - -## Premium Support - -Need expert guidance while integrating or extending Lunar? Our Premium Support plan provides direct access to the Lunar -team, ensuring you get the help you need to build, scale, and optimise your e-commerce platform with confidence. - -### Why Choose Premium Support? -- Direct Access to the Lunar Team – Get support from the developers behind Lunar. -- Fast Response Times – Prioritised assistance to keep your project moving. -- Private Support Channels – Engage with us via a dedicated support portal. -- Best Practices & Guidance – Ensure your implementation follows recommended patterns. -- Extension & Customisation Help – Assistance with extending Lunar to fit your needs. - -### What’s Included? - -Our support plan is designed to help your team navigate the complexities of e-commerce development with Lunar. - -- ✅ Technical Q&A – Get answers to your Lunar-related development questions. -- ✅ Troubleshooting Assistance – Help diagnosing issues with your implementation. -- ✅ Performance & Scalability Advice – Best practices for optimising your Lunar setup. -- ✅ Upgrade Guidance – Assistance with upgrading to newer versions of Lunar. -- ✅ Best Practices & Development Guidance – Advice on structuring your code and custom features. - -### What’s Not Included? - -- 🚫 Custom Development – We’ll guide you, but we won’t write custom code. -- 🚫 Full codebase reviews – We can review snippets of code, but not an entire application. -- 🚫 Bug Fixes Outside Core Lunar – We’ll help identify issues, but fixing bugs in your custom implementation is your responsibility. -- 🚫 General Laravel Support – Our focus is Lunar. For Laravel-specific questions, we recommend the official Laravel support channels. - -### Get Started - -Ready to ensure smooth development with expert-backed support? Our standard plan is $750 per month and we can also -design custom support plans to suit your needs. - - - -## Custom Development - -Need a custom solution built on Lunar? Whether you’re looking for bespoke add-ons, integrations, or a complete -e-commerce storefront, our team can help. As the creators of Lunar, we have the expertise to bring your vision to life, -ensuring a seamless, scalable, and high-performance solution tailored to your business needs. - -How We Can Help... - -#### 🔌 Custom Add-ons & Extensions - -Extend Lunar with bespoke functionality, from advanced product configurations to custom checkout experiences. - -#### 🔄 Integrations - -Seamlessly connect Lunar with third-party services, including payment gateways, ERP systems, and fulfilment providers. - -#### 🛍️ Full Storefront Builds - -Need a complete e-commerce solution? We design and develop fully customised storefronts, optimised for performance, SEO, -and conversions. - -#### 📦 Migration & Optimisation - -Moving from another platform? We can help migrate your data and optimise your store for speed and scalability. - -### Why Work With Us? - -- ✅ Built by the Lunar Team – Get direct access to the people who know Lunar best. -- ✅ Tailored Solutions – Custom development to match your exact business requirements. -- ✅ Scalable & Future-Proof – Solutions built with performance and long-term growth in mind. -- ✅ High-Quality Code – Following best practices to ensure maintainability and efficiency. - -### Let’s Build Together - -Tell us about your project, and we’ll help you create a custom Lunar-powered solution that fits your needs. - - diff --git a/docs/vercel.json b/docs/vercel.json deleted file mode 100644 index 88986d9d0e..0000000000 --- a/docs/vercel.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "redirects": [ - { - "source": "/lunar/:path(.*)", - "destination": "/core/reference/:path", - "statusCode": 301 - }, - { - "source": "/extending/:path(.*)", - "destination": "/core/extending/:path", - "statusCode": 301 - } - ] -} From 8972ebecd124771cbd7d0934c8bdf03eccefcb83 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 11 Nov 2025 17:10:04 +0000 Subject: [PATCH 20/50] Ensure shipping table json fields are set to jsonb (#2279) #2278 Removed the `jsonb` changes from shipping tables but didn't reapply them via the shipping addon. This PR mimics the core migration but within the shipping addon. ## Summary by CodeRabbit - Chores - Migrated shipping method data storage to a more efficient format on PostgreSQL, improving query performance and scalability. - Backward-compatible and reversible; no action required for non-PostgreSQL setups. - Ensures more consistent behavior and prepares the system for future enhancements. - Seamless upgrade path with automatic migration during update; no user-facing changes expected. --------- Co-authored-by: Glenn Jacobs Co-authored-by: Author --- packages/admin/resources/lang/hu/discount.php | 4 +-- .../resources/lang/hu/productvariant.php | 12 +++---- .../resources/lang/hu/relationmanagers.php | 22 ++++++------- packages/admin/resources/lang/hu/widgets.php | 2 +- .../admin/resources/lang/pt_BR/discount.php | 4 +-- packages/admin/resources/lang/pt_BR/order.php | 2 +- .../admin/resources/lang/tr/attribute.php | 2 +- .../resources/lang/tr/attributegroup.php | 2 +- packages/admin/resources/lang/tr/brand.php | 2 +- packages/admin/resources/lang/tr/channel.php | 2 +- packages/admin/resources/lang/tr/discount.php | 6 ++-- .../admin/resources/lang/tr/fieldtypes.php | 4 +-- packages/admin/resources/lang/tr/order.php | 6 ++-- packages/admin/resources/lang/tr/product.php | 2 +- .../admin/resources/lang/tr/productoption.php | 2 +- .../resources/lang/tr/productvariant.php | 6 ++-- .../resources/lang/tr/relationmanagers.php | 4 +-- packages/admin/resources/lang/tr/staff.php | 4 +-- ...0_remap_shipping_polymorphic_relations.php | 13 ++++---- ...e_customer_group_shipping_method_table.php | 4 +-- ...00000_switch_shipping_to_jsonb_columns.php | 33 +++++++++++++++++++ .../resources/lang/hu/shippingmethod.php | 4 +-- .../resources/lang/tr/relationmanagers.php | 10 +++--- .../Pages/EditShippingMethod.php | 1 + 24 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 packages/table-rate-shipping/database/migrations/2025_08_18_100000_switch_shipping_to_jsonb_columns.php diff --git a/packages/admin/resources/lang/hu/discount.php b/packages/admin/resources/lang/hu/discount.php index 8f3d606058..7dbd27e7a2 100644 --- a/packages/admin/resources/lang/hu/discount.php +++ b/packages/admin/resources/lang/hu/discount.php @@ -14,7 +14,7 @@ 'heading' => 'Összeg alapú kedvezmény', ], 'name' => [ - 'label' => 'Név', + 'label' => 'Név', ], 'handle' => [ 'label' => 'Azonosító', @@ -86,7 +86,7 @@ 'label' => 'Név', ], 'status' => [ - 'label' => 'Státusz', + 'label' => 'Státusz', \Lunar\Models\Discount::ACTIVE => [ 'label' => 'Aktív', ], diff --git a/packages/admin/resources/lang/hu/productvariant.php b/packages/admin/resources/lang/hu/productvariant.php index 0061433e4c..f1f171e80b 100644 --- a/packages/admin/resources/lang/hu/productvariant.php +++ b/packages/admin/resources/lang/hu/productvariant.php @@ -7,14 +7,14 @@ 'edit' => [ 'title' => 'Alapinformációk', ], - 'media' => [ - 'title' => 'Média', - 'form' => [ + 'media' => [ + 'title' => 'Média', + 'form' => [ 'no_selection' => [ 'label' => 'Ehhez a termékváltozathoz jelenleg nincs kép kiválasztva.', ], - 'no_media_available' => [ - 'label' => 'Jelenleg nincs elérhető média ehhez a termékhez.', + 'no_media_available' => [ + 'label' => 'Jelenleg nincs elérhető média ehhez a termékhez.', ], 'images' => [ 'label' => 'Elsődleges kép', @@ -34,7 +34,7 @@ ], 'form' => [ 'sku' => [ - 'label' => 'Cikkszám (SKU)', + 'label' => 'Cikkszám (SKU)', ], 'gtin' => [ 'label' => 'Globális kereskedelmi cikkszám (GTIN)', diff --git a/packages/admin/resources/lang/hu/relationmanagers.php b/packages/admin/resources/lang/hu/relationmanagers.php index 0dc8f432c9..1d6418ee91 100644 --- a/packages/admin/resources/lang/hu/relationmanagers.php +++ b/packages/admin/resources/lang/hu/relationmanagers.php @@ -88,8 +88,8 @@ ], ], 'medias' => [ - 'title' => 'Média', - 'title_plural' => 'Médiák', + 'title' => 'Média', + 'title_plural' => 'Médiák', 'actions' => [ 'attach' => [ 'label' => 'Média csatolása', @@ -133,8 +133,8 @@ 'variant_description' => 'Csatoljon termékképeket ehhez a változathoz', ], 'urls' => [ - 'title' => 'URL', - 'title_plural' => 'URL-ek', + 'title' => 'URL', + 'title_plural' => 'URL-ek', 'actions' => [ 'create' => [ 'label' => 'URL létrehozása', @@ -169,8 +169,8 @@ ], ], 'customer_group_pricing' => [ - 'title' => 'Vásárlói csoport árképzés', - 'title_plural' => 'Vásárlói csoport árképzés', + 'title' => 'Vásárlói csoport árképzés', + 'title_plural' => 'Vásárlói csoport árképzés', 'table' => [ 'heading' => 'Vásárlói csoport árképzés', 'description' => 'Ár társítása vásárlói csoportokhoz a termék árának meghatározásához.', @@ -180,8 +180,8 @@ ], 'actions' => [ 'create' => [ - 'label' => 'Vásárlói csoport ár hozzáadása', - 'modal' => [ + 'label' => 'Vásárlói csoport ár hozzáadása', + 'modal' => [ 'heading' => 'Vásárlói csoport ár létrehozása', ], ], @@ -189,9 +189,9 @@ ], ], 'pricing' => [ - 'title' => 'Árképzés', - 'title_plural' => 'Árképzés', - 'tab_name' => 'Ár lépcsők', + 'title' => 'Árképzés', + 'title_plural' => 'Árképzés', + 'tab_name' => 'Ár lépcsők', 'table' => [ 'heading' => 'Ár lépcsők', 'description' => 'Csökkentsd az árat, ha a vásárló nagyobb mennyiséget vásárol.', diff --git a/packages/admin/resources/lang/hu/widgets.php b/packages/admin/resources/lang/hu/widgets.php index aabaf51b09..040ff463dd 100644 --- a/packages/admin/resources/lang/hu/widgets.php +++ b/packages/admin/resources/lang/hu/widgets.php @@ -8,7 +8,7 @@ 'label' => 'Rendelések ma', 'increase' => ':percentage% növekedés a tegnapi (:count) értékhez képest', 'decrease' => ':percentage% csökkenés a tegnapi (:count) értékhez képest', - 'neutral' => 'Nincs változás tegnaphoz képest', + 'neutral' => 'Nincs változás tegnaphoz képest', ], 'stat_two' => [ 'label' => 'Rendelések az elmúlt 7 napban', diff --git a/packages/admin/resources/lang/pt_BR/discount.php b/packages/admin/resources/lang/pt_BR/discount.php index 1c550ba5de..a81dd3c074 100644 --- a/packages/admin/resources/lang/pt_BR/discount.php +++ b/packages/admin/resources/lang/pt_BR/discount.php @@ -340,10 +340,10 @@ 'type' => [ 'options' => [ 'limitation' => [ - 'label' => 'Limitação', + 'label' => 'Limitação', ], 'exclusion' => [ - 'label' => 'Exclusão', + 'label' => 'Exclusão', ], ], ], diff --git a/packages/admin/resources/lang/pt_BR/order.php b/packages/admin/resources/lang/pt_BR/order.php index 5603233995..bffdfa9897 100644 --- a/packages/admin/resources/lang/pt_BR/order.php +++ b/packages/admin/resources/lang/pt_BR/order.php @@ -272,9 +272,9 @@ 'shipping_address' => [ 'saved' => 'Endereço de entrega salvo', + ], ], ], - ], 'edit_tags' => [ 'label' => 'Editar', 'form' => [ diff --git a/packages/admin/resources/lang/tr/attribute.php b/packages/admin/resources/lang/tr/attribute.php index 5d82aac076..ad46c19cfc 100644 --- a/packages/admin/resources/lang/tr/attribute.php +++ b/packages/admin/resources/lang/tr/attribute.php @@ -30,7 +30,7 @@ ], 'description' => [ 'label' => 'Açıklama', - 'helper' => 'Alan altındaki yardım metnini görüntülemek için kullanın' + 'helper' => 'Alan altındaki yardım metnini görüntülemek için kullanın', ], 'handle' => [ 'label' => 'Tanımlayıcı', diff --git a/packages/admin/resources/lang/tr/attributegroup.php b/packages/admin/resources/lang/tr/attributegroup.php index 89d5e3fda9..8e1303d1b6 100644 --- a/packages/admin/resources/lang/tr/attributegroup.php +++ b/packages/admin/resources/lang/tr/attributegroup.php @@ -43,4 +43,4 @@ ], ], ], -]; \ No newline at end of file +]; diff --git a/packages/admin/resources/lang/tr/brand.php b/packages/admin/resources/lang/tr/brand.php index e2a479b423..8aa75266e5 100644 --- a/packages/admin/resources/lang/tr/brand.php +++ b/packages/admin/resources/lang/tr/brand.php @@ -72,4 +72,4 @@ ], ], -]; \ No newline at end of file +]; diff --git a/packages/admin/resources/lang/tr/channel.php b/packages/admin/resources/lang/tr/channel.php index ff56f1126a..7f2b6deb91 100644 --- a/packages/admin/resources/lang/tr/channel.php +++ b/packages/admin/resources/lang/tr/channel.php @@ -36,4 +36,4 @@ ], ], -]; \ No newline at end of file +]; diff --git a/packages/admin/resources/lang/tr/discount.php b/packages/admin/resources/lang/tr/discount.php index 6533eae417..eee14df15c 100644 --- a/packages/admin/resources/lang/tr/discount.php +++ b/packages/admin/resources/lang/tr/discount.php @@ -11,7 +11,7 @@ 'heading' => 'X Al Y Kazan', ], 'amount_off' => [ - 'heading' => 'Sabit Tutar İndirimi' + 'heading' => 'Sabit Tutar İndirimi', ], 'name' => [ 'label' => 'İndirim Adı', @@ -60,7 +60,7 @@ ], 'min_qty' => [ 'label' => 'Ürün Miktarı', - 'helper_text' => 'İndirimin uygulanması için gerekli ürün adedini belirleyin.' + 'helper_text' => 'İndirimin uygulanması için gerekli ürün adedini belirleyin.', ], 'reward_qty' => [ 'label' => 'Ücretsiz ürün sayısı', @@ -118,7 +118,7 @@ ], 'pages' => [ 'availability' => [ - 'label' => 'Erişilebilirlik' + 'label' => 'Erişilebilirlik', ], 'edit' => [ 'title' => 'Temel Bilgiler', diff --git a/packages/admin/resources/lang/tr/fieldtypes.php b/packages/admin/resources/lang/tr/fieldtypes.php index 4f5f8f3367..794bc18f1f 100644 --- a/packages/admin/resources/lang/tr/fieldtypes.php +++ b/packages/admin/resources/lang/tr/fieldtypes.php @@ -4,7 +4,7 @@ 'dropdown' => [ 'label' => 'Açılır Menü', 'form' => [ - 'lookups' => [ + 'lookups' => [ 'label' => 'Arama Tablosu', 'key_label' => 'Etiket', 'value_label' => 'Değer', @@ -32,7 +32,7 @@ ], ], 'toggle' => [ - 'label' => 'Aç/Kapat' + 'label' => 'Aç/Kapat', ], 'youtube' => [ 'label' => 'YouTube', diff --git a/packages/admin/resources/lang/tr/order.php b/packages/admin/resources/lang/tr/order.php index 56b05497d3..3cc8cf37aa 100644 --- a/packages/admin/resources/lang/tr/order.php +++ b/packages/admin/resources/lang/tr/order.php @@ -57,10 +57,10 @@ 'label' => 'Müşteri Türü', ], 'placed_after' => [ - 'label' => 'Bu Tarihten Sonra' + 'label' => 'Bu Tarihten Sonra', ], 'placed_before' => [ - 'label' => 'Bu Tarihten Önce' + 'label' => 'Bu Tarihten Önce', ], ], @@ -120,7 +120,7 @@ 'label' => 'Miktar', 'hint' => [ - 'less_than_total' => "Toplam işlem tutarından daha az bir miktar tahsil etmek üzeresiniz" + 'less_than_total' => 'Toplam işlem tutarından daha az bir miktar tahsil etmek üzeresiniz', ], ], diff --git a/packages/admin/resources/lang/tr/product.php b/packages/admin/resources/lang/tr/product.php index 93d8780359..ab2458b542 100644 --- a/packages/admin/resources/lang/tr/product.php +++ b/packages/admin/resources/lang/tr/product.php @@ -91,7 +91,7 @@ 'pages' => [ 'availability' => [ - 'label' => 'Erişilebilirlik' + 'label' => 'Erişilebilirlik', ], 'edit' => [ 'title' => 'Temel Bilgiler', diff --git a/packages/admin/resources/lang/tr/productoption.php b/packages/admin/resources/lang/tr/productoption.php index 1b58f42cf7..dd0064e517 100644 --- a/packages/admin/resources/lang/tr/productoption.php +++ b/packages/admin/resources/lang/tr/productoption.php @@ -56,7 +56,7 @@ 'label' => 'Ürün Seçeneği', ], 'no_shared_components' => [ - 'label' => 'Mevcut paylaşılan seçenek yok.' + 'label' => 'Mevcut paylaşılan seçenek yok.', ], 'preselect' => [ 'label' => 'Varsayılan olarak tüm değerleri önceden seç.', diff --git a/packages/admin/resources/lang/tr/productvariant.php b/packages/admin/resources/lang/tr/productvariant.php index 39db0dcee2..d4b4d6ee3d 100644 --- a/packages/admin/resources/lang/tr/productvariant.php +++ b/packages/admin/resources/lang/tr/productvariant.php @@ -49,14 +49,14 @@ 'label' => 'Stokta', ], 'backorder' => [ - 'label' => 'Sipariş Üzerine' + 'label' => 'Sipariş Üzerine', ], 'purchasable' => [ 'label' => 'Satın Alınabilirlik', 'options' => [ 'always' => 'Her Zaman', 'in_stock' => 'Stokta', - 'in_stock_or_on_backorder' => 'Stokta veya Sipariş Üzerine' + 'in_stock_or_on_backorder' => 'Stokta veya Sipariş Üzerine', ], ], 'unit_quantity' => [ @@ -102,4 +102,4 @@ 'label' => 'Ağırlık Birimi', ], ], -]; \ No newline at end of file +]; diff --git a/packages/admin/resources/lang/tr/relationmanagers.php b/packages/admin/resources/lang/tr/relationmanagers.php index d6747686a7..afc80b551c 100644 --- a/packages/admin/resources/lang/tr/relationmanagers.php +++ b/packages/admin/resources/lang/tr/relationmanagers.php @@ -240,7 +240,7 @@ ], 'compare_price' => [ 'label' => 'Karşılaştırma Fiyatı', - 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).' + 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).', ], 'basePrices' => [ 'title' => 'Fiyatlar', @@ -252,7 +252,7 @@ ], 'compare_price' => [ 'label' => 'Karşılaştırma Fiyatı', - 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).' + 'helper_text' => 'Satın alma fiyatıyla karşılaştırma için orijinal fiyat veya tavsiye edilen satış fiyat (RRP).', ], ], 'tooltip' => 'Döviz kurlarına göre otomatik olarak oluşturuldu.', diff --git a/packages/admin/resources/lang/tr/staff.php b/packages/admin/resources/lang/tr/staff.php index 6d7c3691db..33dc21374c 100644 --- a/packages/admin/resources/lang/tr/staff.php +++ b/packages/admin/resources/lang/tr/staff.php @@ -37,7 +37,7 @@ ], 'admin' => [ 'label' => 'Süper Yönetici', - 'helper' => 'Süper yönetici rolleri yönetim panelinde değiştirilemez.' + 'helper' => 'Süper yönetici rolleri yönetim panelinde değiştirilemez.', ], 'roles' => [ 'label' => 'Roller', @@ -78,4 +78,4 @@ ], ], -]; \ No newline at end of file +]; diff --git a/packages/table-rate-shipping/database/migrations/2024_03_19_100000_remap_shipping_polymorphic_relations.php b/packages/table-rate-shipping/database/migrations/2024_03_19_100000_remap_shipping_polymorphic_relations.php index 2bcae006b4..6716355ade 100644 --- a/packages/table-rate-shipping/database/migrations/2024_03_19_100000_remap_shipping_polymorphic_relations.php +++ b/packages/table-rate-shipping/database/migrations/2024_03_19_100000_remap_shipping_polymorphic_relations.php @@ -1,18 +1,19 @@ prefix.'prices') + DB::table($this->prefix.'prices') ->where('priceable_type', '=', \Lunar\Shipping\Models\ShippingRate::class) ->update([ 'priceable_type' => 'shipping_rate', ]); - \Illuminate\Support\Facades\DB::table($this->prefix.'shipping_exclusions') + DB::table($this->prefix.'shipping_exclusions') ->where('purchasable_type', '=', \Lunar\Models\Product::class) ->update([ 'purchasable_type' => 'product', @@ -21,16 +22,16 @@ public function up() public function down() { - \Illuminate\Support\Facades\DB::table($this->prefix.'prices') + DB::table($this->prefix.'prices') ->where('priceable_type', '=', 'shipping_rate') ->update([ 'priceable_type' => \Lunar\Shipping\Models\ShippingRate::class, ]); - \Illuminate\Support\Facades\DB::table($this->prefix.'shipping_exclusions') + DB::table($this->prefix.'shipping_exclusions') ->where('purchasable_type', '=', 'product') ->update([ 'purchasable_type' => \Lunar\Models\Product::class, ]); } -} +}; diff --git a/packages/table-rate-shipping/database/migrations/2024_06_28_100000_create_customer_group_shipping_method_table.php b/packages/table-rate-shipping/database/migrations/2024_06_28_100000_create_customer_group_shipping_method_table.php index 66bb1186c9..8ed215f29a 100644 --- a/packages/table-rate-shipping/database/migrations/2024_06_28_100000_create_customer_group_shipping_method_table.php +++ b/packages/table-rate-shipping/database/migrations/2024_06_28_100000_create_customer_group_shipping_method_table.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema; use Lunar\Base\Migration; -class CreateCustomerGroupShippingMethodTable extends Migration +return new class extends Migration { public function up() { @@ -26,4 +26,4 @@ public function down() { Schema::dropIfExists($this->prefix.'customer_group_shipping_method'); } -} +}; diff --git a/packages/table-rate-shipping/database/migrations/2025_08_18_100000_switch_shipping_to_jsonb_columns.php b/packages/table-rate-shipping/database/migrations/2025_08_18_100000_switch_shipping_to_jsonb_columns.php new file mode 100644 index 0000000000..6f49544b57 --- /dev/null +++ b/packages/table-rate-shipping/database/migrations/2025_08_18_100000_switch_shipping_to_jsonb_columns.php @@ -0,0 +1,33 @@ +prefix.'shipping_methods', function (Blueprint $table) { + $table->jsonb('data')->nullable()->change(); + }); + } + + public function down() + { + // Only run for PostgreSQL + if (DB::getDriverName() !== 'pgsql') { + return; + } + + Schema::table($this->prefix.'shipping_methods', function (Blueprint $table) { + $table->json('data')->nullable()->change(); + }); + } +}; diff --git a/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php b/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php index 612ce2c1df..92df485f40 100644 --- a/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php +++ b/packages/table-rate-shipping/resources/lang/hu/shippingmethod.php @@ -26,8 +26,8 @@ 'driver' => [ 'label' => 'Típus', 'options' => [ - 'ship-by' => 'Házhozszállítás', - 'collection'=> 'Személyes átvétel', + 'ship-by' => 'Házhozszállítás', + 'collection' => 'Személyes átvétel', ], ], 'stock_available' => [ diff --git a/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php b/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php index 16ca24080d..2c483f4bfc 100644 --- a/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php +++ b/packages/table-rate-shipping/resources/lang/tr/relationmanagers.php @@ -3,14 +3,14 @@ return [ 'shipping_methods' => [ 'customer_groups' => [ - 'description' => "Müşteri gruplarını bu kargo yöntemiyle ilişkilendirerek kullanılabilirliğini belirleyin.", + 'description' => 'Müşteri gruplarını bu kargo yöntemiyle ilişkilendirerek kullanılabilirliğini belirleyin.', ], ], 'shipping_rates' => [ 'title_plural' => 'Kargo Tarifeleri', 'actions' => [ 'create' => [ - 'label' => 'Kargo Tarifesi Oluştur' + 'label' => 'Kargo Tarifesi Oluştur', ], ], 'notices' => [ @@ -54,7 +54,7 @@ 'label' => 'Fiyat', ], 'price_breaks_count' => [ - 'label' => 'Fiyat Aralıkları' + 'label' => 'Fiyat Aralıkları', ], ], ], @@ -67,10 +67,10 @@ ], 'actions' => [ 'create' => [ - 'label' => 'Kargo istisna listesi ekle' + 'label' => 'Kargo istisna listesi ekle', ], 'attach' => [ - 'label' => 'İstisna listesi ekle' + 'label' => 'İstisna listesi ekle', ], 'detach' => [ 'label' => 'Kaldır', diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php index ce9c463a76..957497254a 100644 --- a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php +++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php @@ -23,6 +23,7 @@ public static function getNavigationLabel(): string 'label' => __('lunarpanel.shipping::shippingmethod.label'), ]); } + protected function getRedirectUrl(): string { return $this->getResource()::getUrl('index'); From 9657278ecc86f92dd266bfa1ea9c607d82703c14 Mon Sep 17 00:00:00 2001 From: wychoong <67364036+wychoong@users.noreply.github.com> Date: Wed, 12 Nov 2025 01:26:28 +0800 Subject: [PATCH 21/50] Delete product inverseAssociations (#2280) ## Summary by CodeRabbit * **Bug Fixes** * Permanently deleting a product now removes both direct and reverse product relationships, ensuring no lingering links appear on related products. * Improves data integrity by fully clearing associated relationships during force delete, preventing inconsistent or orphaned connections. * Soft-delete behavior is unchanged. Co-authored-by: Alec Ritson Co-authored-by: Glenn Jacobs --- packages/core/src/Observers/ProductObserver.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/Observers/ProductObserver.php b/packages/core/src/Observers/ProductObserver.php index 18163224c3..cd98d6edd6 100644 --- a/packages/core/src/Observers/ProductObserver.php +++ b/packages/core/src/Observers/ProductObserver.php @@ -24,6 +24,8 @@ public function deleting(ProductContract $product): void $product->associations()->delete(); + $product->inverseAssociations()->delete(); + $product->channels()->detach(); $product->tags()->detach(); From e41a128b27279464b72348182efbb13a778eb9e2 Mon Sep 17 00:00:00 2001 From: Jakub Theimer <5587309+repl6669@users.noreply.github.com> Date: Tue, 11 Nov 2025 18:40:20 +0100 Subject: [PATCH 22/50] Fix product status color matching in products table (#2292) If we were to use a different status than `published | draft | deleted`, we get an exception in products table. This PR just adds `default` status color handling. ## Summary by CodeRabbit * **Bug Fixes** * Product status badges now display a consistent default color when an unrecognized status appears, improving readability and visual consistency in the products table. * Previously, only certain statuses had defined colors, which could lead to unexpected or missing styling for others. * No other user-facing behavior changes. Co-authored-by: Jakub Theimer <5587309+theimerj@users.noreply.github.com> Co-authored-by: Alec Ritson Co-authored-by: Glenn Jacobs --- packages/admin/src/Filament/Resources/ProductResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/admin/src/Filament/Resources/ProductResource.php b/packages/admin/src/Filament/Resources/ProductResource.php index f5d6b1202c..0725483236 100644 --- a/packages/admin/src/Filament/Resources/ProductResource.php +++ b/packages/admin/src/Filament/Resources/ProductResource.php @@ -271,6 +271,7 @@ public static function getTableColumns(): array 'draft' => 'warning', 'published' => 'success', 'deleted' => 'danger', + default => 'primary', }), SpatieMediaLibraryImageColumn::make('thumbnail') ->collection(config('lunar.media.collection')) From b545f784eacce642ec1cadec9d8aef0f4e48f101 Mon Sep 17 00:00:00 2001 From: Christian Widlund Date: Tue, 11 Nov 2025 18:40:45 +0100 Subject: [PATCH 23/50] :bug: fix(console): switch to components->info() (#2283) components->line requires two arguments in Laravel 12, causing an ArgumentCountError with a single string. Using components->info preserves formatting and restores compatibility. The $this->line helper has a different API and is not interchangeable with components->line. --------- Co-authored-by: Glenn Jacobs --- packages/core/src/Addons/Manifest.php | 2 +- packages/core/src/Console/Commands/AddonsDiscover.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/Addons/Manifest.php b/packages/core/src/Addons/Manifest.php index 59ed478cc0..fde1ae96d2 100644 --- a/packages/core/src/Addons/Manifest.php +++ b/packages/core/src/Addons/Manifest.php @@ -70,7 +70,7 @@ protected function formatPackage($package) 'id' => base64_encode($package['name']), 'slug' => $lunar['slug'] ?? null, 'editions' => $lunar['editions'] ?? [], - 'github_url' => $package['source']['url'], + 'github_url' => $package['source']['url'] ?? '', 'version' => $package['version'], 'namespace' => $namespace, 'autoload' => $autoload, diff --git a/packages/core/src/Console/Commands/AddonsDiscover.php b/packages/core/src/Console/Commands/AddonsDiscover.php index 72d2fef6e5..b4fc510e70 100644 --- a/packages/core/src/Console/Commands/AddonsDiscover.php +++ b/packages/core/src/Console/Commands/AddonsDiscover.php @@ -29,7 +29,7 @@ public function handle(Manifest $manifest) $manifest->build(); foreach (array_keys($manifest->manifest) as $package) { - $this->components->line("Discovered Addon: {$package}"); + $this->components->info("Discovered Addon: {$package}"); } $this->components->info('Addon manifest generated successfully.'); From 0a4941f386148ebe51590a05157250f73ba4d00d Mon Sep 17 00:00:00 2001 From: jargoud Date: Tue, 11 Nov 2025 18:42:33 +0100 Subject: [PATCH 24/50] Handle events after transaction commit (#2325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull requests intends to solve issue #2324. Co-authored-by: Jérémy ARGOUD --- packages/core/src/Observers/PriceObserver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/Observers/PriceObserver.php b/packages/core/src/Observers/PriceObserver.php index 426cecb2fc..8a6590f423 100644 --- a/packages/core/src/Observers/PriceObserver.php +++ b/packages/core/src/Observers/PriceObserver.php @@ -2,10 +2,11 @@ namespace Lunar\Observers; +use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; use Lunar\Jobs\Currencies\SyncPriceCurrencies; use Lunar\Models\Contracts\Price; -class PriceObserver +class PriceObserver implements ShouldHandleEventsAfterCommit { public function created(Price $price): void { From c8d464f1f7261be361f19a6f2b1c176a159e09f7 Mon Sep 17 00:00:00 2001 From: "Adrian P. Blunier" Date: Tue, 11 Nov 2025 19:01:44 +0100 Subject: [PATCH 25/50] Fix: Prevents fatal errors for custom Purchasable models (#2306) This PR improves support for custom models that implement the Purchasable interface but are not standard ProductVariants. Currently, using a custom purchasable can lead to two exceptions: 1. DiscountManager error: It assumes the purchasable always has a ->product->collections relationship, causing a null pointer exception if it doesn't. 2. Admin Panel error: The order details view assumes the purchasable has a getOptions() method, which is specific to ProductVariant. This PR resolves these issues with minimal impact: 1. The DiscountManager call chain is made null-safe to gracefully handle purchasables without product collections. 2. The Purchasable interface now includes getOptions(). This makes the Purchasable interface more robust and truly extensible as intended. Any feedback is welcome. --------- Co-authored-by: Glenn Jacobs --- packages/core/src/Base/Purchasable.php | 7 +++++++ packages/core/src/DataTypes/ShippingOption.php | 14 +++++++++++++- packages/core/src/Managers/DiscountManager.php | 2 +- tests/core/Stubs/TestPurchasable.php | 9 ++++++++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Base/Purchasable.php b/packages/core/src/Base/Purchasable.php index 5256c38ef2..dcd9cc8c3c 100644 --- a/packages/core/src/Base/Purchasable.php +++ b/packages/core/src/Base/Purchasable.php @@ -52,6 +52,13 @@ public function getDescription(); */ public function getOption(); + /** + * Return the options for this purchasable. + * + * @return \Illuminate\Support\Collection + */ + public function getOptions(): Collection; + /** * Return a unique string which identifies the purchasable item. * diff --git a/packages/core/src/DataTypes/ShippingOption.php b/packages/core/src/DataTypes/ShippingOption.php index 578d6e4968..474422f2a8 100644 --- a/packages/core/src/DataTypes/ShippingOption.php +++ b/packages/core/src/DataTypes/ShippingOption.php @@ -17,7 +17,7 @@ public function __construct( public ?string $taxReference = null, public ?string $option = null, public bool $collect = false, - public ?array $meta = null + public ?array $meta = null, ) { // .. } @@ -108,6 +108,18 @@ public function getOption() return $this->option; } + /** + * Return the options for this purchasable + * + * @return Collection + */ + public function getOptions(): Collection + { + return collect([ + $this->option, + ]); + } + /** * Return a unique string which identifies the purchasable item. * diff --git a/packages/core/src/Managers/DiscountManager.php b/packages/core/src/Managers/DiscountManager.php index 53273cad72..aa2509feae 100644 --- a/packages/core/src/Managers/DiscountManager.php +++ b/packages/core/src/Managers/DiscountManager.php @@ -156,7 +156,7 @@ function ($query, $value) { ) ) ->orWhere(fn ($query) => $query->collections( - $value->lines->map(fn ($line) => $line->purchasable->product->collections->pluck('id'))->flatten()->filter()->values(), + $value->lines->map(fn ($line) => $line->purchasable?->product?->collections?->pluck('id'))->flatten()->filter()->values(), ['condition'] ) ); diff --git a/tests/core/Stubs/TestPurchasable.php b/tests/core/Stubs/TestPurchasable.php index 8da4f8f8ae..080f4a6711 100644 --- a/tests/core/Stubs/TestPurchasable.php +++ b/tests/core/Stubs/TestPurchasable.php @@ -19,7 +19,7 @@ public function __construct( public $taxReference = null, public $option = null, public bool $collect = false, - public $meta = null + public $meta = null, ) { // .. } @@ -110,6 +110,13 @@ public function getOption() return $this->option; } + public function getOptions(): Collection + { + return collect([ + $this->option, + ]); + } + /** * Return a unique string which identifies the purchasable item. * From a09de92ab5420d668dc6726838fe64638cdb0940 Mon Sep 17 00:00:00 2001 From: shanebrightbulb <51361356+shanebrightbulb@users.noreply.github.com> Date: Tue, 11 Nov 2025 18:12:39 +0000 Subject: [PATCH 26/50] Fix admin activity log feed rendering (#2314) This PR fixes 2 issues when rendering the 'Activity Log' component on the order screen in the admin. The first issue is visible when using Filament Notifications. The z-index of the comment form in the activity log is set to 20 which brings it in front of the Filament notifications sidebar when it is opened. The second issue is when using dark mode. The date heading in the activity log is using the 'text-gray-300' Tailwind class, however this class isn't generated so the 'date' heading in the activity log is black and cannot be read. --------- Co-authored-by: Glenn Jacobs --- packages/admin/resources/dist/lunar-panel.css | 2 +- .../livewire/components/activity-log-feed.blade.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/admin/resources/dist/lunar-panel.css b/packages/admin/resources/dist/lunar-panel.css index 2753bbbe4b..e6255d20ba 100644 --- a/packages/admin/resources/dist/lunar-panel.css +++ b/packages/admin/resources/dist/lunar-panel.css @@ -1 +1 @@ -/*! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='rgba(var(--gray-500), var(--tw-stroke-opacity, 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.-left-\[calc\(0\.5rem_-_1px\)\]{left:calc(-.5rem - -1px)}.-left-\[calc\(0\.75rem_-_1px\)\]{left:calc(-.75rem - -1px)}.left-0{left:0}.left-5{left:1.25rem}.right-0{right:0}.top-\[2px\]{top:2px}.-my-8{margin-top:-2rem;margin-bottom:-2rem}.-ml-\[5px\]{margin-left:-5px}.-mt-3\.5{margin-top:-.875rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.ml-7{margin-left:1.75rem}.ml-8{margin-left:2rem}.mt-4{margin-top:1rem}.flow-root{display:flow-root}.w-1\/3{width:33.333333%}.w-\[2px\]{width:2px}.min-w-\[50vw\]{min-width:50vw}.min-w-full{min-width:100%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-y-px{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\!cursor-default{cursor:default!important}.cursor-grab{cursor:grab}.scroll-mt-32{scroll-margin-top:8rem}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.place-content-center{place-content:center}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.gap-2\.5{gap:.625rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.divide-y-2>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(2px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(2px*var(--tw-divide-y-reverse))}.divide-gray-950\/10>:not([hidden])~:not([hidden]){border-color:rgba(var(--gray-950),.1)}.\!overflow-auto{overflow:auto!important}.\!rounded-full{border-radius:9999px!important}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.\!border-red-300{--tw-border-opacity:1!important;border-color:rgb(252 165 165/var(--tw-border-opacity))!important}.\!border-red-500{--tw-border-opacity:1!important;border-color:rgb(239 68 68/var(--tw-border-opacity))!important}.border-green-300{--tw-border-opacity:1;border-color:rgb(134 239 172/var(--tw-border-opacity))}.border-orange-300{--tw-border-opacity:1;border-color:rgb(253 186 116/var(--tw-border-opacity))}.border-sky-300{--tw-border-opacity:1;border-color:rgb(125 211 252/var(--tw-border-opacity))}.border-white\/10{border-color:#ffffff1a}.\!bg-red-50{--tw-bg-opacity:1!important;background-color:rgb(254 242 242/var(--tw-bg-opacity))!important}.bg-gray-300\/20{background-color:rgba(var(--gray-300),.2)}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity))}.bg-sky-50{--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity))}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-teal-500{--tw-bg-opacity:1;background-color:rgb(20 184 166/var(--tw-bg-opacity))}.bg-white\/70{background-color:#ffffffb3}.\!p-0{padding:0!important}.\!p-3{padding:.75rem!important}.\!ps-6{padding-inline-start:1.5rem!important}.pl-2{padding-left:.5rem}.pl-8{padding-left:2rem}.pt-8{padding-top:2rem}.pt-\[1px\]{padding-top:1px}.pt-\[5px\]{padding-top:5px}.uppercase{text-transform:uppercase}.\!text-red-400\/60{color:#f8717199!important}.\!text-red-400\/80{color:#f87171cc!important}.\!text-red-600{--tw-text-opacity:1!important;color:rgb(220 38 38/var(--tw-text-opacity))!important}.text-gray-900{--tw-text-opacity:1;color:rgba(var(--gray-900),var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-orange-500{--tw-text-opacity:1;color:rgb(249 115 22/var(--tw-text-opacity))}.text-orange-600{--tw-text-opacity:1;color:rgb(234 88 12/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-sky-600{--tw-text-opacity:1;color:rgb(2 132 199/var(--tw-text-opacity))}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.ring-1,.ring-4{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-gray-100{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-100),var(--tw-ring-opacity))}.ring-purple-100{--tw-ring-opacity:1;--tw-ring-color:rgb(243 232 255/var(--tw-ring-opacity))}.ring-sky-100{--tw-ring-opacity:1;--tw-ring-color:rgb(224 242 254/var(--tw-ring-opacity))}.ring-teal-100{--tw-ring-opacity:1;--tw-ring-color:rgb(204 251 241/var(--tw-ring-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:bg-danger-100\/80:hover{background-color:rgba(var(--danger-100),.8)}.hover\:bg-primary-50:hover{--tw-bg-opacity:1;background-color:rgba(var(--primary-50),var(--tw-bg-opacity))}.hover\:bg-primary-50\/50:hover{background-color:rgba(var(--primary-50),.5)}.hover\:underline:hover{text-decoration-line:underline}.group:hover .group-hover\:flex{display:flex}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/button:hover .group-hover\/button\:text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity))}.group:hover .group-hover\:text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.group:hover .group-hover\:text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}@media (min-width:768px){.md\:min-w-\[32rem\]{min-width:32rem}}:is(:where([dir=ltr]) .ltr\:rotate-90){--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(:where([dir=rtl]) .rtl\:\!rotate-90){--tw-rotate:90deg!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}:is(:where([dir=rtl]) .rtl\:rotate-180){--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(:where([dir=rtl]) .rtl\:space-x-reverse)>:not([hidden])~:not([hidden]){--tw-space-x-reverse:1}:is(:where([dir=rtl]) .rtl\:text-right){text-align:right}:is(:where(.dark) .dark\:divide-gray-600)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgba(var(--gray-600),var(--tw-divide-opacity))}:is(:where(.dark) .dark\:divide-white\/10)>:not([hidden])~:not([hidden]){border-color:#ffffff1a}:is(:where(.dark) .dark\:divide-white\/5)>:not([hidden])~:not([hidden]){border-color:#ffffff0d}:is(:where(.dark) .dark\:border-b){border-bottom-width:1px}:is(:where(.dark) .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity))}:is(:where(.dark) .dark\:border-white\/10){border-color:#ffffff1a}:is(:where(.dark) .dark\:border-t-white\/10){border-top-color:#ffffff1a}:is(:where(.dark) .dark\:bg-gray-600){--tw-bg-opacity:1;background-color:rgba(var(--gray-600),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-green-400\/10){background-color:#4ade801a}:is(:where(.dark) .dark\:bg-orange-400\/10){background-color:#fb923c1a}:is(:where(.dark) .dark\:bg-sky-400\/10){background-color:#38bdf81a}:is(:where(.dark) .dark\:bg-white\/10){background-color:#ffffff1a}:is(:where(.dark) .dark\:bg-white\/5){background-color:#ffffff0d}:is(:where(.dark) .dark\:\!text-red-400\/60){color:#f8717199!important}:is(:where(.dark) .dark\:text-gray-100){--tw-text-opacity:1;color:rgba(var(--gray-100),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-200){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-300){--tw-text-opacity:1;color:rgba(var(--gray-300),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-500){--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-green-400\/80){color:#4ade80cc}:is(:where(.dark) .dark\:text-orange-400){--tw-text-opacity:1;color:rgb(251 146 60/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-primary-400){--tw-text-opacity:1;color:rgba(var(--primary-400),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-primary-400\/80){color:rgba(var(--primary-400),.8)}:is(:where(.dark) .dark\:text-red-400\/80){color:#f87171cc}:is(:where(.dark) .dark\:text-sky-400){--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(:where(.dark) .dark\:ring-gray-600){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-600),var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-gray-700){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-700),var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-purple-800){--tw-ring-opacity:1;--tw-ring-color:rgb(107 33 168/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-sky-800){--tw-ring-opacity:1;--tw-ring-color:rgb(7 89 133/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-teal-800){--tw-ring-opacity:1;--tw-ring-color:rgb(17 94 89/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-white\/10){--tw-ring-color:#ffffff1a}:is(:where(.dark) .dark\:ring-white\/20){--tw-ring-color:#fff3}:is(:where(.dark) .dark\:hover\:bg-danger-300\/20:hover){background-color:rgba(var(--danger-300),.2)}:is(:where(.dark) .dark\:hover\:bg-white\/5:hover){background-color:#ffffff0d}:is(:where(.dark) .dark\:focus-visible\:ring-primary-500:focus-visible){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity))}:is(:where(.dark) .group\/button:hover .dark\:group-hover\/button\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(:where(.dark) .group:hover .dark\:group-hover\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(:where(.dark) .group:hover .dark\:group-hover\:text-red-400){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.\[\&_table\]\:h-\[1px\] table{height:1px} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}.-left-\[calc\(0\.5rem_-_1px\)\]{left:calc(-.5rem - -1px)}.-left-\[calc\(0\.75rem_-_1px\)\]{left:calc(-.75rem - -1px)}.left-0{left:0}.left-5{left:1.25rem}.right-0{right:0}.top-\[2px\]{top:2px}.-my-8{margin-top:-2rem;margin-bottom:-2rem}.-ml-\[5px\]{margin-left:-5px}.-mt-3\.5{margin-top:-.875rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.ml-7{margin-left:1.75rem}.ml-8{margin-left:2rem}.mt-4{margin-top:1rem}.flow-root{display:flow-root}.w-1\/3{width:33.333333%}.w-\[2px\]{width:2px}.min-w-\[50vw\]{min-width:50vw}.min-w-full{min-width:100%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-y-px{--tw-translate-y:-1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\!cursor-default{cursor:default!important}.cursor-grab{cursor:grab}.scroll-mt-32{scroll-margin-top:8rem}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.place-content-center{place-content:center}.gap-0\.5{gap:.125rem}.gap-2\.5{gap:.625rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.divide-y-2>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(2px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(2px*var(--tw-divide-y-reverse))}.divide-gray-950\/10>:not([hidden])~:not([hidden]){border-color:rgba(var(--gray-950),.1)}.\!overflow-auto{overflow:auto!important}.\!rounded-full{border-radius:9999px!important}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.\!border-red-300{--tw-border-opacity:1!important;border-color:rgb(252 165 165/var(--tw-border-opacity,1))!important}.\!border-red-500{--tw-border-opacity:1!important;border-color:rgb(239 68 68/var(--tw-border-opacity,1))!important}.border-green-300{--tw-border-opacity:1;border-color:rgb(134 239 172/var(--tw-border-opacity,1))}.border-orange-300{--tw-border-opacity:1;border-color:rgb(253 186 116/var(--tw-border-opacity,1))}.border-sky-300{--tw-border-opacity:1;border-color:rgb(125 211 252/var(--tw-border-opacity,1))}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.\!bg-red-50{--tw-bg-opacity:1!important;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))!important}.bg-gray-300\/20{background-color:rgba(var(--gray-300),.2)}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity,1))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-sky-50{--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity,1))}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity,1))}.bg-teal-500{--tw-bg-opacity:1;background-color:rgb(20 184 166/var(--tw-bg-opacity,1))}.bg-white\/70{background-color:hsla(0,0%,100%,.7)}.\!p-0{padding:0!important}.\!p-3{padding:.75rem!important}.\!ps-6{padding-inline-start:1.5rem!important}.pl-2{padding-left:.5rem}.pl-8{padding-left:2rem}.pt-8{padding-top:2rem}.pt-\[1px\]{padding-top:1px}.pt-\[5px\]{padding-top:5px}.uppercase{text-transform:uppercase}.\!text-red-400\/60{color:hsla(0,91%,71%,.6)!important}.\!text-red-400\/80{color:hsla(0,91%,71%,.8)!important}.\!text-red-600{--tw-text-opacity:1!important;color:rgb(220 38 38/var(--tw-text-opacity,1))!important}.text-gray-900{--tw-text-opacity:1;color:rgba(var(--gray-900),var(--tw-text-opacity,1))}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-orange-500{--tw-text-opacity:1;color:rgb(249 115 22/var(--tw-text-opacity,1))}.text-orange-600{--tw-text-opacity:1;color:rgb(234 88 12/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-sky-600{--tw-text-opacity:1;color:rgb(2 132 199/var(--tw-text-opacity,1))}.shadow,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.ring-1,.ring-4{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-gray-100{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-100),var(--tw-ring-opacity,1))}.ring-purple-100{--tw-ring-opacity:1;--tw-ring-color:rgb(243 232 255/var(--tw-ring-opacity,1))}.ring-sky-100{--tw-ring-opacity:1;--tw-ring-color:rgb(224 242 254/var(--tw-ring-opacity,1))}.ring-teal-100{--tw-ring-opacity:1;--tw-ring-color:rgb(204 251 241/var(--tw-ring-opacity,1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:bg-danger-100\/80:hover{background-color:rgba(var(--danger-100),.8)}.hover\:bg-primary-50:hover{--tw-bg-opacity:1;background-color:rgba(var(--primary-50),var(--tw-bg-opacity,1))}.hover\:bg-primary-50\/50:hover{background-color:rgba(var(--primary-50),.5)}.hover\:underline:hover{text-decoration-line:underline}.group:hover .group-hover\:flex{display:flex}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/button:hover .group-hover\/button\:text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity,1))}.group:hover .group-hover\:text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.dark\:divide-gray-600:is(.dark *)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgba(var(--gray-600),var(--tw-divide-opacity,1))}.dark\:border-b:is(.dark *){border-bottom-width:1px}.dark\:bg-green-400\/10:is(.dark *){background-color:rgba(74,222,128,.1)}.dark\:bg-orange-400\/10:is(.dark *){background-color:rgba(251,146,60,.1)}.dark\:bg-sky-400\/10:is(.dark *){background-color:rgba(56,189,248,.1)}.dark\:\!text-red-400\/60:is(.dark *){color:hsla(0,91%,71%,.6)!important}.dark\:text-gray-100:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-100),var(--tw-text-opacity,1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity:1;color:rgba(var(--gray-300),var(--tw-text-opacity,1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.dark\:text-green-400\/80:is(.dark *){color:rgba(74,222,128,.8)}.dark\:text-orange-400:is(.dark *){--tw-text-opacity:1;color:rgb(251 146 60/var(--tw-text-opacity,1))}.dark\:text-primary-400\/80:is(.dark *){color:rgba(var(--primary-400),.8)}.dark\:text-red-400\/80:is(.dark *){color:hsla(0,91%,71%,.8)}.dark\:text-sky-400:is(.dark *){--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity,1))}.dark\:ring-gray-600:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-600),var(--tw-ring-opacity,1))}.dark\:ring-purple-800:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(107 33 168/var(--tw-ring-opacity,1))}.dark\:ring-sky-800:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(7 89 133/var(--tw-ring-opacity,1))}.dark\:ring-teal-800:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(17 94 89/var(--tw-ring-opacity,1))}.dark\:hover\:bg-danger-300\/20:hover:is(.dark *){background-color:rgba(var(--danger-300),.2)}.group:hover .dark\:group-hover\:text-green-400:is(.dark *){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.group:hover .dark\:group-hover\:text-red-400:is(.dark *){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}@media (min-width:768px){.md\:min-w-\[32rem\]{min-width:32rem}}.ltr\:rotate-90:where([dir=ltr],[dir=ltr] *){--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rtl\:\!rotate-90:where([dir=rtl],[dir=rtl] *){--tw-rotate:90deg!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.rtl\:space-x-reverse:where([dir=rtl],[dir=rtl] *)>:not([hidden])~:not([hidden]){--tw-space-x-reverse:1}.rtl\:text-right:where([dir=rtl],[dir=rtl] *){text-align:right}.\[\&_table\]\:h-\[1px\] table{height:1px} \ No newline at end of file diff --git a/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php b/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php index 7b42494b83..f57db7f0f3 100644 --- a/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php +++ b/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php @@ -1,5 +1,5 @@

-
+
- - {{ $this->form }} - + + {{ $this->form }} +
{{ $this->addCommentAction }}
@@ -43,7 +43,7 @@ class="inline-block w-8 h-8 rounded-full" /> '-left-[calc(0.5rem_-_1px)]' => !$item['log']->causer, ])> @if ($email = $item['log']->causer?->email) - {{ $logUserName }} Date: Tue, 11 Nov 2025 18:23:16 +0000 Subject: [PATCH 27/50] Ensure customer discount search works across fields (#2285) Closes https://github.com/lunarphp/lunar/issues/2284#issuecomment-3210762278 ## Summary by CodeRabbit * **New Features** * Improved customer selection when attaching to discounts: supports multi-word, space-separated searching that matches first name, last name, and company name to return more accurate results. * Faster narrowing of results when entering multiple terms, making it easier to find customers by partial names or company matches. --------- Co-authored-by: Glenn Jacobs Co-authored-by: Author --- .../CustomerLimitationRelationManager.php | 15 +++++++++++++++ packages/core/src/Base/Purchasable.php | 2 -- packages/core/src/DataTypes/ShippingOption.php | 2 -- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CustomerLimitationRelationManager.php b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CustomerLimitationRelationManager.php index 598597c0b8..ff329055af 100644 --- a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CustomerLimitationRelationManager.php +++ b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CustomerLimitationRelationManager.php @@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Model; use Lunar\Admin\Support\RelationManagers\BaseRelationManager; +use function Filament\Support\generate_search_column_expression; + class CustomerLimitationRelationManager extends BaseRelationManager { protected static bool $isLazy = false; @@ -37,6 +39,19 @@ public function getDefaultTable(Table $table): Table ])->recordTitle(function ($record) { return $record->full_name; })->preloadRecordSelect() + ->recordSelectOptionsQuery(function ($query, $search) { + if (! filled($search)) { + return $query; + } + + foreach (explode(' ', $search) as $word) { + $query->where(function ($query) use ($word) { + foreach (['first_name', 'last_name', 'company_name'] as $index => $column) { + $query->{$index == 0 ? 'where' : 'orWhere'}(generate_search_column_expression($query->qualifyColumn($column), true, $query->getConnection()), 'like', "%{$word}%"); + } + }); + } + }) ->label( __('lunarpanel::discount.relationmanagers.customers.actions.attach.label') ), diff --git a/packages/core/src/Base/Purchasable.php b/packages/core/src/Base/Purchasable.php index dcd9cc8c3c..a299268ed7 100644 --- a/packages/core/src/Base/Purchasable.php +++ b/packages/core/src/Base/Purchasable.php @@ -54,8 +54,6 @@ public function getOption(); /** * Return the options for this purchasable. - * - * @return \Illuminate\Support\Collection */ public function getOptions(): Collection; diff --git a/packages/core/src/DataTypes/ShippingOption.php b/packages/core/src/DataTypes/ShippingOption.php index 474422f2a8..f74b8ec90e 100644 --- a/packages/core/src/DataTypes/ShippingOption.php +++ b/packages/core/src/DataTypes/ShippingOption.php @@ -110,8 +110,6 @@ public function getOption() /** * Return the options for this purchasable - * - * @return Collection */ public function getOptions(): Collection { From 16e92c66ae1a436cf22b2527871bbaaa78898c9e Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 11 Nov 2025 18:43:47 +0000 Subject: [PATCH 28/50] Translate product tabs (#2344) Closes #2256 --- packages/admin/resources/lang/en/product.php | 2 ++ .../Filament/Resources/ProductResource/Pages/ListProducts.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/admin/resources/lang/en/product.php b/packages/admin/resources/lang/en/product.php index 6f02bc16e1..53bc4c9096 100644 --- a/packages/admin/resources/lang/en/product.php +++ b/packages/admin/resources/lang/en/product.php @@ -8,6 +8,8 @@ 'tabs' => [ 'all' => 'All', + 'published' => 'Published', + 'draft' => 'Draft', ], 'status' => [ diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php index e6f7f44a40..7b38d40f02 100644 --- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php +++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php @@ -84,9 +84,9 @@ public function getDefaultTabs(): array { return [ 'all' => Tab::make(__('lunarpanel::product.tabs.all')), - 'published' => Tab::make('Published') + 'published' => Tab::make(__('lunarpanel::product.tabs.published')) ->modifyQueryUsing(fn (Builder $query) => $query->where('status', 'published')), - 'draft' => Tab::make('Draft') + 'draft' => Tab::make(__('lunarpanel::product.tabs.draft')) ->modifyQueryUsing(fn (Builder $query) => $query->where('status', 'draft')) ->badge(Product::query()->where('status', 'draft')->count()), ]; From 5888ca934b0ce06af0b91c524677c13b39acb836 Mon Sep 17 00:00:00 2001 From: Ahmed Taha Date: Tue, 11 Nov 2025 22:55:17 +0200 Subject: [PATCH 29/50] Fix: Allow the discount to be applied if no condition coupon exists regardless of the coupon code on the cart (#2300) Allow the discounts to be applied if no condition coupon exists regardless of cart coupon. This way discounts that doesn't have a coupon requirement can be added and not be removed because of another coupon discount that is applied. Current behaviour: if a discount that has a coupon requirement is applied. and other discounts that doesn't have a coupon requirements are applied as well. the coupon code discount causes the other discounts not to apply to the cart at all because of a wrong $validCoupon comparison logic. Expected behaviour: discounts should be applied if there is no coupon code requirement regardless of the coupon code set on the cart. this way coupon discounts doesn't simply override all other discounts. --------- Co-authored-by: Glenn Jacobs --- packages/core/src/DiscountTypes/AbstractDiscountType.php | 2 +- tests/core/Unit/DiscountTypes/AmountOffTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/DiscountTypes/AbstractDiscountType.php b/packages/core/src/DiscountTypes/AbstractDiscountType.php index ab9f68db7d..87735a83c1 100644 --- a/packages/core/src/DiscountTypes/AbstractDiscountType.php +++ b/packages/core/src/DiscountTypes/AbstractDiscountType.php @@ -74,7 +74,7 @@ protected function checkDiscountConditions(CartContract $cart): bool $cartCoupon = strtoupper($cart->coupon_code ?? ''); $conditionCoupon = strtoupper($this->discount->coupon ?? ''); - $validCoupon = $cartCoupon ? ($cartCoupon === $conditionCoupon) : blank($conditionCoupon); + $validCoupon = filled($conditionCoupon) ? ($cartCoupon === $conditionCoupon) : true; $minSpend = (int) ($data['min_prices'][$cart->currency->code] ?? 0) / (int) $cart->currency->factor; $minSpend = (int) bcmul($minSpend, $cart->currency->factor); diff --git a/tests/core/Unit/DiscountTypes/AmountOffTest.php b/tests/core/Unit/DiscountTypes/AmountOffTest.php index 6f7a032f10..5b2adfb82b 100644 --- a/tests/core/Unit/DiscountTypes/AmountOffTest.php +++ b/tests/core/Unit/DiscountTypes/AmountOffTest.php @@ -963,6 +963,7 @@ $cart = Cart::factory()->create([ 'currency_id' => $currency->id, 'channel_id' => $channel->id, + 'coupon_code' => 'NOTAPPLICABLE', ]); $purchasableA = ProductVariant::factory()->create(); From 54816a3e77bf1f46c5a0aebf8755fcfe5002c30c Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 12 Nov 2025 15:33:08 +0000 Subject: [PATCH 30/50] Remove Meilisearch package from monorepo (#2347) The Meilisearch package was originally created to add functionality missing from Laravel Scout, but Scout now includes this functionality (`php artisan scout:sync-index-settings`) so it's redundant. This PR removes the package from the monorepo and the actual repo shall be marked as archived in GitHub. --- .github/workflows/split_packages.yml | 2 +- composer.json | 4 - packages/meilisearch/.github/FUNDING.yml | 3 - .../.github/workflows/close-pull-request.yml | 13 ---- packages/meilisearch/README.md | 3 - packages/meilisearch/composer.json | 32 -------- .../src/Console/MeilisearchSetup.php | 75 ------------------- .../src/MeilisearchServiceProvider.php | 23 ------ 8 files changed, 1 insertion(+), 154 deletions(-) delete mode 100644 packages/meilisearch/.github/FUNDING.yml delete mode 100644 packages/meilisearch/.github/workflows/close-pull-request.yml delete mode 100644 packages/meilisearch/README.md delete mode 100644 packages/meilisearch/composer.json delete mode 100644 packages/meilisearch/src/Console/MeilisearchSetup.php delete mode 100644 packages/meilisearch/src/MeilisearchServiceProvider.php diff --git a/.github/workflows/split_packages.yml b/.github/workflows/split_packages.yml index 4bd58029d1..04d67c0d8c 100644 --- a/.github/workflows/split_packages.yml +++ b/.github/workflows/split_packages.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "meilisearch", "search"] + package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "search"] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 0e116cd0ba..c2d89dd4c5 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,6 @@ "Lunar\\Database\\Factories\\": "packages/core/database/factories", "Lunar\\Database\\Seeders\\": "packages/core/database/seeders", "Lunar\\Database\\State\\": "packages/core/database/state", - "Lunar\\Meilisearch\\": "packages/meilisearch/src/", "Lunar\\Opayo\\": "packages/opayo/src/", "Lunar\\Paypal\\": "packages/paypal/src/", "Lunar\\Search\\": "packages/search/src/", @@ -93,7 +92,6 @@ "Table Rate Shipping", "Opayo Payments", "Search", - "Meilisearch", "Paypal Payments", "Stripe Payments" ] @@ -102,7 +100,6 @@ "providers": [ "Lunar\\Stripe\\StripePaymentsServiceProvider", "Lunar\\Paypal\\PaypalServiceProvider", - "Lunar\\Meilisearch\\MeilisearchServiceProvider", "Lunar\\Search\\SearchServiceProvider", "Lunar\\Admin\\LunarPanelProvider", "Lunar\\Opayo\\OpayoServiceProvider", @@ -114,7 +111,6 @@ "replace": { "lunarphp/core": "self.version", "lunarphp/lunar": "self.version", - "lunarphp/meilisearch": "self.version", "lunarphp/opayo": "self.version", "lunarphp/paypal": "self.version", "lunarphp/search": "self.version", diff --git a/packages/meilisearch/.github/FUNDING.yml b/packages/meilisearch/.github/FUNDING.yml deleted file mode 100644 index 8c14ff3364..0000000000 --- a/packages/meilisearch/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: [lunar] diff --git a/packages/meilisearch/.github/workflows/close-pull-request.yml b/packages/meilisearch/.github/workflows/close-pull-request.yml deleted file mode 100644 index 177299fd8a..0000000000 --- a/packages/meilisearch/.github/workflows/close-pull-request.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Close Pull Request - -on: - pull_request_target: - types: [opened] - -jobs: - run: - runs-on: ubuntu-latest - steps: - - uses: superbrothers/close-pull-request@v3 - with: - comment: "Thank you for your pull request. However, you have submitted this PR on the Lunar Meilisearch Repo which is a read-only sub split of `lunarphp/lunar`. Please submit your PR on the https://github.com/lunarphp/lunar repository.

Thanks!" diff --git a/packages/meilisearch/README.md b/packages/meilisearch/README.md deleted file mode 100644 index 2f2fb72e74..0000000000 --- a/packages/meilisearch/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Lunar Meilisearch - -The package indexes filterable and sortable attributes on Meilisearch engine diff --git a/packages/meilisearch/composer.json b/packages/meilisearch/composer.json deleted file mode 100644 index 9b8557dfb8..0000000000 --- a/packages/meilisearch/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lunarphp/meilisearch", - "description": "Meilisearch Addon", - "keywords": ["lunarphp", "laravel", "ecommerce", "e-commerce", "headless", "store", "meilisearch"], - "license": "MIT", - "authors": [ - { - "name": "Lunar", - "homepage": "https://lunarphp.io/" - } - ], - "require": { - "meilisearch/meilisearch-php": "^1.10" - }, - "autoload": { - "psr-4": { - "Lunar\\Meilisearch\\": "src/" - } - }, - "extra": { - "lunar": { - "name": "Meilisearch" - }, - "laravel": { - "providers": [ - "Lunar\\Meilisearch\\MeilisearchServiceProvider" - ] - } - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/packages/meilisearch/src/Console/MeilisearchSetup.php b/packages/meilisearch/src/Console/MeilisearchSetup.php deleted file mode 100644 index 91001e335c..0000000000 --- a/packages/meilisearch/src/Console/MeilisearchSetup.php +++ /dev/null @@ -1,75 +0,0 @@ -engine = $engine->createMeilisearchDriver(); - - // Make sure we have the relevant indexes ready to go. - foreach ($searchables as $searchable) { - $model = (new $searchable); - - $indexName = $model->searchableAs(); - - try { - $index = $this->engine->getIndex($indexName); - $this->warn("Index {$indexName} found for {$searchable}"); - } catch (Exception $e) { - $this->warn($e->getMessage()); - $this->info("Creating index {$indexName} for {$searchable}"); - - $task = $this->engine->createIndex($indexName); - $this->engine->waitForTask($task['taskUid']); - - $index = $this->engine->getIndex($indexName); - } - - $this->info("Update filterable fields to {$searchable}"); - $task = $index->updateFilterableAttributes( - $model->getFilterableAttributes() - ); - $this->engine->waitForTask($task['taskUid']); - - $this->info("Update sortable fields to {$searchable}"); - $task = $index->updateSortableAttributes( - $model->getSortableAttributes() - ); - $this->engine->waitForTask($task['taskUid']); - - $this->newLine(); - } - } -} diff --git a/packages/meilisearch/src/MeilisearchServiceProvider.php b/packages/meilisearch/src/MeilisearchServiceProvider.php deleted file mode 100644 index 5c3221713c..0000000000 --- a/packages/meilisearch/src/MeilisearchServiceProvider.php +++ /dev/null @@ -1,23 +0,0 @@ -app->runningInConsole()) { - $this->commands([ - MeilisearchSetup::class, - ]); - } - } -} From 90ee9d7e36844ea0f5a5a341785829715c512f9d Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 12 Nov 2025 15:36:10 +0000 Subject: [PATCH 31/50] Remove READMEs, LICENSE's and redundant phpunit.xml (#2348) We now have dedicated docs using Mintlify, so we don't want to be maintaining lots of README files, etc. Instead, we should highlight that the individual package repo's are read-only subtree splits similar to how Filament do it. E.g. image --- packages/admin/LICENSE.md | 21 -- packages/admin/README.md | 3 - packages/admin/phpunit.xml | 22 -- packages/core/LICENSE.md | 21 -- packages/core/README.md | 16 - packages/core/phpunit.xml | 40 --- packages/opayo/README.md | 169 --------- packages/paypal/README.md | 3 - packages/search/README.md | 125 ------- packages/stripe/README.md | 435 ----------------------- packages/table-rate-shipping/README.md | 116 ------ packages/table-rate-shipping/phpunit.xml | 27 -- 12 files changed, 998 deletions(-) delete mode 100644 packages/admin/LICENSE.md delete mode 100644 packages/admin/README.md delete mode 100644 packages/admin/phpunit.xml delete mode 100644 packages/core/LICENSE.md delete mode 100644 packages/core/README.md delete mode 100644 packages/core/phpunit.xml delete mode 100644 packages/opayo/README.md delete mode 100644 packages/paypal/README.md delete mode 100644 packages/search/README.md delete mode 100644 packages/stripe/README.md delete mode 100644 packages/table-rate-shipping/README.md delete mode 100644 packages/table-rate-shipping/phpunit.xml diff --git a/packages/admin/LICENSE.md b/packages/admin/LICENSE.md deleted file mode 100644 index a5dec88053..0000000000 --- a/packages/admin/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) LunarPHP Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/admin/README.md b/packages/admin/README.md deleted file mode 100644 index c37f05c46d..0000000000 --- a/packages/admin/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Lunar Admin Panel - -See the docs at https://docs.lunarphp.io/ diff --git a/packages/admin/phpunit.xml b/packages/admin/phpunit.xml deleted file mode 100644 index 60aba9af1b..0000000000 --- a/packages/admin/phpunit.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - ./tests - - - - - ./src - - - - - - - - diff --git a/packages/core/LICENSE.md b/packages/core/LICENSE.md deleted file mode 100644 index a5dec88053..0000000000 --- a/packages/core/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) LunarPHP Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index 03ff78a374..0000000000 --- a/packages/core/README.md +++ /dev/null @@ -1,16 +0,0 @@ -

Lunar

- -## About Lunar - -Lunar is an open source E-commerce platform which embraces Laravel as it's foundation and uses it to build a highly -extensible, robust and feature rich application you can build any store on. - -We put developers first and try to ensure your experience is as smooth as possible. - -## Documentation - -- [Full documentation](https://docs.lunarphp.io/) - Includes in-depth guides on everything Lunar - -## License - -Lunar is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/packages/core/phpunit.xml b/packages/core/phpunit.xml deleted file mode 100644 index d9c55ad9d7..0000000000 --- a/packages/core/phpunit.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - src/ - - - - - ./tests/Unit - - - ./tests/Feature - - - ./tests/Database - - - ./tests/Utils - - - - - - - - diff --git a/packages/opayo/README.md b/packages/opayo/README.md deleted file mode 100644 index 18b212b178..0000000000 --- a/packages/opayo/README.md +++ /dev/null @@ -1,169 +0,0 @@ -

- - - -

This addon enables Opayo payments on your Lunar storefront.

- -## Alpha Release - -This addon is currently in Alpha, whilst every step is taken to ensure this is working as intended, it will not be considered out of Alpha until more tests have been added and proved. - -## Minimum Requirements - -- Lunar `1.x` -- An [Elavon](https://www.elavon.com/) merchant account - -## Installation - -### Require the composer package - -```sh -composer require lunarphp/opayo -``` - -### Configure the service - -Add the opayo config to the `config/services.php` file. - -```php -// ... -'opayo' => [ - 'vendor' => env('OPAYO_VENDOR'), - 'env' => env('OPAYO_ENV', 'test'), - 'key' => env('OPAYO_KEY'), - 'password' => env('OPAYO_PASSWORD'), - 'host' => env('OPAYO_HOST'), -], -``` - - -### Enable the driver - -Set the driver in `config/lunar/payments.php` - -```php - [ - 'card' => [ - // ... - 'driver' => 'opayo', - ], - ], -]; -``` - - - -## Configuration - -Below is a list of the available configuration options this package uses in `config/lunar/opayo.php` - -| Key | Default | Description | -| --- | --- | --- | -| `policy` | `automatic` | Determines the policy for taking payments and whether you wish to capture the payment manually later or take payment straight away. Available options `deferred` or `automatic` | - ---- - -## Backend Usage - -### Get a merchant key - -```php -Lunar\Opayo\Facades\Opayo::getMerchantKey(); -``` - -### Authorize a charge - -```php -$response = \Lunar\Facades\Payments::driver('opayo')->cart( - $cart = CartSession::current()->calculate() -)->withData([ - 'merchant_key' => $request->get('merchantSessionKey'), - 'card_identifier' => $request->get('cardToken'), - 'browserLanguage' => $request->get('browserLanguage'), - 'challengeWindowSize' => $request->get('challengeWindowSize'), - 'browserIP' => $request->ip(), - 'browserAcceptHeader' => $request->header('accept'), - 'browserUserAgent' => $request->get('browserUserAgent'), - 'browserJavaEnabled' => $request->get('browserJavaEnabled', false), - 'browserColorDepth' => $request->get('browserColorDepth'), - 'browserScreenHeight' => $request->get('browserScreenHeight'), - 'browserScreenWidth' => $request->get('browserScreenWidth'), - 'browserTZ' => $request->get('browserTZ'), - 'status' => 'payment-received', -])->authorize(); -``` - -When authorizing a charge, you may be required to submit extra authentication in the form of 3DSV2, you can handle this in your payment endpoint. - -```php -if (is_a($response, \Lunar\Opayo\Responses\ThreeDSecureResponse::class)) { - return response()->json([ - 'requires_auth' => true, - 'data' => $response, - ]); -} -``` - -`$response` will contain all the 3DSV2 information from Opayo. - -You can find more information about this using the following links: - -- [3-D Secure explained](https://www.elavon.co.uk/resource-center/help-with-your-solutions/opayo/fraud-prevention/3D-Secure.html) -- [3D Secure Transactions](https://developer.elavon.com/products/opayo-direct/v1/3d-secure-transactions) -- Stack overflow [SagePay 3D Secure V2 Flow](https://stackoverflow.com/questions/65329436/sagepay-3d-secure-v2-flow) - -Once you have handled the 3DSV2 response on your storefront, you can then authorize again. - -```php -$response = Payments::driver('opayo')->cart( - $cart = CartSession::current()->calculate() -)->withData([ - 'cres' => $request->get('cres'), - 'pares' => $request->get('pares'), - 'transaction_id' => $request->get('transaction_id'), -])->threedsecure(); - -if (! $response->success) { - abort(401); -} - -``` - -### Opayo card tokens - -When authenticated users make an order on your store, it can be good to offer the ability to save their card information for future use. Whilst we don't store the actual card details, we can use card tokens which represent the card the user has used before. - -> You must have saved payments enabled on your Opayo account because you can use these. - -To save a card, pass in the `saveCard` data key when authorizing a payment. - -```php -$response = \Lunar\Facades\Payments::driver('opayo')->cart( - $cart = CartSession::current()->calculate() -)->withData([ - // ... - 'saveCard' => true -])->authorize(); -``` - -Assuming everything went well, there will be a new entry in the `opayo_tokens` table, associated to the authenticated user. You can then display these card representations at checkout for the user to select. The `token` is what replaces the `card_identifier` data key. - -```php -$response = \Lunar\Facades\Payments::driver('opayo')->cart( - $cart = CartSession::current()->calculate() -)->withData([ - // ... - 'card_identifier' => $request->get('cardToken'), - 'reusable' => true -])->authorize(); -``` - -Responses are then handled the same as any other transaction. - -## Contributing - -Contributions are welcome, if you are thinking of adding a feature, please submit an issue first so we can determine whether it should be included. diff --git a/packages/paypal/README.md b/packages/paypal/README.md deleted file mode 100644 index 91c3a064cf..0000000000 --- a/packages/paypal/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Lunar PayPal Payments - -WIP diff --git a/packages/search/README.md b/packages/search/README.md deleted file mode 100644 index 7d6d9cd819..0000000000 --- a/packages/search/README.md +++ /dev/null @@ -1,125 +0,0 @@ - -## Lunar Search - -This packages brings E-Commerce search to Lunar. ---- - -## Requirements -- Lunar >= 1.x - -## License - -Lunar is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). - -## Installation - -### Require the composer package - -```sh -composer require lunarphp/search -``` - -## Usage - -### Configuration - -Most configuration is done via `config/lunar/search.php`. Here you can specify which facets should be used and how they are displayed. - -```php -'facets' => [ - \Lunar\Models\Product::class => [ - 'brand' => [ - 'label' => 'Brand', - ], - 'colour' => [ - 'label' => 'Colour', - ], - 'size' => [ - 'label' => 'Size', - ], - 'shoe-size' => [ - 'label' => 'Shoe Size', - ] - ] -], -``` - -### Basic Search - -At a basic level, you can search models using the provided facade. - -```php -use Lunar\Search\Facades\Search; - -// Search on a specific model -$results = Search::on(\Lunar\Models\Collection::class)->query('Hoodies')->get(); - -// Search on Lunar\Models\Product by default. -$results = Search::query('Hoodies')->get(); -``` - -Under the hood this will detect what Scout driver is mapped under `lunar.search.engine_map` and -then perform a search using that given driver. To increase performance the results will not be -hydrated from the database, but instead will be the raw results from the search provider. - -### Response format - -This packages makes use of Spatie Data to transform search results from the provider into consistent responses you can use on your frontend. - -You can view all available Data classes in the `src/Data` directory. - -### Transforming Data classes to Typescript - -If you use Spatie Typescript Transformer, you can add the following to your `typescript-transformer.php` config: - -```php -return [ - //... - 'auto_discover_types' => [ - // ... - \Lunar\Search\data_path(), - ], -]; -``` -Then in your Javascript the data classes will be available under the `Lunar.Search` namespace. - -```js -defineProps<{ - results: Lunar.Search.SearchResults -}>() -``` - -### Handling the response - -Searching returns a `Lunar\Data\SearchResult` DTO which you can use in your templates: - -```php -use Lunar\Search\Facades\Search; -$results = Search::query('Hoodies')->get(); -``` - -```bladehtml - -@foreach($results->hits as $hit) - {{ $hit->document['name'] }} -@endforeach - - -@foreach($results->facets as $facet) - - {{ $facet->label }} - @foreach($facet->values as $facetValue) - - $facetValue->active, - ]) - > - {{ $facetValue->label }} - - {{ $facetValue->count }} - @endforeach -
-@endforeach -``` - diff --git a/packages/stripe/README.md b/packages/stripe/README.md deleted file mode 100644 index 45c2f4248f..0000000000 --- a/packages/stripe/README.md +++ /dev/null @@ -1,435 +0,0 @@ -

- - -

This addon enables Stripe payments on your Lunar storefront.

- -## Alpha Release - -This addon is currently in Alpha, whilst every step is taken to ensure this is working as intended, it will not be considered out of Alpha until more tests have been added and proved. - -## Tests required: - -- [ ] Successful charge response from Stripe. -- [ ] Unsuccessful charge response from Stripe. -- [ ] Test `manual` config reacts appropriately. -- [x] Test `automatic` config reacts appropriately. -- [ ] Ensure transactions are stored correctly in the database -- [x] Ensure that the payment intent is not duplicated when using the same Cart -- [ ] Ensure appropriate responses are returned based on Stripe's responses. -- [ ] Test refunds and partial refunds create the expected transactions -- [ ] Make sure we can manually release a payment or part payment and handle the different responses. - -## Minimum Requirements - -- Lunar `1.x` -- A [Stripe](http://stripe.com/) account with secret and public keys - -## Optional Requirements - -- Laravel Livewire (if using frontend components) -- Alpinejs (if using frontend components) -- Javascript framework - -## Installation - -### Require the composer package - -```sh -composer require lunarphp/stripe -``` - -### Publish the configuration - -This will publish the configuration under `config/lunar/stripe.php`. - -```sh -php artisan vendor:publish --tag=lunar.stripe.config -``` - -### Publish the views (optional) - -Lunar Stripe comes with some helper components for you to use on your checkout, if you intend to edit the views they provide, you can publish them. - -```sh -php artisan vendor:publish --tag=lunar.stripe.components -``` - -### Enable the driver - -Set the driver in `config/lunar/payments.php` - -```php - [ - 'card' => [ - // ... - 'driver' => 'stripe', - ], - ], -]; -``` - -### Add your Stripe credentials - -Make sure you have the Stripe credentials set in `config/services.php` - -```php -'stripe' => [ - 'key' => env('STRIPE_SECRET'), - 'public_key' => env('STRIPE_PK'), - 'webhooks' => [ - 'lunar' => env('LUNAR_STRIPE_WEBHOOK_SECRET'), - ], -], -``` - -> Keys can be found in your Stripe account https://dashboard.stripe.com/apikeys - -## Configuration - -Below is a list of the available configuration options this package uses in `config/lunar/stripe.php` - -| Key | Default | Description | -|------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `policy` | `automatic` | Determines the policy for taking payments and whether you wish to capture the payment manually later or take payment straight away. Available options `manual` or `automatic` | -| `sync_addresses` | `true` | When enabled, the Stripe addon will attempt to sync the billing and shipping addresses which have been stored against the payment intent on Stripe. | ---- - -## Backend Usage - -### Create a PaymentIntent - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::createIntent(\Lunar\Models\Cart $cart, $options = []); -``` - -This method will create a Stripe PaymentIntent from a Cart and add the resulting ID to the meta for retrieval later. If a PaymentIntent already exists for a cart this will fetch it from Stripe and return that instead to avoid duplicate PaymentIntents being created. - -You can pass any additional parameters you need, by default the following are sent: - -```php -[ - 'amount' => 1099, - 'currency' => 'GBP', - 'automatic_payment_methods' => ['enabled' => true], - 'capture_method' => config('lunar.stripe.policy', 'automatic'), - // If a shipping address exists on a cart - // $shipping = $cart->shippingAddress - 'shipping' => [ - 'name' => "{$shipping->first_name} {$shipping->last_name}", - 'phone' => $shipping->contact_phone, - 'address' => [ - 'city' => $shipping->city, - 'country' => $shipping->country->iso2, - 'line1' => $shipping->line_one, - 'line2' => $shipping->line_two, - 'postal_code' => $shipping->postcode, - 'state' => $shipping->state, - ], - ] -] -``` - -```php -$paymentIntentId = $cart->meta['payment_intent']; // The resulting ID from the method above. -``` -```php -$cart->meta->payment_intent; -``` - -### Fetch an existing PaymentIntent - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::fetchIntent($paymentIntentId); -``` - -### Syncing an existing intent - -If a payment intent has been created and there are changes to the cart, you will want to update the intent so it has the correct totals. - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::syncIntent(\Lunar\Models\Cart $cart); -``` - - -### Update an existing intent - -For when you want to update certain properties on the PaymentIntent, without needing to recalculate the cart. - -See https://docs.stripe.com/api/payment_intents/update - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::updateIntent(\Lunar\Models\Cart $cart, [ - 'shipping' => [/*..*/] -]); -``` - -### Cancel an existing intent - -If you need to cancel a PaymentIntent, you can do so. You will need to provide a valid reason, those of which can be found in the Stripe docs: https://docs.stripe.com/api/payment_intents/cancel. - -Lunar Stripe includes a PHP Enum to make this easier for you: - -```php -use Lunar\Stripe\Enums\CancellationReason; - -CancellationReason::ABANDONED; -CancellationReason::DUPLICATE; -CancellationReason::REQUESTED_BY_CUSTOMER; -CancellationReason::FRAUDULENT; -``` - -```php -use Lunar\Stripe\Facades\Stripe; -use Lunar\Stripe\Enums\CancellationReason; - -Stripe::cancelIntent(\Lunar\Models\Cart $cart, CancellationReason $reason); -``` - -### Update the address on Stripe - -So you don't have to manually specify all the shipping address fields you can use the helper function to do it for you. - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::updateShippingAddress(\Lunar\Models\Cart $cart); -``` - -## Charges - -### Retrieve a specific charge - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::getCharge(string $chargeId); -``` - -### Get all charges for a payment intent - -```php -use \Lunar\Stripe\Facades\Stripe; - -Stripe::getCharges(string $paymentIntentId); -``` - -## Webhooks - -The add-on provides an optional webhook you may add to Stripe. You can read the guide on how to do this on the Stripe website [https://stripe.com/docs/webhooks/quickstart](https://stripe.com/docs/webhooks/quickstart). - -The events you should listen to are `payment_intent.payment_failed`, `payment_intent.succeeded`. - -The path to the webhook will be `http:://yoursite.com/stripe/webhook`. - -You can customise the path for the webhook in `config/lunar/stripe.php`. - -You will also need to add the webhook signing secret to the `services.php` config file: - -```php - [ - // ... - 'webhooks' => [ - 'lunar' => '...' - ], - ], -]; -``` - -If you do not wish to use the webhook, or would like to manually process an order as well, you are able to do so. - -```php -$cart = CartSession::current(); - -// With a draft order... -$draftOrder = $cart->createOrder(); -Payments::driver('stripe')->order($draftOrder)->withData([ - 'payment_intent' => $draftOrder->meta['payment_intent'], -])->authorize(); - -// Using just the cart... -Payments::driver('stripe')->cart($cart)->withData([ - 'payment_intent' => $cart->meta['payment_intent'], -])->authorize(); -``` - - -## Storefront Examples - -First we need to set up the backend API call to fetch or create the intent, this isn't Vue specific but will likely be different if you're using Livewire. - -```php -use \Lunar\Stripe\Facades\Stripe; - -Route::post('api/payment-intent', function () { - $cart = CartSession::current(); - - $cartData = CartData::from($cart); - - if ($paymentIntent = $cartData->meta['payment_intent'] ?? false) { - $intent = StripeFacade::fetchIntent($paymentIntent); - } else { - $intent = StripeFacade::createIntent($cart); - } - - if ($intent->amount != $cart->total->value) { - StripeFacade::syncIntent($cart); - } - - return $intent; -})->middleware('web'); -``` - -### Vuejs - -This is just using Stripe's payment elements, for more information [check out the Stripe guides](https://stripe.com/docs/payments/elements) - -### Payment component - -```js - -``` - -```html - -``` ---- - -## Extending - -### Webhook event params - -In order to process the payment intent and link it to an order, we need the PaymentIntent ID and an optional Order ID. - -By default Lunar Stripe will look for the PaymentIntent ID via the Stripe Event and try and determine whether an existing order ID has been defined on the PaymentIntent meta. - -You can customise this behaviour by overriding the `ProcessesEventParameters` instance. - -```php -// AppServiceProvider -use Lunar\Stripe\Concerns\ProcessesEventParameters; -use Lunar\Stripe\DataTransferObjects\EventParameters; - -public function boot() -{ - $this->app->instance(ProcessesEventParameters::class, new class implements ProcessesEventParameters - { - public function handle(\Stripe\Event $event): EventParameters - { - $paymentIntentId = $event->data->object->id; - // Setting $orderId to null will mean a new order is created. - $orderId = null; - - return new EventParameters($paymentIntentId, $orderId); - } - }); - -## Events - -Below are the events which are dispatched under specific situations within the addon. - -### `Lunar\Stripe\Events\Webhook\CartMissingForIntent` - -Dispatched when attempting to process a payment intent, but no matching Order or Cart model can be found. - -```php -public function handle(\Lunar\Stripe\Events\Webhook\CartMissingForIntent $event) -{ - echo $event->paymentIntentId; -} -``` - -## Contributing - -Contributions are welcome, if you are thinking of adding a feature, please submit an issue first so we can determine whether it should be included. - - -## Testing - -A [MockClient](https://github.com/lunar/stripe/blob/main/tests/Stripe/MockClient.php) class is used to mock responses the Stripe API will return. diff --git a/packages/table-rate-shipping/README.md b/packages/table-rate-shipping/README.md deleted file mode 100644 index 4b90525994..0000000000 --- a/packages/table-rate-shipping/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# Lunar Table Rate Shipping - - -# Requirements - -- LunarPHP Admin `>` `1.x` - -# Installation - -Install via Composer - -``` -composer require lunarphp/table-rate-shipping -``` - -Then register the plugin in your service provider - -```php -use Lunar\Admin\Support\Facades\LunarPanel; -use Lunar\Shipping\ShippingPlugin; -// ... - -public function register(): void -{ - LunarPanel::panel(function (Panel $panel) { - return $panel->plugin(new ShippingPlugin()); - })->register(); - - // ... -} -``` -# Getting Started -This addon provides an easy way for you to add different shipping to your storefront and allow your customers to choose from the different shipping rates you have set up, based on various factors such as zones, minimum spend etc - -## Shipping Methods - -Shipping Methods are the different ways in which your storefront can send orders to customers, you could also allow customers to collect their order from your store which this addon supports. - -## Shipping Zones - -Shipping Zones allow you to section of area's of the countries you ship to, providing you with an easy way to offer distinct shipping methods and pricing to each zone, each zone can be restricted by the following: - -- Postal codes -- Country -- State/Province (based on country) - -## Shipping Rates - -Shipping Rates are the prices you offer for each of your shipping zones, they are linked to a shipping method. So for example you might have a Courier Area Shipping Zone and an Everywhere Else Shipping Zone, you can offer different pricing restrictions using the same shipping methods. - -## Shipping Exclusion Lists - -Sometimes, you might not want to ship certain items to particular Shipping Zone, this is where exclusion lists come in. You can associate purchasables to a list which you can then associate to a shipping zone, if a cart contains any of them then they won't be able to select a shipping rate. - -# Storefront usage - -This addon uses the shipping modifiers provided by the Lunar core, so you shouldn't need to change your existing implementation. - -```php -$options = \Lunar\Base\ShippingManifest::getOptions( - $cart -); -``` - -# Advanced usage - -## Return available drivers - -```php -\Lunar\Shipping\Facades\Shipping::getSupportedDrivers(); -``` - -## Using the driver directly - -```php -\Lunar\Shipping\Facades\Shipping::with('ship-by')->resolve( - new \Lunar\Shipping\DataTransferObjects\ShippingOptionRequest( - shippingRate: \Lunar\Shipping\Models\ShippingRate $shippingRate, - cart: \Lunar\Models\Cart $cart - ) -); -``` - -## Shipping Zones - -Each method is optional, the more you add the more strict it becomes. - -```php -$shippingZones = Lunar\Shipping\Facades\Shipping::zones() - ->country(\Lunar\Models\Country $country) - ->state(\Lunar\Models\State $state) - ->postcode( - new \Lunar\Shipping\DataTransferObjects\PostcodeLookup( - country: \Lunar\Models\Country $country, - postcode: 'NW1' - ) - )->get() - -$shippingZones->map(/* .. */); -``` - -## Shipping Rates - -```php -$shippingRates = \Lunar\Shipping\Facades\Shipping::shippingRates( - \Lunar\Models\Cart $cart -); -``` - -## Shipping Options - -```php -$shippingOptions = \Lunar\Shipping\Facades\Shipping::shippingOptions( - \Lunar\Models\Cart $cart -); -``` \ No newline at end of file diff --git a/packages/table-rate-shipping/phpunit.xml b/packages/table-rate-shipping/phpunit.xml deleted file mode 100644 index 1b64e22fb6..0000000000 --- a/packages/table-rate-shipping/phpunit.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - ./tests/Unit - - - ./tests/Feature - - - - - ./app - - - - - - - - - From e185dd54d209ecfc58b6165fad01e8d877f9db16 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Wed, 12 Nov 2025 16:36:58 +0000 Subject: [PATCH 32/50] Update split_packages.yml --- .github/workflows/split_packages.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/split_packages.yml b/.github/workflows/split_packages.yml index 04d67c0d8c..9458f6ad15 100644 --- a/.github/workflows/split_packages.yml +++ b/.github/workflows/split_packages.yml @@ -21,7 +21,7 @@ jobs: coverage: none - name: Split ${{ matrix.package }} - uses: "danharrin/monorepo-split-github-action@v2.3.0" + uses: "danharrin/monorepo-split-github-action@v2.4.0" if: "!startsWith(github.ref, 'refs/tags/')" env: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} @@ -38,7 +38,7 @@ jobs: run: echo "GITHUB_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: Split ${{ matrix.package }} - uses: "danharrin/monorepo-split-github-action@v2.3.0" + uses: "danharrin/monorepo-split-github-action@v2.4.0" if: "startsWith(github.ref, 'refs/tags/')" env: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} @@ -52,7 +52,7 @@ jobs: - name: Tag ${{ matrix.package }} if: "startsWith(github.ref, 'refs/tags/')" - uses: "danharrin/monorepo-split-github-action@v2.3.0" + uses: "danharrin/monorepo-split-github-action@v2.4.0" env: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} with: From 4db3b1b87569b0ff888f2d2b4597c65c5b90fb7f Mon Sep 17 00:00:00 2001 From: wychoong <67364036+wychoong@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:33:43 +0800 Subject: [PATCH 33/50] Add ordering by position to attribute queries (#2361) In the panel, AtrributeGroup and Attibute position is not used to when configuring product type attributes --- .../admin/src/Support/Forms/Components/AttributeSelector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/admin/src/Support/Forms/Components/AttributeSelector.php b/packages/admin/src/Support/Forms/Components/AttributeSelector.php index f6820986b3..069fd2b844 100644 --- a/packages/admin/src/Support/Forms/Components/AttributeSelector.php +++ b/packages/admin/src/Support/Forms/Components/AttributeSelector.php @@ -65,7 +65,7 @@ public function getAttributeGroups() $type = $this->attributableType; } - return AttributeGroup::whereAttributableType($type)->get(); + return AttributeGroup::whereAttributableType($type)->orderBy('position')->get(); } public function getSelectedAttributes($groupId) @@ -75,6 +75,6 @@ public function getSelectedAttributes($groupId) public function getAttributes($groupId) { - return Attribute::where('attribute_group_id', $groupId)->get(); + return Attribute::where('attribute_group_id', $groupId)->orderBy('position')->get(); } } From dcf21b31786dbd11c8da3b27b10b82c2bedb9975 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 8 Dec 2025 08:22:15 +0000 Subject: [PATCH 34/50] Ensure PDF outputs when an order has no shipping address (#2365) This PR fixes a 500 error from being displayed when a PDF is generated for an order with no shipping address. --- packages/admin/resources/views/pdf/order.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/admin/resources/views/pdf/order.blade.php b/packages/admin/resources/views/pdf/order.blade.php index d965c69cc2..5551bbe75e 100644 --- a/packages/admin/resources/views/pdf/order.blade.php +++ b/packages/admin/resources/views/pdf/order.blade.php @@ -114,6 +114,7 @@ @endif + @if ($record->shippingAddress)

Shipping

{{ $record->shippingAddress->fullName }}
@@ -135,6 +136,7 @@ {{ $record->shippingAddress->postcode }}
{{ $record->shippingAddress->country->name }}
+ @endif From cbae3b090de1c055d27aa5b171f846e75ea37a08 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Mon, 8 Dec 2025 13:51:41 +0000 Subject: [PATCH 35/50] Revert breadcrumbs back to ancestors (#2364) PR to revert #2249 as this is seems to have broken breadcrumbs on collections. Suggest we have a look at performance in more depth as the collection tree will also stall if you have over 1k collections in a group, suggesting there is a bigger issue at player with that. Closes #2360 --- packages/admin/src/Filament/Resources/CollectionResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/admin/src/Filament/Resources/CollectionResource.php b/packages/admin/src/Filament/Resources/CollectionResource.php index 58fd33664c..ee85d19e67 100644 --- a/packages/admin/src/Filament/Resources/CollectionResource.php +++ b/packages/admin/src/Filament/Resources/CollectionResource.php @@ -47,7 +47,7 @@ public static function getCollectionBreadcrumbs(CollectionContract $collection): ]) => $collection->group->name, ]; - foreach ($collection->children as $childCollection) { + foreach ($collection->ancestors as $childCollection) { $crumbs[ CollectionResource::getUrl('edit', [ 'record' => $childCollection, From d78c1900f02f2dbfbc8987017bef3aace4fc707b Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Mon, 8 Dec 2025 13:52:10 +0000 Subject: [PATCH 36/50] Allow shipping rates to be disabled (#2363) Currently the `enabled` column on a Shipping Rate doesn't actually get taken into account during resolution. This PR looks to add the `enabled` check to the `ShippingRateResolver` whilst also providing an action in the panel to toggle this value. --------- Co-authored-by: Author --- .../resources/lang/en/relationmanagers.php | 7 ++ .../Pages/ManageShippingRates.php | 30 +++++- .../src/Resolvers/ShippingRateResolver.php | 3 + .../Resolvers/ShippingRateResolverTest.php | 94 +++++++++++++++++++ 4 files changed, 130 insertions(+), 4 deletions(-) diff --git a/packages/table-rate-shipping/resources/lang/en/relationmanagers.php b/packages/table-rate-shipping/resources/lang/en/relationmanagers.php index 09bcc1e522..26eae22285 100644 --- a/packages/table-rate-shipping/resources/lang/en/relationmanagers.php +++ b/packages/table-rate-shipping/resources/lang/en/relationmanagers.php @@ -47,8 +47,15 @@ ], ], 'table' => [ + 'enabled' => [ + 'label' => 'Enabled', + ], + 'disabled' => [ + 'label' => 'disabled', + ], 'shipping_method' => [ 'label' => 'Shipping Method', + 'disabled' => 'Disabled', ], 'price' => [ 'label' => 'Price', diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php index 93e9b1a0d0..41e7fb8438 100644 --- a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php +++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php @@ -2,6 +2,8 @@ namespace Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages; +use Awcodes\FilamentBadgeableColumn\Components\Badge; +use Awcodes\FilamentBadgeableColumn\Components\BadgeableColumn; use Awcodes\Shout\Components\Shout; use Filament\Forms; use Filament\Forms\Form; @@ -147,10 +149,15 @@ static function (Forms\Components\Repeater $component, ?Model $record = null): v public function table(Table $table): Table { return $table->columns([ - TextColumn::make('shippingMethod.name') - ->label( - __('lunarpanel.shipping::relationmanagers.shipping_rates.table.shipping_method.label') - ), + BadgeableColumn::make('shippingMethod.name') + ->separator('') + ->suffixBadges([ + Badge::make('default') + ->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.shipping_method.disabled')) + ->color('warning') + ->visible(fn (Model $record) => ! $record->enabled), + ]) + ->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.shipping_method.label')), TextColumn::make('basePrices.0')->formatStateUsing( fn ($state = null) => $state->price->formatted )->label( @@ -178,6 +185,21 @@ public function table(Table $table): Table static::saveShippingRate($shippingRate, $data); }), Tables\Actions\DeleteAction::make()->requiresConfirmation(), + Tables\Actions\Action::make('disable')->color('warning')->action(function (ShippingRate $shippingRate) { + $shippingRate->updateQuietly([ + 'enabled' => false, + ]); + })->hidden( + fn (ShippingRate $shippingRate) => ! $shippingRate->enabled + ), + Tables\Actions\Action::make('enable')->color('success')->action(function (ShippingRate $shippingRate) { + $shippingRate->updateQuietly([ + 'enabled' => true, + ]); + })->hidden( + fn (ShippingRate $shippingRate) => (bool) $shippingRate->enabled + ), + ]); } diff --git a/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php b/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php index 51f6bb5b67..af8cd3bc10 100644 --- a/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php +++ b/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php @@ -151,6 +151,9 @@ public function get(): Collection foreach ($zones as $zone) { $zoneShippingRates = $zone->rates + ->reject(function ($state) { + return ! $state->enabled; + }) ->reject(function ($rate) { $method = $rate->shippingMethod()->customerGroup($this->customerGroups)->first(); diff --git a/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php b/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php index cbbf503f48..c02f03a170 100644 --- a/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php +++ b/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php @@ -454,3 +454,97 @@ expect($shippingRates)->toHaveCount(0); }); + +test('will not resolve rates which are not enabled.', function () { + $currency = Currency::factory()->create([ + 'default' => true, + ]); + + $country = Country::factory()->create(); + + TaxClass::factory()->create([ + 'default' => true, + ]); + + $shippingZone = ShippingZone::factory()->create([ + 'type' => 'postcodes', + ]); + + $shippingZone->postcodes()->create([ + 'postcode' => 'AB1', + ]); + + $shippingZone->countries()->attach($country); + + $shippingMethod = ShippingMethod::factory()->create([ + 'driver' => 'ship-by', + 'data' => [], + 'stock_available' => 0, + ]); + + $customerGroup = \Lunar\Models\CustomerGroup::factory()->create([ + 'default' => true, + ]); + $shippingMethod->customerGroups()->sync([ + $customerGroup->id => ['enabled' => true, 'visible' => true, 'starts_at' => now(), 'ends_at' => null], + ]); + + $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([ + 'shipping_method_id' => $shippingMethod->id, + 'shipping_zone_id' => $shippingZone->id, + 'enabled' => false, + ]); + + $shippingRate->prices()->createMany([ + [ + 'price' => 600, + 'min_quantity' => 1, + 'currency_id' => $currency->id, + ], + [ + 'price' => 500, + 'min_quantity' => 700, + 'currency_id' => $currency->id, + ], + [ + 'price' => 0, + 'min_quantity' => 800, + 'currency_id' => $currency->id, + ], + ]); + + $cart = $this->createCart($currency, 500); + + $cart->lines()->delete(); + + $purchasable = ProductVariant::factory()->create(); + $purchasable->shippable = true; + + Price::factory()->create([ + 'price' => 200, + 'min_quantity' => 1, + 'currency_id' => $currency->id, + 'priceable_type' => $purchasable->getMorphClass(), + 'priceable_id' => $purchasable->id, + ]); + + $cart->lines()->create([ + 'purchasable_type' => $purchasable->getMorphClass(), + 'purchasable_id' => $purchasable->id, + 'quantity' => 1, + ]); + + $cart->shippingAddress()->create( + CartAddress::factory()->make([ + 'country_id' => $country->id, + 'state' => null, + 'postcode' => 'AB1 1CD', + ])->toArray() + ); + + $shippingRates = Shipping::shippingRates( + $cart->refresh()->calculate() + )->get(); + + expect($shippingRates)->toHaveCount(0); +}); From 0bd904eb81173bf0ffd969527f0d0933196e9a83 Mon Sep 17 00:00:00 2001 From: Valerio Pio De Nicola <38079112+xV4L3x@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:54:08 +0100 Subject: [PATCH 37/50] Updated config key for allowsMultipleOrdersPerCart function in CartSessionManager (#2356) Replaced a typo in the `allowsMultipleOrdersPerCart` function in `packages/core/src/Managers/CartSessionManager.php` the function was trying to access the the `cart_session` config with a wrong key: `allowsMultipleOrdersPerCart`. I replaced it with the key that was actually in the config: `allow_multiple_orders_per_cart` --- packages/core/src/Managers/CartSessionManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/Managers/CartSessionManager.php b/packages/core/src/Managers/CartSessionManager.php index a23f715e4c..8ccef088f2 100644 --- a/packages/core/src/Managers/CartSessionManager.php +++ b/packages/core/src/Managers/CartSessionManager.php @@ -30,7 +30,7 @@ public function __construct( public function allowsMultipleOrdersPerCart(): bool { - return config('lunar.cart_session.allow_multiple_per_order', false); + return config('lunar.cart_session.allow_multiple_orders_per_cart', false); } /** From 6c777cdf2b093053bd2b2dd6f05acce4a7a384df Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Mon, 8 Dec 2025 13:55:33 +0000 Subject: [PATCH 38/50] Use max to prevent negative sub totals (#2351) This PR prevents reward lines from producing a negative discounted subtotal. Currently, if a BuyXGetY discount grants multiple free items but the cart contains fewer qualifying products, the discount calculation can push the subtotal below zero. By wrapping the final discounted amount in max(0, ...), we ensure the subtotal never becomes negative. If the discount exceeds the cart line value, the subtotal is simply set to 0.00 until the customer adds enough qualifying items. --------- Co-authored-by: Author --- packages/core/src/DiscountTypes/BuyXGetY.php | 9 +- .../core/Unit/DiscountTypes/BuyXGetYTest.php | 112 ++++++++++++++++++ 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/packages/core/src/DiscountTypes/BuyXGetY.php b/packages/core/src/DiscountTypes/BuyXGetY.php index 1271db4538..f7524f2ebd 100644 --- a/packages/core/src/DiscountTypes/BuyXGetY.php +++ b/packages/core/src/DiscountTypes/BuyXGetY.php @@ -173,6 +173,7 @@ public function apply(CartContract $cart): CartContract $remainingRewardQty -= $qtyToAllocate; $subTotal = $rewardLine->subTotal->value; + $unitPrice = $rewardLine->unitPrice->value; $lineDiscountTotal = $unitPrice * $qtyToAllocate; @@ -283,14 +284,18 @@ private function processAutomaticRewards(CartContract $cart, int $remainingRewar $discountTotal += $unitPrice; + if ($discountTotal > $rewardLine->subTotal->value) { + $discountTotal = $rewardLine->subTotal->value; + } + $rewardLine->discountTotal = new Price( - ($rewardLine->discountTotal?->value ?? 0) + $unitPrice, + $discountTotal, $cart->currency, 1 ); $rewardLine->subTotalDiscounted = new Price( - $rewardLine->subTotal->value - $rewardLine->discountTotal->value, + max(0, $rewardLine->subTotal->value - $rewardLine->discountTotal->value), $cart->currency, 1 ); diff --git a/tests/core/Unit/DiscountTypes/BuyXGetYTest.php b/tests/core/Unit/DiscountTypes/BuyXGetYTest.php index 615ab7d30b..ade9a1c865 100644 --- a/tests/core/Unit/DiscountTypes/BuyXGetYTest.php +++ b/tests/core/Unit/DiscountTypes/BuyXGetYTest.php @@ -1242,3 +1242,115 @@ $this->assertCount(1, $cart->freeItems); }); + +test('discounted sub total will not fall below zero', function () { + $customerGroup = CustomerGroup::factory()->create([ + 'default' => true, + ]); + + $channel = Channel::factory()->create([ + 'default' => true, + ]); + + $currency = Currency::factory()->create([ + 'code' => 'GBP', + ]); + + /** + * Product set up. + */ + $productA = Product::factory()->create(); + $productB = Product::factory()->create(); + + $purchasableA = ProductVariant::factory()->create([ + 'product_id' => $productA->id, + ]); + $purchasableB = ProductVariant::factory()->create([ + 'product_id' => $productB->id, + ]); + + Price::factory()->create([ + 'price' => 1000, // £10 + 'min_quantity' => 1, + 'currency_id' => $currency->id, + 'priceable_type' => $purchasableA->getMorphClass(), + 'priceable_id' => $purchasableA->id, + ]); + + Price::factory()->create([ + 'price' => 500, // £5 + 'min_quantity' => 1, + 'currency_id' => $currency->id, + 'priceable_type' => $purchasableB->getMorphClass(), + 'priceable_id' => $purchasableB->id, + ]); + + /** + * Cart set up. + */ + $cart = Cart::factory()->create([ + 'channel_id' => $channel->id, + 'currency_id' => $currency->id, + ]); + + $cart->lines()->create([ + 'purchasable_type' => $purchasableA->getMorphClass(), + 'purchasable_id' => $purchasableA->id, + 'quantity' => 20, + ]); + + $cart->lines()->create([ + 'purchasable_type' => $purchasableB->getMorphClass(), + 'purchasable_id' => $purchasableB->id, + 'quantity' => 1, + ]); + + /** + * Discount set up. + */ + $discountA = Discount::factory()->create([ + 'type' => BuyXGetY::class, + 'priority' => 1, + 'name' => 'Test Product Discount', + 'data' => [ + 'min_qty' => 10, + 'reward_qty' => 1, + 'max_reward_qty' => null, + 'automatically_add_rewards' => true, + ], + ]); + + foreach ([$discountA] as $discount) { + $discount->customerGroups()->sync([ + $customerGroup->id => [ + 'enabled' => true, + 'starts_at' => now(), + ], + ]); + $discount->channels()->sync([ + $channel->id => [ + 'enabled' => true, + 'starts_at' => now()->subHour(), + ], + ]); + } + + $discountA->discountableConditions()->create([ + 'discountable_type' => $productA->getMorphClass(), + 'discountable_id' => $productA->id, + ]); + + $discountA->discountableRewards()->create([ + 'discountable_type' => $productB->getMorphClass(), + 'discountable_id' => $productB->id, + 'type' => 'reward', + ]); + + $cart = $cart->calculate(); + + $lineB = $cart->lines->first(function ($line) use ($purchasableB) { + return $line->purchasable_id == $purchasableB->id; + }); + + expect($lineB->subTotalDiscounted->value)->toEqual(0); +}); From 5718f5fe9a5e76f84831f628f12d9e1a56af518e Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 13:57:54 +0000 Subject: [PATCH 39/50] Add meta to product options and values (#2346) This adds meta to Product Options & Product Option Values to support customisation, e.g. assigning hex colours. It also updates the model annotations and corrects the `name` cast on ProductOptionValue (this might be considered a breaking change...) ## TODO - [ ] Add docs PR, including upgrade guide entry --- ..._12_005200_add_meta_to_product_options.php | 30 +++++++++++++++++++ packages/core/src/Models/ProductOption.php | 7 +++-- .../core/src/Models/ProductOptionValue.php | 8 +++-- 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 packages/core/database/migrations/2025_11_12_005200_add_meta_to_product_options.php diff --git a/packages/core/database/migrations/2025_11_12_005200_add_meta_to_product_options.php b/packages/core/database/migrations/2025_11_12_005200_add_meta_to_product_options.php new file mode 100644 index 0000000000..7ecb7e7c64 --- /dev/null +++ b/packages/core/database/migrations/2025_11_12_005200_add_meta_to_product_options.php @@ -0,0 +1,30 @@ +prefix.'product_options', function (Blueprint $table) { + $table->jsonb('meta')->nullable()->after('shared'); + }); + + Schema::table($this->prefix.'product_option_values', function (Blueprint $table) { + $table->jsonb('meta')->nullable()->after('position'); + }); + } + + public function down(): void + { + Schema::table($this->prefix.'product_options', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + + Schema::table($this->prefix.'product_option_values', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + } +}; diff --git a/packages/core/src/Models/ProductOption.php b/packages/core/src/Models/ProductOption.php index e1a0a86543..e41848b1f8 100644 --- a/packages/core/src/Models/ProductOption.php +++ b/packages/core/src/Models/ProductOption.php @@ -18,10 +18,12 @@ /** * @property int $id - * @property \Illuminate\Support\Collection $name - * @property \Illuminate\Support\Collection $label + * @property AsArrayObject $name + * @property ?AsArrayObject $label * @property int $position * @property ?string $handle + * @property bool $shared + * @property ?AsArrayObject $meta * @property ?\Illuminate\Support\Carbon $created_at * @property ?\Illuminate\Support\Carbon $updated_at */ @@ -43,6 +45,7 @@ class ProductOption extends BaseModel implements Contracts\ProductOption, Spatie 'name' => AsArrayObject::class, 'label' => AsArrayObject::class, 'shared' => 'boolean', + 'meta' => AsArrayObject::class, ]; /** diff --git a/packages/core/src/Models/ProductOptionValue.php b/packages/core/src/Models/ProductOptionValue.php index 02ed9066b8..9270e8c3d6 100644 --- a/packages/core/src/Models/ProductOptionValue.php +++ b/packages/core/src/Models/ProductOptionValue.php @@ -2,7 +2,7 @@ namespace Lunar\Models; -use Illuminate\Database\Eloquent\Casts\AsCollection; +use Illuminate\Database\Eloquent\Casts\AsArrayObject; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -16,8 +16,9 @@ /** * @property int $id * @property int $product_option_id - * @property string $name + * @property AsArrayObject $name * @property int $position + * @property ?AsArrayObject $meta * @property ?\Illuminate\Support\Carbon $created_at * @property ?\Illuminate\Support\Carbon $updated_at */ @@ -34,7 +35,8 @@ class ProductOptionValue extends BaseModel implements Contracts\ProductOptionVal * @var array */ protected $casts = [ - 'name' => AsCollection::class, + 'name' => AsArrayObject::class, + 'meta' => AsArrayObject::class, ]; /** From b39b56e87b525f78c43822ea51fd83f868f74a5e Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 14:38:03 +0000 Subject: [PATCH 40/50] Revert "Remove Meilisearch package from monorepo (#2347)" This reverts commit 54816a3e77bf1f46c5a0aebf8755fcfe5002c30c. --- .github/workflows/split_packages.yml | 2 +- composer.json | 4 + packages/meilisearch/.github/FUNDING.yml | 3 + .../.github/workflows/close-pull-request.yml | 13 ++++ packages/meilisearch/README.md | 3 + packages/meilisearch/composer.json | 32 ++++++++ .../src/Console/MeilisearchSetup.php | 75 +++++++++++++++++++ .../src/MeilisearchServiceProvider.php | 23 ++++++ 8 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 packages/meilisearch/.github/FUNDING.yml create mode 100644 packages/meilisearch/.github/workflows/close-pull-request.yml create mode 100644 packages/meilisearch/README.md create mode 100644 packages/meilisearch/composer.json create mode 100644 packages/meilisearch/src/Console/MeilisearchSetup.php create mode 100644 packages/meilisearch/src/MeilisearchServiceProvider.php diff --git a/.github/workflows/split_packages.yml b/.github/workflows/split_packages.yml index 9458f6ad15..db7f86c050 100644 --- a/.github/workflows/split_packages.yml +++ b/.github/workflows/split_packages.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "search"] + package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "meilisearch", "search"] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index c2d89dd4c5..0e116cd0ba 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,7 @@ "Lunar\\Database\\Factories\\": "packages/core/database/factories", "Lunar\\Database\\Seeders\\": "packages/core/database/seeders", "Lunar\\Database\\State\\": "packages/core/database/state", + "Lunar\\Meilisearch\\": "packages/meilisearch/src/", "Lunar\\Opayo\\": "packages/opayo/src/", "Lunar\\Paypal\\": "packages/paypal/src/", "Lunar\\Search\\": "packages/search/src/", @@ -92,6 +93,7 @@ "Table Rate Shipping", "Opayo Payments", "Search", + "Meilisearch", "Paypal Payments", "Stripe Payments" ] @@ -100,6 +102,7 @@ "providers": [ "Lunar\\Stripe\\StripePaymentsServiceProvider", "Lunar\\Paypal\\PaypalServiceProvider", + "Lunar\\Meilisearch\\MeilisearchServiceProvider", "Lunar\\Search\\SearchServiceProvider", "Lunar\\Admin\\LunarPanelProvider", "Lunar\\Opayo\\OpayoServiceProvider", @@ -111,6 +114,7 @@ "replace": { "lunarphp/core": "self.version", "lunarphp/lunar": "self.version", + "lunarphp/meilisearch": "self.version", "lunarphp/opayo": "self.version", "lunarphp/paypal": "self.version", "lunarphp/search": "self.version", diff --git a/packages/meilisearch/.github/FUNDING.yml b/packages/meilisearch/.github/FUNDING.yml new file mode 100644 index 0000000000..8c14ff3364 --- /dev/null +++ b/packages/meilisearch/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [lunar] diff --git a/packages/meilisearch/.github/workflows/close-pull-request.yml b/packages/meilisearch/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000000..177299fd8a --- /dev/null +++ b/packages/meilisearch/.github/workflows/close-pull-request.yml @@ -0,0 +1,13 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: "Thank you for your pull request. However, you have submitted this PR on the Lunar Meilisearch Repo which is a read-only sub split of `lunarphp/lunar`. Please submit your PR on the https://github.com/lunarphp/lunar repository.

Thanks!" diff --git a/packages/meilisearch/README.md b/packages/meilisearch/README.md new file mode 100644 index 0000000000..2f2fb72e74 --- /dev/null +++ b/packages/meilisearch/README.md @@ -0,0 +1,3 @@ +# Lunar Meilisearch + +The package indexes filterable and sortable attributes on Meilisearch engine diff --git a/packages/meilisearch/composer.json b/packages/meilisearch/composer.json new file mode 100644 index 0000000000..9b8557dfb8 --- /dev/null +++ b/packages/meilisearch/composer.json @@ -0,0 +1,32 @@ +{ + "name": "lunarphp/meilisearch", + "description": "Meilisearch Addon", + "keywords": ["lunarphp", "laravel", "ecommerce", "e-commerce", "headless", "store", "meilisearch"], + "license": "MIT", + "authors": [ + { + "name": "Lunar", + "homepage": "https://lunarphp.io/" + } + ], + "require": { + "meilisearch/meilisearch-php": "^1.10" + }, + "autoload": { + "psr-4": { + "Lunar\\Meilisearch\\": "src/" + } + }, + "extra": { + "lunar": { + "name": "Meilisearch" + }, + "laravel": { + "providers": [ + "Lunar\\Meilisearch\\MeilisearchServiceProvider" + ] + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/packages/meilisearch/src/Console/MeilisearchSetup.php b/packages/meilisearch/src/Console/MeilisearchSetup.php new file mode 100644 index 0000000000..91001e335c --- /dev/null +++ b/packages/meilisearch/src/Console/MeilisearchSetup.php @@ -0,0 +1,75 @@ +engine = $engine->createMeilisearchDriver(); + + // Make sure we have the relevant indexes ready to go. + foreach ($searchables as $searchable) { + $model = (new $searchable); + + $indexName = $model->searchableAs(); + + try { + $index = $this->engine->getIndex($indexName); + $this->warn("Index {$indexName} found for {$searchable}"); + } catch (Exception $e) { + $this->warn($e->getMessage()); + $this->info("Creating index {$indexName} for {$searchable}"); + + $task = $this->engine->createIndex($indexName); + $this->engine->waitForTask($task['taskUid']); + + $index = $this->engine->getIndex($indexName); + } + + $this->info("Update filterable fields to {$searchable}"); + $task = $index->updateFilterableAttributes( + $model->getFilterableAttributes() + ); + $this->engine->waitForTask($task['taskUid']); + + $this->info("Update sortable fields to {$searchable}"); + $task = $index->updateSortableAttributes( + $model->getSortableAttributes() + ); + $this->engine->waitForTask($task['taskUid']); + + $this->newLine(); + } + } +} diff --git a/packages/meilisearch/src/MeilisearchServiceProvider.php b/packages/meilisearch/src/MeilisearchServiceProvider.php new file mode 100644 index 0000000000..5c3221713c --- /dev/null +++ b/packages/meilisearch/src/MeilisearchServiceProvider.php @@ -0,0 +1,23 @@ +app->runningInConsole()) { + $this->commands([ + MeilisearchSetup::class, + ]); + } + } +} From 2abb7b67163457e092227436006924c101c85010 Mon Sep 17 00:00:00 2001 From: Charlie Langridge Date: Mon, 8 Dec 2025 14:40:07 +0000 Subject: [PATCH 41/50] Add cart_session config option to allow for the prevention of the cart deletion on user logout (#2349) Added 'delete_on_logout' config option within cart_session.php config file to allow the prevention of the cart deletion when a user logs out --------- Co-authored-by: Glenn Jacobs --- packages/core/config/cart_session.php | 10 +++ .../core/src/Managers/CartSessionManager.php | 4 +- .../Listeners/CartSessionAuthListenerTest.php | 72 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/core/Unit/Listeners/CartSessionAuthListenerTest.php diff --git a/packages/core/config/cart_session.php b/packages/core/config/cart_session.php index 849235881c..3fd80bbb39 100644 --- a/packages/core/config/cart_session.php +++ b/packages/core/config/cart_session.php @@ -35,4 +35,14 @@ | */ 'allow_multiple_orders_per_cart' => false, + + /* + |-------------------------------------------------------------------------- + | Delete cart on logout + |-------------------------------------------------------------------------- + | + | Determines whether the cart sholud be soft deleted when the user logs out. + | + */ + 'delete_on_forget' => true, ]; diff --git a/packages/core/src/Managers/CartSessionManager.php b/packages/core/src/Managers/CartSessionManager.php index 8ccef088f2..5335674179 100644 --- a/packages/core/src/Managers/CartSessionManager.php +++ b/packages/core/src/Managers/CartSessionManager.php @@ -68,8 +68,10 @@ public function getShippingEstimateMeta(): array /** * {@inheritDoc} */ - public function forget(bool $delete = true): void + public function forget(?bool $delete = null): void { + $delete = is_null($delete) ? config('lunar.cart_session.delete_on_forget', true) : $delete; + if ($delete) { Cart::destroy( $this->sessionManager->get( diff --git a/tests/core/Unit/Listeners/CartSessionAuthListenerTest.php b/tests/core/Unit/Listeners/CartSessionAuthListenerTest.php new file mode 100644 index 0000000000..4f9ce6fc07 --- /dev/null +++ b/tests/core/Unit/Listeners/CartSessionAuthListenerTest.php @@ -0,0 +1,72 @@ +group('cart_session'); +uses(\Illuminate\Foundation\Testing\RefreshDatabase::class); + +test('cart is soft deleted on logout when delete_on_logout is true', function () { + // Ensure required defaults exist + Currency::factory()->create(['default' => true]); + Channel::factory()->create(['default' => true]); + + // Create a session cart + Config::set('lunar.cart_session.auto_create', true); + + $cart = CartSession::current(); + + // Authenticate a Lunar user so the Logout listener will act on it + $user = \Lunar\Tests\Core\Stubs\User::factory()->create(); + actingAs($user); + + // Sanity checks + expect($cart)->toBeInstanceOf(Cart::class); + expect(Session::get(config('lunar.cart_session.session_key')))->toEqual($cart->id); + + // Config dictates cart should be soft deleted on logout + Config::set('lunar.cart_session.delete_on_forget', true); + + // Fire the logout event (this triggers CartSessionAuthListener@logout) + event(new Logout('web', $user)); + + // Session cart should be cleared and the cart soft deleted + expect(Session::get(config('lunar.cart_session.session_key')))->toBeNull(); + expect($cart->refresh()->deleted_at)->not->toBeNull(); +}); + +test('cart is not soft deleted on logout when delete_on_logout is false', function () { + // Ensure required defaults exist + Currency::factory()->create(['default' => true]); + Channel::factory()->create(['default' => true]); + + // Create a session cart + Config::set('lunar.cart_session.auto_create', true); + + $cart = CartSession::current(); + + // Authenticate a Lunar user so the Logout listener will act on it + $user = \Lunar\Tests\Core\Stubs\User::factory()->create(); + actingAs($user); + + // Sanity checks + expect($cart)->toBeInstanceOf(Cart::class); + expect(Session::get(config('lunar.cart_session.session_key')))->toEqual($cart->id); + + // Config dictates cart should NOT be soft deleted on logout + Config::set('lunar.cart_session.delete_on_forget', false); + + // Fire the logout event (this triggers CartSessionAuthListener@logout) + event(new Logout('web', $user)); + + // Session cart should be cleared but the cart should remain not-deleted + expect(Session::get(config('lunar.cart_session.session_key')))->toBeNull(); + expect($cart->refresh()->deleted_at)->toBeNull(); +}); From 0ce76bcd527ad43f05ac4b9bbbf9b8ed5edc9c75 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Mon, 8 Dec 2025 14:49:52 +0000 Subject: [PATCH 42/50] Allow product association types to be customised (#2357) PR to allow product association types to be customised. - Deprecates the use of constants like `UP_SELL` on the model - Introduces a new enum to handle association types - Allow the enum to be replaced in config by developers. Closes #2337 --------- Co-authored-by: Author Co-authored-by: Glenn Jacobs --- .../Pages/ManageProductAssociations.php | 12 +++---- packages/core/config/products.php | 5 +++ packages/core/resources/lang/de/base.php | 9 +++++ packages/core/resources/lang/en/base.php | 5 +++ packages/core/resources/lang/es/base.php | 5 +++ packages/core/resources/lang/fr/base.php | 5 +++ packages/core/resources/lang/hu/base.php | 5 +++ packages/core/resources/lang/nl/base.php | 5 +++ packages/core/resources/lang/pl/base.php | 5 +++ packages/core/resources/lang/pt_BR/base.php | 5 +++ packages/core/resources/lang/ro/base.php | 5 +++ packages/core/resources/lang/tr/base.php | 5 +++ packages/core/resources/lang/vi/base.php | 5 +++ .../ProvidesProductAssociationType.php | 13 +++++++ .../src/Base/Enums/ProductAssociation.php | 21 +++++++++++ .../Jobs/Products/Associations/Associate.php | 14 +++----- .../Jobs/Products/Associations/Dissociate.php | 16 +++++---- packages/core/src/LunarServiceProvider.php | 1 + .../core/src/Models/Contracts/Product.php | 5 +-- .../Models/Contracts/ProductAssociation.php | 3 +- packages/core/src/Models/Product.php | 5 +-- .../core/src/Models/ProductAssociation.php | 36 ++++++++++++++++--- tests/core/Unit/Models/ProductTest.php | 34 +++++++++--------- 23 files changed, 176 insertions(+), 48 deletions(-) create mode 100644 packages/core/config/products.php create mode 100644 packages/core/resources/lang/de/base.php create mode 100644 packages/core/src/Base/Enums/Concerns/ProvidesProductAssociationType.php create mode 100644 packages/core/src/Base/Enums/ProductAssociation.php diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductAssociations.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductAssociations.php index a4134b2d52..bc961c53e6 100644 --- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductAssociations.php +++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductAssociations.php @@ -52,11 +52,7 @@ public function form(Form $form): Form }), Forms\Components\Select::make('type') ->required() - ->options([ - ProductAssociation::ALTERNATE => 'Alternate', - ProductAssociation::CROSS_SELL => 'Cross-Sell', - ProductAssociation::UP_SELL => 'Upsell', - ]), + ->options(ProductAssociation::getTypes()), ]); } @@ -82,7 +78,11 @@ public function table(Table $table): Table ->label(__('lunarpanel::product.table.name.label')), Tables\Columns\TextColumn::make('target.variants.sku') ->label('SKU'), - Tables\Columns\TextColumn::make('type'), + Tables\Columns\TextColumn::make('type')->formatStateUsing(function ($state) { + $enum = config('lunar.products.association_types_enum', \Lunar\Base\Enums\ProductAssociation::class); + + return $enum::tryFrom($state)?->label() ?: $state; + }), ]) ->filters([ // diff --git a/packages/core/config/products.php b/packages/core/config/products.php new file mode 100644 index 0000000000..550f3c65b4 --- /dev/null +++ b/packages/core/config/products.php @@ -0,0 +1,5 @@ + \Lunar\Base\Enums\ProductAssociation::class, +]; diff --git a/packages/core/resources/lang/de/base.php b/packages/core/resources/lang/de/base.php new file mode 100644 index 0000000000..8e37047b90 --- /dev/null +++ b/packages/core/resources/lang/de/base.php @@ -0,0 +1,9 @@ + [ + 'cross-sell' => 'Cross-Selling', + 'up-sell' => 'Up-Selling', + 'alternate' => 'Alternative', + ], +]; diff --git a/packages/core/resources/lang/en/base.php b/packages/core/resources/lang/en/base.php index 59511ed390..4ddde51027 100644 --- a/packages/core/resources/lang/en/base.php +++ b/packages/core/resources/lang/en/base.php @@ -6,4 +6,9 @@ 'images' => 'Images', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Cross Sell', + 'up-sell' => 'Up Sell', + 'alternate' => 'Alternate', + ], ]; diff --git a/packages/core/resources/lang/es/base.php b/packages/core/resources/lang/es/base.php index 526e2863ca..fea8f8ac48 100644 --- a/packages/core/resources/lang/es/base.php +++ b/packages/core/resources/lang/es/base.php @@ -6,4 +6,9 @@ 'images' => 'Imágenes', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Venta cruzada', + 'up-sell' => 'Venta adicional', + 'alternate' => 'Alternativa', + ], ]; diff --git a/packages/core/resources/lang/fr/base.php b/packages/core/resources/lang/fr/base.php index 59511ed390..f9c7a1f88f 100644 --- a/packages/core/resources/lang/fr/base.php +++ b/packages/core/resources/lang/fr/base.php @@ -6,4 +6,9 @@ 'images' => 'Images', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Vente croisée', + 'up-sell' => 'Montée en gamme', + 'alternate' => 'Alternative', + ], ]; diff --git a/packages/core/resources/lang/hu/base.php b/packages/core/resources/lang/hu/base.php index b2a36c2e17..229a903212 100644 --- a/packages/core/resources/lang/hu/base.php +++ b/packages/core/resources/lang/hu/base.php @@ -6,4 +6,9 @@ 'images' => 'Képek', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Keresztértékesítés', + 'up-sell' => 'Felülértékesítés', + 'alternate' => 'Alternatíva', + ], ]; diff --git a/packages/core/resources/lang/nl/base.php b/packages/core/resources/lang/nl/base.php index 9f681208d9..498e53a621 100644 --- a/packages/core/resources/lang/nl/base.php +++ b/packages/core/resources/lang/nl/base.php @@ -6,4 +6,9 @@ 'images' => 'Afbeeldingen', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Cross-sell', + 'up-sell' => 'Up-sell', + 'alternate' => 'Alternatief', + ], ]; diff --git a/packages/core/resources/lang/pl/base.php b/packages/core/resources/lang/pl/base.php index 364b5cfc3a..fbe4e6008c 100644 --- a/packages/core/resources/lang/pl/base.php +++ b/packages/core/resources/lang/pl/base.php @@ -6,4 +6,9 @@ 'images' => 'Obrazy', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Sprzedaż krzyżowa', + 'up-sell' => 'Sprzedaż dodatkowa', + 'alternate' => 'Alternatywa', + ], ]; diff --git a/packages/core/resources/lang/pt_BR/base.php b/packages/core/resources/lang/pt_BR/base.php index 5d46562d84..1d302cfa01 100644 --- a/packages/core/resources/lang/pt_BR/base.php +++ b/packages/core/resources/lang/pt_BR/base.php @@ -6,4 +6,9 @@ 'images' => 'Imagens', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Venda cruzada', + 'up-sell' => 'Venda adicional', + 'alternate' => 'Alternativa', + ], ]; diff --git a/packages/core/resources/lang/ro/base.php b/packages/core/resources/lang/ro/base.php index 9b027dd150..4ae27ad4f8 100644 --- a/packages/core/resources/lang/ro/base.php +++ b/packages/core/resources/lang/ro/base.php @@ -6,4 +6,9 @@ 'images' => 'Imagini', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Vânzare încrucișată', + 'up-sell' => 'Vânzare superioară', + 'alternate' => 'Alternativă', + ], ]; diff --git a/packages/core/resources/lang/tr/base.php b/packages/core/resources/lang/tr/base.php index 4b9aa0f86a..3305fe8995 100644 --- a/packages/core/resources/lang/tr/base.php +++ b/packages/core/resources/lang/tr/base.php @@ -6,4 +6,9 @@ 'images' => 'Görseller', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Çapraz satış', + 'up-sell' => 'Üst satış', + 'alternate' => 'Alternatif', + ], ]; diff --git a/packages/core/resources/lang/vi/base.php b/packages/core/resources/lang/vi/base.php index a666ea6abc..df5770d24b 100644 --- a/packages/core/resources/lang/vi/base.php +++ b/packages/core/resources/lang/vi/base.php @@ -6,4 +6,9 @@ 'images' => 'Hình ảnh', ], ], + 'product-association-types' => [ + 'cross-sell' => 'Bán chéo', + 'up-sell' => 'Bán nâng cấp', + 'alternate' => 'Thay thế', + ], ]; diff --git a/packages/core/src/Base/Enums/Concerns/ProvidesProductAssociationType.php b/packages/core/src/Base/Enums/Concerns/ProvidesProductAssociationType.php new file mode 100644 index 0000000000..0648945d50 --- /dev/null +++ b/packages/core/src/Base/Enums/Concerns/ProvidesProductAssociationType.php @@ -0,0 +1,13 @@ + __('lunar::base.product-association-types.cross-sell'), + self::UP_SELL => __('lunar::base.product-association-types.up-sell'), + self::ALTERNATE => __('lunar::base.product-association-types.alternate'), + }; + } +} diff --git a/packages/core/src/Jobs/Products/Associations/Associate.php b/packages/core/src/Jobs/Products/Associations/Associate.php index 281fad21f3..070c7a496d 100644 --- a/packages/core/src/Jobs/Products/Associations/Associate.php +++ b/packages/core/src/Jobs/Products/Associations/Associate.php @@ -8,6 +8,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; use Lunar\Facades\DB; use Lunar\Models\Contracts\Product as ProductContract; use Lunar\Models\Product; @@ -34,19 +35,14 @@ class Associate implements ShouldQueue protected ProductContract $product; /** - * The SKU for the generated variant. - * - * @var string + * The product association type. */ - protected $type = null; + protected ProvidesProductAssociationType|string $type; /** * Create a new job instance. - * - * @param mixed $targets - * @param string $type */ - public function __construct(ProductContract $product, $targets, $type = null) + public function __construct(ProductContract $product, mixed $targets, ProvidesProductAssociationType|string $type) { if (is_array($targets)) { $targets = collect($targets); @@ -73,7 +69,7 @@ public function handle() $this->targets->map(function ($model) { return [ 'product_target_id' => $model->id, - 'type' => $this->type, + 'type' => is_string($this->type) ? $this->type : $this->type->value, ]; }) ); diff --git a/packages/core/src/Jobs/Products/Associations/Dissociate.php b/packages/core/src/Jobs/Products/Associations/Dissociate.php index 4709b97eab..5ffe62b1e1 100644 --- a/packages/core/src/Jobs/Products/Associations/Dissociate.php +++ b/packages/core/src/Jobs/Products/Associations/Dissociate.php @@ -8,6 +8,8 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; +use Lunar\Base\BaseModel; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; use Lunar\Facades\DB; use Lunar\Models\Contracts\Product as ProductContract; use Lunar\Models\Product; @@ -42,11 +44,8 @@ class Dissociate implements ShouldQueue /** * Create a new job instance. - * - * @param mixed $targets - * @param string $type */ - public function __construct(ProductContract $product, $targets, $type = null) + public function __construct(ProductContract $product, Collection|BaseModel|array $targets, ProvidesProductAssociationType|string|null $type = null) { if (is_array($targets)) { $targets = collect($targets); @@ -72,11 +71,14 @@ public function handle() $query = $this->product->associations()->whereIn( 'product_target_id', $this->targets->pluck('id') + )->when( + $this->type, + fn ($query) => $query->where( + 'type', + is_string($this->type) ? $this->type : $this->type->value + ) ); - if ($this->type) { - $query->whereType($this->type); - } $query->delete(); }); } diff --git a/packages/core/src/LunarServiceProvider.php b/packages/core/src/LunarServiceProvider.php index fd29e2583d..37daed2ae3 100644 --- a/packages/core/src/LunarServiceProvider.php +++ b/packages/core/src/LunarServiceProvider.php @@ -113,6 +113,7 @@ class LunarServiceProvider extends ServiceProvider 'orders', 'payments', 'pricing', + 'products', 'search', 'shipping', 'taxes', diff --git a/packages/core/src/Models/Contracts/Product.php b/packages/core/src/Models/Contracts/Product.php index 5157df08f1..bf3d5d518c 100644 --- a/packages/core/src/Models/Contracts/Product.php +++ b/packages/core/src/Models/Contracts/Product.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; interface Product { @@ -56,12 +57,12 @@ public function inverseAssociations(): HasMany; /** * Associate a product to another with a type. */ - public function associate(mixed $product, string $type): void; + public function associate(mixed $product, ProvidesProductAssociationType $type): void; /** * Dissociate a product to another with a type. */ - public function dissociate(mixed $product, ?string $type = null): void; + public function dissociate(mixed $product, ?ProvidesProductAssociationType $type = null): void; /** * Return the customer groups relationship. diff --git a/packages/core/src/Models/Contracts/ProductAssociation.php b/packages/core/src/Models/Contracts/ProductAssociation.php index 6673ce4882..ef81248c18 100644 --- a/packages/core/src/Models/Contracts/ProductAssociation.php +++ b/packages/core/src/Models/Contracts/ProductAssociation.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; interface ProductAssociation { @@ -35,5 +36,5 @@ public function scopeAlternate(Builder $query): Builder; /** * Apply the type scope. */ - public function scopeType(Builder $query, string $type): Builder; + public function scopeType(Builder $query, ProvidesProductAssociationType $type): Builder; } diff --git a/packages/core/src/Models/Product.php b/packages/core/src/Models/Product.php index 0f372ad7f9..0338aa3be5 100644 --- a/packages/core/src/Models/Product.php +++ b/packages/core/src/Models/Product.php @@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Lunar\Base\BaseModel; use Lunar\Base\Casts\AsAttributeData; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; use Lunar\Base\HasThumbnailImage; use Lunar\Base\Traits\HasChannels; use Lunar\Base\Traits\HasCustomerGroups; @@ -144,7 +145,7 @@ public function inverseAssociations(): HasMany return $this->hasMany(ProductAssociation::modelClass(), 'product_target_id'); } - public function associate(mixed $product, string $type): void + public function associate(mixed $product, ProvidesProductAssociationType|string $type): void { Associate::dispatch($this, $product, $type); } @@ -152,7 +153,7 @@ public function associate(mixed $product, string $type): void /** * Dissociate a product to another with a type. */ - public function dissociate(mixed $product, ?string $type = null): void + public function dissociate(mixed $product, ProvidesProductAssociationType|string|null $type = null): void { Dissociate::dispatch($this, $product, $type); } diff --git a/packages/core/src/Models/ProductAssociation.php b/packages/core/src/Models/ProductAssociation.php index a23760e87a..f2f7891b99 100644 --- a/packages/core/src/Models/ProductAssociation.php +++ b/packages/core/src/Models/ProductAssociation.php @@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Lunar\Base\BaseModel; +use Lunar\Base\Enums\Concerns\ProvidesProductAssociationType; +use Lunar\Base\Enums\ProductAssociation as ProductAssociationEnum; use Lunar\Base\Traits\HasMacros; use Lunar\Database\Factories\ProductAssociationFactory; @@ -24,16 +26,25 @@ class ProductAssociation extends BaseModel implements Contracts\ProductAssociati /** * Define the cross-sell type. + * + * @deprecated 1.2.0 + * @see \Lunar\Base\Enums\ProductAssociation */ const CROSS_SELL = 'cross-sell'; /** * Define the upsell type. + * + * @deprecated 1.2.0 + * @see \Lunar\Base\Enums\ProductAssociation */ const UP_SELL = 'up-sell'; /** * Define the alternate type. + * + * @deprecated 1.2.0 + * @see \Lunar\Base\Enums\ProductAssociation */ const ALTERNATE = 'alternate'; @@ -77,7 +88,7 @@ public function target(): BelongsTo */ public function scopeCrossSell(Builder $query): Builder { - return $query->type(self::CROSS_SELL); + return $query->type(ProductAssociationEnum::CROSS_SELL); } /** @@ -85,7 +96,7 @@ public function scopeCrossSell(Builder $query): Builder */ public function scopeUpSell(Builder $query): Builder { - return $query->type(self::UP_SELL); + return $query->type(ProductAssociationEnum::UP_SELL); } /** @@ -93,14 +104,29 @@ public function scopeUpSell(Builder $query): Builder */ public function scopeAlternate(Builder $query): Builder { - return $query->type(self::ALTERNATE); + return $query->type(ProductAssociationEnum::ALTERNATE); } /** * Apply the type scope. */ - public function scopeType(Builder $query, string $type): Builder + public function scopeType(Builder $query, ProvidesProductAssociationType|string $type): Builder { - return $query->whereType($type); + return $query->where( + 'type', + '=', + is_string($type) ? $type : $type->value + ); + } + + public static function getTypes(): array + { + $enum = config('lunar.products.association_types_enum', \Lunar\Base\Enums\ProductAssociation::class); + + return collect($enum::cases())->mapWithKeys(function ($item) { + return [ + $item->value => $item->label(), + ]; + })->toArray(); } } diff --git a/tests/core/Unit/Models/ProductTest.php b/tests/core/Unit/Models/ProductTest.php index 6cc4c1f514..62eaf3ce7d 100644 --- a/tests/core/Unit/Models/ProductTest.php +++ b/tests/core/Unit/Models/ProductTest.php @@ -407,9 +407,9 @@ $targetA = Product::factory()->create(); $targetB = Product::factory()->create(); - $parent->associate([$targetA, $targetB], ProductAssociation::UP_SELL); + $parent->associate([$targetA, $targetB], \Lunar\Base\Enums\ProductAssociation::UP_SELL); - $assoc = $parent->associations()->type(ProductAssociation::UP_SELL)->get(); + $assoc = $parent->associations()->type(\Lunar\Base\Enums\ProductAssociation::UP_SELL)->get(); expect($assoc)->toHaveCount(2); }); @@ -418,12 +418,12 @@ $parent = Product::factory()->create(); $target = Product::factory()->create(); - $parent->associate($target, 'custom-type'); + $parent->associate($target, \Lunar\Base\Enums\ProductAssociation::UP_SELL); - $assoc = $parent->associations()->type('custom-type')->get(); + $assoc = $parent->associations()->type(\Lunar\Base\Enums\ProductAssociation::UP_SELL)->get(); - expect($assoc)->toHaveCount(1); - expect($assoc->first()->type)->toEqual('custom-type'); + expect($assoc)->toHaveCount(1) + ->and($assoc->first()->type)->toEqual(\Lunar\Base\Enums\ProductAssociation::UP_SELL->value); }); test('can remove all associations', function () { @@ -450,21 +450,23 @@ ProductAssociation::factory()->create([ 'product_parent_id' => $parent, 'product_target_id' => $target, - 'type' => 'cross-sell', + 'type' => \Lunar\Base\Enums\ProductAssociation::CROSS_SELL->value, ]); ProductAssociation::factory()->create([ 'product_parent_id' => $parent, 'product_target_id' => $target, - 'type' => 'up-sell', + 'type' => \Lunar\Base\Enums\ProductAssociation::UP_SELL->value, ]); expect($parent->refresh()->associations)->toHaveCount(2); - $parent->dissociate($target, 'cross-sell'); + $parent->dissociate($target, \Lunar\Base\Enums\ProductAssociation::CROSS_SELL); - expect($parent->refresh()->associations)->toHaveCount(1); - expect($parent->refresh()->associations->first()->type)->toEqual('up-sell'); + expect($parent->refresh()->associations)->toHaveCount(1) + ->and($parent->refresh()->associations->first()->type)->toEqual( + \Lunar\Base\Enums\ProductAssociation::UP_SELL->value + ); }); test('can have collections relationship', function () { @@ -472,11 +474,11 @@ $product = Product::factory()->create(); $product->collections()->sync($collection); - expect($product->collections)->toBeInstanceOf(EloquentCollection::class); - expect($product->collections)->toHaveCount(1); - expect($product->collections->first())->toBeInstanceOf(Collection::class); - expect($product->collections->first()->pivot)->not->toBeNull(); - expect($product->collections->first()->pivot->position)->not->toBeNull(); + expect($product->collections)->toBeInstanceOf(EloquentCollection::class) + ->and($product->collections)->toHaveCount(1) + ->and($product->collections->first())->toBeInstanceOf(Collection::class) + ->and($product->collections->first()->pivot)->not->toBeNull() + ->and($product->collections->first()->pivot->position)->not->toBeNull(); }); test('can retrieve prices', function () { From a5c4b86f02b3ca81de2622eda8009e2609836553 Mon Sep 17 00:00:00 2001 From: somegooser <49899368+somegooser@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:08:44 +0100 Subject: [PATCH 43/50] Fix table prefix duplication in multi-level model inheritance (#2353) When extending a model multiple times (e.g., CustomProduct extends Product extends BaseModel), the table prefix was being applied multiple times, resulting in table names like "lunar_lunar_products" instead of "lunar_products". **Problem:** The `HasModelExtending::getTable()` method instantiated parent classes recursively with `(new $parentClass)->table`, causing the BaseModel constructor to apply the prefix at each inheritance level. **Solution:** - Added `resolveRootModelClass()` to find the root Lunar model in the inheritance chain - Added `resolveBaseTableName()` to get the base table name using reflection without triggering the constructor - Modified `getTable()` to resolve the root model and apply the prefix only once **Test Coverage:** Added test case for multi-level inheritance to ensure prefix is not duplicated across multiple levels of model extension. --- .../src/Base/Traits/HasModelExtending.php | 40 ++++++++++++++++++- .../Stubs/Models/Custom/DeepCustomProduct.php | 13 ++++++ .../Base/Traits/HasModelExtendingTest.php | 19 +++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/core/Stubs/Models/Custom/DeepCustomProduct.php diff --git a/packages/core/src/Base/Traits/HasModelExtending.php b/packages/core/src/Base/Traits/HasModelExtending.php index 342ad0c456..eaa886e44d 100644 --- a/packages/core/src/Base/Traits/HasModelExtending.php +++ b/packages/core/src/Base/Traits/HasModelExtending.php @@ -9,6 +9,7 @@ use Illuminate\Support\Str; use Lunar\Base\BaseModel; use Lunar\Facades\ModelManifest; +use ReflectionClass; trait HasModelExtending { @@ -67,7 +68,44 @@ public function getTable() { $parentClass = get_parent_class($this); - return $parentClass == BaseModel::class ? parent::getTable() : (new $parentClass)->table; + if ($parentClass === BaseModel::class) { + return parent::getTable(); + } + + if (!empty($this->table)) { + return $this->table; + } + + $rootModelClass = $this->resolveRootModelClass($parentClass); + + return $this->resolveBaseTableName($rootModelClass); + } + + protected function resolveRootModelClass(string $childClass): string + { + $parentClass = get_parent_class($childClass); + + while ($parentClass && $parentClass !== BaseModel::class) { + $childClass = $parentClass; + $parentClass = get_parent_class($parentClass); + } + + return $childClass; + } + + protected function resolveBaseTableName(string $modelClass): string + { + $reflection = new ReflectionClass($modelClass); + $defaultProperties = $reflection->getDefaultProperties(); + + if (!empty($defaultProperties['table'])) { + return $defaultProperties['table']; + } + + /** @var Model $modelInstance */ + $modelInstance = $reflection->newInstanceWithoutConstructor(); + + return $modelInstance->getTable(); } public static function __callStatic($method, $parameters) diff --git a/tests/core/Stubs/Models/Custom/DeepCustomProduct.php b/tests/core/Stubs/Models/Custom/DeepCustomProduct.php new file mode 100644 index 0000000000..6e60c39865 --- /dev/null +++ b/tests/core/Stubs/Models/Custom/DeepCustomProduct.php @@ -0,0 +1,13 @@ +getTable()) + ->toBe($lunarProduct->getTable()) + ->and($deepCustomProduct->getTable()) + ->toBe($lunarProduct->getTable()); + + // Verify the table name is correctly prefixed (not duplicated) + $expectedTable = config('lunar.database.table_prefix').'products'; + expect($deepCustomProduct->getTable())->toBe($expectedTable); + + // Ensure prefix is not duplicated + expect($deepCustomProduct->getTable())->not->toContain(config('lunar.database.table_prefix').config('lunar.database.table_prefix')); +}); From 28c17940fd60a0a3fbfb03695b5c8f66f44e650f Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 8 Dec 2025 15:08:59 +0000 Subject: [PATCH 44/50] Fix discount conditions on collections (#2345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR builds on the work done in https://github.com/lunarphp/lunar/pull/2250 It: 1. Adds a brand check 2. Seperates collections into its own relation manager to allow it to continue to use collection_discount table It's breaking, but for v2 we should consolidate everything into the discountable table - I missed these when I did this initial piece of work. It would make the UI much more streamlined and from a data point of view reduce the redundancy of the tables. --------- Co-authored-by: fredmarmillod Co-authored-by: Author Co-authored-by: Frédéric Marmillod <9134609+fredmarmillod@users.noreply.github.com> Co-authored-by: Glenn Jacobs --- packages/admin/resources/lang/en/discount.php | 18 +- .../Filament/Resources/DiscountResource.php | 4 +- .../DiscountResource/Pages/EditDiscount.php | 6 +- .../CollectionConditionRelationManager.php | 67 +++++ .../CollectionLimitationRelationManager.php | 4 + .../ProductConditionRelationManager.php | 14 +- .../core/src/Managers/DiscountManager.php | 5 + packages/core/src/Models/Discount.php | 41 ++- tests/core/Unit/Models/DiscountTest.php | 254 ++++++++++++++++++ 9 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionConditionRelationManager.php diff --git a/packages/admin/resources/lang/en/discount.php b/packages/admin/resources/lang/en/discount.php index 57ec0aff8a..3791acf136 100644 --- a/packages/admin/resources/lang/en/discount.php +++ b/packages/admin/resources/lang/en/discount.php @@ -283,8 +283,8 @@ ], ], 'conditions' => [ - 'title' => 'Conditions', - 'description' => 'Select the conditions required for the discount to apply.', + 'title' => 'Product and Variant Conditions', + 'description' => 'Select the product or variant conditions required for the discount to apply.', 'actions' => [ 'attach' => [ 'label' => 'Add Condition', @@ -317,6 +317,20 @@ ], ], ], + 'collection_conditions' => [ + 'title' => 'Collection Conditions', + 'description' => 'Select the collection conditions required for the discount to apply.', + 'actions' => [ + 'attach' => [ + 'label' => 'Add Condition', + ], + ], + 'table' => [ + 'name' => [ + 'label' => 'Name', + ], + ], + ], 'productvariants' => [ 'title' => 'Product Variants', 'description' => 'Select which product variants this discount should be limited to.', diff --git a/packages/admin/src/Filament/Resources/DiscountResource.php b/packages/admin/src/Filament/Resources/DiscountResource.php index c9a1f3b3c6..df2eff8934 100644 --- a/packages/admin/src/Filament/Resources/DiscountResource.php +++ b/packages/admin/src/Filament/Resources/DiscountResource.php @@ -13,6 +13,7 @@ use Lunar\Admin\Base\LunarPanelDiscountInterface; use Lunar\Admin\Filament\Resources\DiscountResource\Pages; use Lunar\Admin\Filament\Resources\DiscountResource\RelationManagers\BrandLimitationRelationManager; +use Lunar\Admin\Filament\Resources\DiscountResource\RelationManagers\CollectionConditionRelationManager; use Lunar\Admin\Filament\Resources\DiscountResource\RelationManagers\CollectionLimitationRelationManager; use Lunar\Admin\Filament\Resources\DiscountResource\RelationManagers\CustomerLimitationRelationManager; use Lunar\Admin\Filament\Resources\DiscountResource\RelationManagers\ProductConditionRelationManager; @@ -419,8 +420,7 @@ protected static function getDefaultRelations(): array CustomerLimitationRelationManager::class, ProductRewardRelationManager::class, ProductConditionRelationManager::class, - ProductRewardRelationManager::class, - ProductConditionRelationManager::class, + CollectionConditionRelationManager::class, ]; } diff --git a/packages/admin/src/Filament/Resources/DiscountResource/Pages/EditDiscount.php b/packages/admin/src/Filament/Resources/DiscountResource/Pages/EditDiscount.php index 8e43707d91..f84c61e340 100644 --- a/packages/admin/src/Filament/Resources/DiscountResource/Pages/EditDiscount.php +++ b/packages/admin/src/Filament/Resources/DiscountResource/Pages/EditDiscount.php @@ -3,6 +3,7 @@ namespace Lunar\Admin\Filament\Resources\DiscountResource\Pages; use Filament\Actions; +use Filament\Resources\RelationManagers\RelationGroup; use Lunar\Admin\Base\LunarPanelDiscountInterface; use Lunar\Admin\Filament\Resources\DiscountResource; use Lunar\Admin\Support\Pages\BaseEditRecord; @@ -87,7 +88,10 @@ public function getRelationManagers(): array $managers = []; if ($this->record->type == BuyXGetY::class) { - $managers[] = DiscountResource\RelationManagers\ProductConditionRelationManager::class; + $managers[] = RelationGroup::make('Conditions', [ + DiscountResource\RelationManagers\ProductConditionRelationManager::class, + DiscountResource\RelationManagers\CollectionConditionRelationManager::class, + ]); $managers[] = DiscountResource\RelationManagers\ProductRewardRelationManager::class; } diff --git a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionConditionRelationManager.php b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionConditionRelationManager.php new file mode 100644 index 0000000000..3eaa8fe48a --- /dev/null +++ b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionConditionRelationManager.php @@ -0,0 +1,67 @@ +heading( + __('lunarpanel::discount.relationmanagers.collection_conditions.title') + ) + ->description( + __('lunarpanel::discount.relationmanagers.collection_conditions.description') + ) + ->paginated(false) + ->modifyQueryUsing( + fn ($query) => $query->whereIn($prefix.'collection_discount.type', ['condition']) + ) + ->headerActions([ + Tables\Actions\AttachAction::make()->form(fn (Tables\Actions\AttachAction $action): array => [ + $action->getRecordSelect(), + Forms\Components\Hidden::make('type')->default('condition'), + ])->recordTitle(function ($record) { + return $record->attr('name'); + })->recordSelectSearchColumns(['attribute_data->name']) + ->preloadRecordSelect() + ->label( + __('lunarpanel::discount.relationmanagers.collection_conditions.actions.attach.label') + ), + ])->columns([ + Tables\Columns\TextColumn::make('id') + ->label( + __('lunarpanel::discount.relationmanagers.collection_conditions.table.name.label') + ) + ->formatStateUsing( + fn (Model $record) => $record->attr('name') + ), + ])->actions([ + Tables\Actions\DeleteAction::make(), + ])->bulkActions([ + Tables\Actions\DeleteBulkAction::make(), + ]); + } +} diff --git a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionLimitationRelationManager.php b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionLimitationRelationManager.php index 57120c3a0f..eae687ff26 100644 --- a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionLimitationRelationManager.php +++ b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/CollectionLimitationRelationManager.php @@ -27,11 +27,15 @@ public function isReadOnly(): bool public function getDefaultTable(Table $table): Table { + $prefix = config('lunar.database.table_prefix'); return $table ->description( __('lunarpanel::discount.relationmanagers.collections.description') ) + ->modifyQueryUsing( + fn ($query) => $query->whereIn($prefix.'collection_discount.type', ['limitation', 'exclusion']) + ) ->paginated(false) ->headerActions([ Tables\Actions\AttachAction::make()->form(fn (Tables\Actions\AttachAction $action): array => [ diff --git a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/ProductConditionRelationManager.php b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/ProductConditionRelationManager.php index 178cb0c26e..2ddfddd580 100644 --- a/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/ProductConditionRelationManager.php +++ b/packages/admin/src/Filament/Resources/DiscountResource/RelationManagers/ProductConditionRelationManager.php @@ -8,8 +8,6 @@ use Illuminate\Database\Eloquent\Model; use Lunar\Admin\Support\RelationManagers\BaseRelationManager; use Lunar\Admin\Support\Tables\Columns\ThumbnailImageColumn; -use Lunar\Models\Collection; -use Lunar\Models\Contracts\Collection as CollectionContract; use Lunar\Models\Contracts\Product as ProductContract; use Lunar\Models\Contracts\ProductVariant as ProductVariantContract; use Lunar\Models\Product; @@ -33,6 +31,7 @@ public function isReadOnly(): bool public function getDefaultTable(Table $table): Table { + $prefix = config('lunar.database.table_prefix'); return $table ->heading( @@ -44,7 +43,7 @@ public function getDefaultTable(Table $table): Table ->paginated(false) ->modifyQueryUsing( fn ($query) => $query->whereIn('type', ['condition']) - ->whereIn('discountable_type', [Collection::morphName(), Product::morphName(), ProductVariant::morphName()]) + ->whereIn('discountable_type', [Product::morphName(), ProductVariant::morphName()]) ->whereHas('discountable') ) ->headerActions([ @@ -52,15 +51,6 @@ public function getDefaultTable(Table $table): Table Forms\Components\MorphToSelect::make('discountable') ->searchable(true) ->types([ - Forms\Components\MorphToSelect\Type::make(Collection::modelClass()) - ->titleAttribute('name.en') - ->getSearchResultsUsing(static function (Forms\Components\Select $component, string $search): array { - return get_search_builder(Collection::modelClass(), $search) - ->get() - ->mapWithKeys(fn (CollectionContract $record): array => [$record->getKey() => $record->attr('name')]) - ->all(); - }), - Forms\Components\MorphToSelect\Type::make(Product::modelClass()) ->titleAttribute('name.en') ->getSearchResultsUsing(static function (Forms\Components\Select $component, string $search): array { diff --git a/packages/core/src/Managers/DiscountManager.php b/packages/core/src/Managers/DiscountManager.php index aa2509feae..080a8a242f 100644 --- a/packages/core/src/Managers/DiscountManager.php +++ b/packages/core/src/Managers/DiscountManager.php @@ -159,6 +159,11 @@ function ($query, $value) { $value->lines->map(fn ($line) => $line->purchasable?->product?->collections?->pluck('id'))->flatten()->filter()->values(), ['condition'] ) + ) + ->orWhere(fn ($query) => $query->brands( + $value->lines->map(fn ($line) => $line->purchasable?->product?->brand_id)->flatten()->filter()->values(), + ['condition'] + ) ); }); } diff --git a/packages/core/src/Models/Discount.php b/packages/core/src/Models/Discount.php index b8bf7f3dbe..8d5822bc49 100644 --- a/packages/core/src/Models/Discount.php +++ b/packages/core/src/Models/Discount.php @@ -188,15 +188,36 @@ public function scopeCollections(Builder $query, iterable $collectionIds = [], a } $types = Arr::wrap($types); + $prefix = config('lunar.database.table_prefix'); return $query->where( - fn ($subQuery) => $subQuery->whereDoesntHave('discountables', fn ($query) => $query->when($types, fn ($query) => $query->whereIn('type', $types))) - ->orWhereHas('discountables', - fn ($relation) => $relation->whereIn('discountable_id', $collectionIds) - ->whereDiscountableType(Collection::morphName()) + fn ($subQuery) => $subQuery->whereDoesntHave('collections', fn ($query) => $query->when($types, fn ($query) => $query->whereIn("{$prefix}collection_discount.type", $types))) + ->orWhereHas('collections', + fn ($relation) => $relation->whereIn('collection_id', $collectionIds) + ->when( + $types, + fn ($query) => $query->whereIn("{$prefix}collection_discount.type", $types) + ) + ) + ); + } + + public function scopeBrands(Builder $query, iterable $brandIds = [], array|string $types = []): Builder + { + if (is_array($brandIds)) { + $brandIds = collect($brandIds); + } + + $types = Arr::wrap($types); + $prefix = config('lunar.database.table_prefix'); + + return $query->where( + fn ($subQuery) => $subQuery->whereDoesntHave('brands', fn ($query) => $query->when($types, fn ($query) => $query->whereIn("{$prefix}brand_discount.type", $types))) + ->orWhereHas('brands', + fn ($relation) => $relation->whereIn('brand_id', $brandIds) ->when( $types, - fn ($query) => $query->whereIn('type', $types) + fn ($query) => $query->whereIn("{$prefix}brand_discount.type", $types) ) ) ); @@ -209,15 +230,16 @@ public function scopeProducts(Builder $query, iterable $productIds = [], array|s } $types = Arr::wrap($types); + $prefix = config('lunar.database.table_prefix'); return $query->where( - fn ($subQuery) => $subQuery->whereDoesntHave('discountables', fn ($query) => $query->when($types, fn ($query) => $query->whereIn('type', $types))) + fn ($subQuery) => $subQuery->whereDoesntHave('discountables', fn ($query) => $query->whereDiscountableType(Product::morphName())->when($types, fn ($query) => $query->whereIn("{$prefix}discountables.type", $types))) ->orWhereHas('discountables', fn ($relation) => $relation->whereIn('discountable_id', $productIds) ->whereDiscountableType(Product::morphName()) ->when( $types, - fn ($query) => $query->whereIn('type', $types) + fn ($query) => $query->whereIn("{$prefix}discountables.type", $types) ) ) ); @@ -230,15 +252,16 @@ public function scopeProductVariants(Builder $query, iterable $variantIds = [], } $types = Arr::wrap($types); + $prefix = config('lunar.database.table_prefix'); return $query->where( - fn ($subQuery) => $subQuery->whereDoesntHave('discountables', fn ($query) => $query->when($types, fn ($query) => $query->whereIn('type', $types))) + fn ($subQuery) => $subQuery->whereDoesntHave('discountables', fn ($query) => $query->whereDiscountableType(ProductVariant::morphName())->when($types, fn ($query) => $query->whereIn("{$prefix}discountables.type", $types))) ->orWhereHas('discountables', fn ($relation) => $relation->whereIn('discountable_id', $variantIds) ->whereDiscountableType(ProductVariant::morphName()) ->when( $types, - fn ($query) => $query->whereIn('type', $types) + fn ($query) => $query->whereIn("{$prefix}discountables.type", $types) ) ) ); diff --git a/tests/core/Unit/Models/DiscountTest.php b/tests/core/Unit/Models/DiscountTest.php index 14e1f2db67..44a0625fab 100644 --- a/tests/core/Unit/Models/DiscountTest.php +++ b/tests/core/Unit/Models/DiscountTest.php @@ -1,7 +1,12 @@ $discount->id == $discountC->id ))->toBeNull(); }); + +test('can apply collections scope', function () { + $collectionA = Collection::factory()->create(); + $collectionB = Collection::factory()->create(); + $collectionC = Collection::factory()->create(); + + // Discount with collection relationships using pivot table + $discountWithCollectionA = Discount::factory()->create(); + $discountWithCollectionA->collections()->attach($collectionA->id, ['type' => 'limitation']); + + // Discount with multiple collections and different types + $discountWithMultipleCollections = Discount::factory()->create(); + $discountWithMultipleCollections->collections()->attach($collectionA->id, ['type' => 'condition']); + $discountWithMultipleCollections->collections()->attach($collectionB->id, ['type' => 'limitation']); + + // Discount with collection of different type + $discountWithCollectionBReward = Discount::factory()->create(); + $discountWithCollectionBReward->collections()->attach($collectionB->id, ['type' => 'reward']); + + // Discount with no collections + $discountWithoutCollections = Discount::factory()->create(); + + // Test with specific collection IDs - should return discounts that either have NO collections OR have matching collections + $discounts = Discount::query()->collections([$collectionA->id])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithCollectionA))->toBeTrue(); // Has matching collection + expect($discounts->contains($discountWithMultipleCollections))->toBeTrue(); // Has matching collection + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); // No collections at all + expect($discounts->contains($discountWithCollectionBReward))->toBeFalse(); // Has collections but not matching + + // Test with different collection ID + $discounts = Discount::query()->collections([$collectionC->id])->get(); + expect($discounts)->toHaveCount(1); + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); + expect($discounts->contains($discountWithCollectionA))->toBeFalse(); + expect($discounts->contains($discountWithMultipleCollections))->toBeFalse(); + expect($discounts->contains($discountWithCollectionBReward))->toBeFalse(); + + // Test with empty array (should return all discounts without any collection restrictions) + $discounts = Discount::query()->collections([])->get(); + expect($discounts)->toHaveCount(1); + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); + expect($discounts->contains($discountWithCollectionA))->toBeFalse(); + expect($discounts->contains($discountWithMultipleCollections))->toBeFalse(); + expect($discounts->contains($discountWithCollectionBReward))->toBeFalse(); + + // Test with types filter - limitation type + $discounts = Discount::query()->collections([$collectionA->id], ['limitation'])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithCollectionA))->toBeTrue(); // Has limitation type for collectionA + expect($discounts->contains($discountWithMultipleCollections))->toBeFalse(); // Has limitation type collections but not for collectionA + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); // No limitation type collections + expect($discounts->contains($discountWithCollectionBReward))->toBeTrue(); // Doesn't have limitation type collections + + // Test with types filter - reward type + $discounts = Discount::query()->collections([$collectionB->id], ['reward'])->get(); + expect($discounts)->toHaveCount(4); + expect($discounts->contains($discountWithCollectionBReward))->toBeTrue(); // Has reward type for collectionB + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); // No reward type collections + expect($discounts->contains($discountWithCollectionA))->toBeTrue(); // Doesn't have reward type collections + expect($discounts->contains($discountWithMultipleCollections))->toBeTrue(); // Doesn't have reward type collections + + // Test with multiple types + $discounts = Discount::query()->collections([$collectionA->id], ['limitation', 'condition'])->get(); + expect($discounts)->toHaveCount(4); + expect($discounts->contains($discountWithCollectionA))->toBeTrue(); // Has limitation type for collectionA + expect($discounts->contains($discountWithMultipleCollections))->toBeTrue(); // Has condition type for collectionA + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); // No limitation/condition type collections + expect($discounts->contains($discountWithCollectionBReward))->toBeTrue(); // Doesn't have limitation/condition type collections + + // Test with string type instead of array + $discounts = Discount::query()->collections([$collectionA->id], 'limitation')->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithCollectionA))->toBeTrue(); // Has limitation type for collectionA + expect($discounts->contains($discountWithMultipleCollections))->toBeFalse(); // Has limitation type collections but not for collectionA + expect($discounts->contains($discountWithoutCollections))->toBeTrue(); // No limitation type collections + expect($discounts->contains($discountWithCollectionBReward))->toBeTrue(); // Doesn't have limitation type collections +}); + +test('can apply brands scope', function () { + $brandA = Brand::factory()->create(); + $brandB = Brand::factory()->create(); + $brandC = Brand::factory()->create(); + + // Discount with brand relationships using pivot table + $discountWithBrandA = Discount::factory()->create(); + $discountWithBrandA->brands()->attach($brandA->id, ['type' => 'limitation']); + + // Discount with multiple brands and different types + $discountWithMultipleBrands = Discount::factory()->create(); + $discountWithMultipleBrands->brands()->attach($brandA->id, ['type' => 'condition']); + $discountWithMultipleBrands->brands()->attach($brandB->id, ['type' => 'limitation']); + + // Discount with brand of different type + $discountWithBrandBReward = Discount::factory()->create(); + $discountWithBrandBReward->brands()->attach($brandB->id, ['type' => 'reward']); + + // Discount with no brands + $discountWithoutBrands = Discount::factory()->create(); + + // Test with specific brand IDs - should return discounts that either have NO brands OR have matching brands + $discounts = Discount::query()->brands([$brandA->id])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithBrandA))->toBeTrue(); // Has matching brand + expect($discounts->contains($discountWithMultipleBrands))->toBeTrue(); // Has matching brand + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); // No brands at all + expect($discounts->contains($discountWithBrandBReward))->toBeFalse(); // Has brands but not matching + + // Test with different brand ID + $discounts = Discount::query()->brands([$brandC->id])->get(); + expect($discounts)->toHaveCount(1); + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); + expect($discounts->contains($discountWithBrandA))->toBeFalse(); + expect($discounts->contains($discountWithMultipleBrands))->toBeFalse(); + expect($discounts->contains($discountWithBrandBReward))->toBeFalse(); + + // Test with empty array (should return all discounts without any brand restrictions) + $discounts = Discount::query()->brands([])->get(); + expect($discounts)->toHaveCount(1); + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); + expect($discounts->contains($discountWithBrandA))->toBeFalse(); + expect($discounts->contains($discountWithMultipleBrands))->toBeFalse(); + expect($discounts->contains($discountWithBrandBReward))->toBeFalse(); + + // Test with types filter - limitation type + $discounts = Discount::query()->brands([$brandA->id], ['limitation'])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithBrandA))->toBeTrue(); // Has limitation type for brandA + expect($discounts->contains($discountWithMultipleBrands))->toBeFalse(); // Has limitation type brands but not for brandA + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); // No limitation type brands + expect($discounts->contains($discountWithBrandBReward))->toBeTrue(); // Doesn't have limitation type brands + + // Test with types filter - reward type + $discounts = Discount::query()->brands([$brandB->id], ['reward'])->get(); + expect($discounts)->toHaveCount(4); + expect($discounts->contains($discountWithBrandBReward))->toBeTrue(); // Has reward type for brandB + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); // No reward type brands + expect($discounts->contains($discountWithBrandA))->toBeTrue(); // Doesn't have reward type brands + expect($discounts->contains($discountWithMultipleBrands))->toBeTrue(); // Doesn't have reward type brands + + // Test with multiple types + $discounts = Discount::query()->brands([$brandA->id], ['limitation', 'condition'])->get(); + expect($discounts)->toHaveCount(4); + expect($discounts->contains($discountWithBrandA))->toBeTrue(); // Has limitation type for brandA + expect($discounts->contains($discountWithMultipleBrands))->toBeTrue(); // Has condition type for brandA + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); // No limitation/condition type brands + expect($discounts->contains($discountWithBrandBReward))->toBeTrue(); // Doesn't have limitation/condition type brands + + // Test with string type instead of array + $discounts = Discount::query()->brands([$brandA->id], 'limitation')->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithBrandA))->toBeTrue(); // Has limitation type for brandA + expect($discounts->contains($discountWithMultipleBrands))->toBeFalse(); // Has limitation type brands but not for brandA + expect($discounts->contains($discountWithoutBrands))->toBeTrue(); // No limitation type brands + expect($discounts->contains($discountWithBrandBReward))->toBeTrue(); // Doesn't have limitation type brands +}); + +test('can apply products scope', function () { + $productA = Product::factory()->create(); + $productB = Product::factory()->create(); + $collection = Collection::factory()->create(); + + // Discount with product discountables + $discountWithProducts = Discount::factory()->create(); + $discountWithProducts->discountables()->create([ + 'discountable_type' => Product::morphName(), + 'discountable_id' => $productA->id, + 'type' => 'limitation', + ]); + + // Discount with collection discountables (different type) + $discountWithCollections = Discount::factory()->create(); + $discountWithCollections->discountables()->create([ + 'discountable_type' => Collection::morphName(), + 'discountable_id' => $collection->id, + 'type' => 'limitation', + ]); + + // Discount with no discountables + $discountWithoutDiscountables = Discount::factory()->create(); + + // Test with specific product IDs + $discounts = Discount::query()->products([$productA->id])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithProducts))->toBeTrue(); // Matches product + expect($discounts->contains($discountWithCollections))->toBeTrue(); // No product restrictions + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); // No product restrictions + + // Test with different product ID + $discounts = Discount::query()->products([$productB->id])->get(); + expect($discounts)->toHaveCount(2); + expect($discounts->contains($discountWithCollections))->toBeTrue(); + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); + expect($discounts->contains($discountWithProducts))->toBeFalse(); // Doesn't match product + + // Test with empty array + $discounts = Discount::query()->products([])->get(); + expect($discounts)->toHaveCount(2); + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); + expect($discounts->contains($discountWithCollections))->toBeTrue(); + expect($discounts->contains($discountWithProducts))->toBeFalse(); +}); + +test('can apply product variants scope', function () { + $product = Product::factory()->create(); + $variantA = ProductVariant::factory()->create(['product_id' => $product->id]); + $variantB = ProductVariant::factory()->create(['product_id' => $product->id]); + $collection = Collection::factory()->create(); + + // Discount with variant discountables + $discountWithVariants = Discount::factory()->create(); + $discountWithVariants->discountables()->create([ + 'discountable_type' => ProductVariant::morphName(), + 'discountable_id' => $variantA->id, + 'type' => 'limitation', + ]); + + // Discount with collection discountables (different type) + $discountWithCollections = Discount::factory()->create(); + $discountWithCollections->discountables()->create([ + 'discountable_type' => Collection::morphName(), + 'discountable_id' => $collection->id, + 'type' => 'limitation', + ]); + + // Discount with no discountables + $discountWithoutDiscountables = Discount::factory()->create(); + + // Test with specific variant IDs + $discounts = Discount::query()->productVariants([$variantA->id])->get(); + expect($discounts)->toHaveCount(3); + expect($discounts->contains($discountWithVariants))->toBeTrue(); // Matches variant + expect($discounts->contains($discountWithCollections))->toBeTrue(); // No variant restrictions + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); // No variant restrictions + + // Test with different variant ID + $discounts = Discount::query()->productVariants([$variantB->id])->get(); + expect($discounts)->toHaveCount(2); + expect($discounts->contains($discountWithCollections))->toBeTrue(); + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); + expect($discounts->contains($discountWithVariants))->toBeFalse(); // Doesn't match variant + + // Test with empty array + $discounts = Discount::query()->productVariants([])->get(); + expect($discounts)->toHaveCount(2); + expect($discounts->contains($discountWithoutDiscountables))->toBeTrue(); + expect($discounts->contains($discountWithCollections))->toBeTrue(); + expect($discounts->contains($discountWithVariants))->toBeFalse(); +}); From 4a6ffe6c6515f57a4494ffbe66a7d6d9dc2b5c88 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 15:40:33 +0000 Subject: [PATCH 45/50] Temporary removal of Meilisearch from split packages GitHub Action --- .github/workflows/split_packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/split_packages.yml b/.github/workflows/split_packages.yml index db7f86c050..9458f6ad15 100644 --- a/.github/workflows/split_packages.yml +++ b/.github/workflows/split_packages.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "meilisearch", "search"] + package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "search"] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 From 99547e810bb9a153574e66141d8628969dbbae8f Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 15:44:17 +0000 Subject: [PATCH 46/50] Update document-facades.yml --- .github/workflows/document-facades.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/document-facades.yml b/.github/workflows/document-facades.yml index a5ce7528f1..c3780a2101 100644 --- a/.github/workflows/document-facades.yml +++ b/.github/workflows/document-facades.yml @@ -2,9 +2,9 @@ name: document-facades on: - push: - branches: - - '*.x' +# push: +# branches: +# - '*.x' workflow_dispatch: permissions: From 9495edd0f6a03c30de8a61889d748c39787a9312 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 15:50:54 +0000 Subject: [PATCH 47/50] Update fix-code-style.yml --- .github/workflows/fix-code-style.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-code-style.yml b/.github/workflows/fix-code-style.yml index 517d722242..695a8097e3 100644 --- a/.github/workflows/fix-code-style.yml +++ b/.github/workflows/fix-code-style.yml @@ -1,7 +1,7 @@ name: fix-code-style on: - push: + pull_request: jobs: fix-code-style: From b88e6796a27dbc8bd26126423b7a7939d19a6436 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Mon, 8 Dec 2025 16:22:34 +0000 Subject: [PATCH 48/50] Update split_packages.yml --- .github/workflows/split_packages.yml | 67 ++++++++-------------------- 1 file changed, 18 insertions(+), 49 deletions(-) diff --git a/.github/workflows/split_packages.yml b/.github/workflows/split_packages.yml index 9458f6ad15..401acbe432 100644 --- a/.github/workflows/split_packages.yml +++ b/.github/workflows/split_packages.yml @@ -1,65 +1,34 @@ -name: Split Packages +name: monorepo-split + on: push: - branches: - - main - - "0.*" - - "1.*" - tags: - - "*" + tags: '*' + jobs: - tag_split_packages: + split-monorepo: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - package: ["admin", "core", "opayo", "paypal", "table-rate-shipping", "stripe", "search"] + package: + - "admin" + - "core" + - "opayo" + - "paypal" + - "table-rate-shipping" + - "stripe" + - "search" steps: - uses: actions/checkout@v4 - - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - coverage: none - - - name: Split ${{ matrix.package }} - uses: "danharrin/monorepo-split-github-action@v2.4.0" - if: "!startsWith(github.ref, 'refs/tags/')" - env: - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - with: - package_directory: "packages/${{ matrix.package }}" - repository_organization: "lunarphp" - repository_name: "${{ matrix.package }}" - user_name: "GitHub Action" - user_email: "action@github.com" - branch: ${GITHUB_REF#refs/heads/} - - - name: Set env - if: "startsWith(github.ref, 'refs/tags/')" - run: echo "GITHUB_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - - name: Split ${{ matrix.package }} - uses: "danharrin/monorepo-split-github-action@v2.4.0" - if: "startsWith(github.ref, 'refs/tags/')" - env: - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - with: - package_directory: "packages/${{ matrix.package }}" - repository_organization: "lunarphp" - repository_name: "${{ matrix.package }}" - user_name: "GitHub Action" - user_email: "action@github.com" - branch: ${GITHUB_TAG%.*} - - - name: Tag ${{ matrix.package }} - if: "startsWith(github.ref, 'refs/tags/')" - uses: "danharrin/monorepo-split-github-action@v2.4.0" + - name: Monorepo Split of ${{ matrix.package }} + uses: danharrin/monorepo-split-github-action@v2.4.0 env: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} with: - tag: ${GITHUB_REF#refs/tags/} package_directory: "packages/${{ matrix.package }}" repository_organization: "lunarphp" repository_name: "${{ matrix.package }}" + branch: 1.x + tag: ${{ github.ref_name }} user_name: "GitHub Action" user_email: "action@github.com" - branch: ${GITHUB_TAG%.*} From c359f3d3d6c8aadf09070f9ee76d8083032dda66 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Thu, 11 Dec 2025 09:39:46 +0000 Subject: [PATCH 49/50] Prevent activity log button being unreachable (#2372) Currently the activity log feed overlaps the submit button, meaning in some cases users cannot submit comments. This PR prevents pointer events on that overlap, but not on the items themselves, so whatever happens you can click the submit button. --------- Co-authored-by: Author --- .../views/livewire/components/activity-log-feed.blade.php | 4 ++-- packages/core/src/Base/Traits/HasModelExtending.php | 4 ++-- tests/core/Stubs/Models/Custom/DeepCustomProduct.php | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php b/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php index f57db7f0f3..a938dd68e4 100644 --- a/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php +++ b/packages/admin/resources/views/livewire/components/activity-log-feed.blade.php @@ -18,7 +18,7 @@ class="inline-block w-8 h-8 rounded-full" />
-
+
@@ -30,7 +30,7 @@ class="inline-block w-8 h-8 rounded-full" /> {{ $log['date']->format('F jS, Y') }}

-
    +
      @foreach ($log['items'] as $item) @php $logUserName = $item['log']->causer ? ($item['log']->causer->fullName ?: $item['log']->causer->name) : null; diff --git a/packages/core/src/Base/Traits/HasModelExtending.php b/packages/core/src/Base/Traits/HasModelExtending.php index eaa886e44d..20790d29c4 100644 --- a/packages/core/src/Base/Traits/HasModelExtending.php +++ b/packages/core/src/Base/Traits/HasModelExtending.php @@ -72,7 +72,7 @@ public function getTable() return parent::getTable(); } - if (!empty($this->table)) { + if (! empty($this->table)) { return $this->table; } @@ -98,7 +98,7 @@ protected function resolveBaseTableName(string $modelClass): string $reflection = new ReflectionClass($modelClass); $defaultProperties = $reflection->getDefaultProperties(); - if (!empty($defaultProperties['table'])) { + if (! empty($defaultProperties['table'])) { return $defaultProperties['table']; } diff --git a/tests/core/Stubs/Models/Custom/DeepCustomProduct.php b/tests/core/Stubs/Models/Custom/DeepCustomProduct.php index 6e60c39865..d312ebe201 100644 --- a/tests/core/Stubs/Models/Custom/DeepCustomProduct.php +++ b/tests/core/Stubs/Models/Custom/DeepCustomProduct.php @@ -8,6 +8,4 @@ * This model extends CustomProduct which already extends \Lunar\Models\Product. * This creates a 3-level inheritance chain to test the table prefix bug fix. */ -class DeepCustomProduct extends CustomProduct -{ -} +class DeepCustomProduct extends CustomProduct {} From 6cddcf5a73963054b7f524dc647172dd323e5545 Mon Sep 17 00:00:00 2001 From: Robert Lester Date: Thu, 11 Dec 2025 04:54:31 -0500 Subject: [PATCH 50/50] Add doctrine/dbal v4 to require (#2371) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0e116cd0ba..290346940e 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "awcodes/shout": "^2.0.4", "barryvdh/laravel-dompdf": "^3.0", "cartalyst/converter": "^9.0|^10", - "doctrine/dbal": "^3.6", + "doctrine/dbal": "^3.6|^4.0", "dompdf/dompdf": "^3.1", "ext-bcmath": "*", "ext-exif": "*",