Skip to content

Commit c804fb7

Browse files
authored
feat(groups): implement product groups (CT-73)
* feat(groups): implement product groups * chore: improve product <-> group managing * chore(groups): add fixed tests * chore(groups): add seeder * chore(groups): tenancy & seeder improvements & fixes * fix: revert to original migration names * style: fix code style * chore: more fixes and tenancy improvements * chore: groups improvements & tenancy edge case fixes * fix: format * refactor: rewrite duplicate methods
1 parent 14a596f commit c804fb7

27 files changed

Lines changed: 1933 additions & 37 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('pim_group', function (Blueprint $table) {
12+
$table->id();
13+
14+
// Add foreign key for tenant if it's configured in the catalogue config
15+
if (config('eclipse-catalogue.tenancy.model')) {
16+
$tenantClass = config('eclipse-catalogue.tenancy.model');
17+
/** @var \Illuminate\Database\Eloquent\Model $tenant */
18+
$tenant = new $tenantClass;
19+
$table->foreignId(config('eclipse-catalogue.tenancy.foreign_key'))
20+
->constrained($tenant->getTable(), $tenant->getKeyName())
21+
->cascadeOnUpdate()
22+
->cascadeOnDelete();
23+
}
24+
25+
$table->string('code', 50)->nullable();
26+
$table->string('name', 100);
27+
$table->boolean('is_active')->default(true);
28+
$table->boolean('is_browsable')->default(false);
29+
$table->timestamps();
30+
31+
// Create unique index on code
32+
if (config('eclipse-catalogue.tenancy.foreign_key')) {
33+
$table->unique([config('eclipse-catalogue.tenancy.foreign_key'), 'code']);
34+
} else {
35+
$table->unique('code');
36+
}
37+
});
38+
}
39+
40+
public function down(): void
41+
{
42+
Schema::dropIfExists('pim_group');
43+
}
44+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::create('pim_group_has_product', function (Blueprint $table) {
12+
$table->foreignId('product_id')
13+
->constrained('catalogue_products', 'id')
14+
->cascadeOnUpdate()
15+
->cascadeOnDelete();
16+
17+
$table->foreignId('group_id')
18+
->constrained('pim_group', 'id')
19+
->cascadeOnUpdate()
20+
->cascadeOnDelete();
21+
22+
$table->integer('sort')->nullable();
23+
24+
$table->primary(['product_id', 'group_id']);
25+
});
26+
}
27+
28+
public function down(): void
29+
{
30+
Schema::dropIfExists('pim_group_has_product');
31+
}
32+
};

database/seeders/CatalogueSeeder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public function run(): void
1313
{
1414
$this->call(CategorySeeder::class);
1515
$this->call(ProductTypeSeeder::class);
16+
$this->call(GroupSeeder::class);
1617
$this->call(PropertySeeder::class);
1718
$this->call(ProductSeeder::class);
1819
}

database/seeders/GroupSeeder.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Seeders;
4+
5+
use Eclipse\Catalogue\Models\Group;
6+
use Illuminate\Database\Seeder;
7+
8+
class GroupSeeder extends Seeder
9+
{
10+
public function run(): void
11+
{
12+
// Create different groups for each tenant
13+
$tenantModel = config('eclipse-catalogue.tenancy.model');
14+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key');
15+
$sites = $tenantModel::all();
16+
17+
// Define various group options
18+
$groupOptions = [
19+
['code' => 'featured', 'name' => 'Featured Products', 'is_browsable' => true],
20+
['code' => 'new-arrivals', 'name' => 'New Arrivals', 'is_browsable' => true],
21+
['code' => 'best-sellers', 'name' => 'Best Sellers', 'is_browsable' => true],
22+
['code' => 'sale', 'name' => 'Sale Items', 'is_browsable' => false],
23+
['code' => 'trending', 'name' => 'Trending Now', 'is_browsable' => true],
24+
['code' => 'staff-picks', 'name' => 'Staff Picks', 'is_browsable' => true],
25+
['code' => 'seasonal', 'name' => 'Seasonal Collection', 'is_browsable' => true],
26+
['code' => 'limited-edition', 'name' => 'Limited Edition', 'is_browsable' => true],
27+
['code' => 'clearance', 'name' => 'Clearance Items', 'is_browsable' => false],
28+
['code' => 'premium', 'name' => 'Premium Selection', 'is_browsable' => true],
29+
['code' => 'eco-friendly', 'name' => 'Eco-Friendly', 'is_browsable' => true],
30+
['code' => 'gift-guide', 'name' => 'Gift Guide', 'is_browsable' => true],
31+
];
32+
33+
foreach ($sites as $site) {
34+
// Randomly select 4-6 groups for each tenant
35+
$numGroups = rand(4, 6);
36+
$selectedGroups = collect($groupOptions)->shuffle()->take($numGroups);
37+
38+
foreach ($selectedGroups as $groupData) {
39+
Group::create([
40+
$tenantFK => $site->id,
41+
'code' => $groupData['code'],
42+
'name' => $groupData['name'],
43+
'is_active' => true,
44+
'is_browsable' => $groupData['is_browsable'],
45+
]);
46+
}
47+
}
48+
}
49+
}

database/seeders/ProductSeeder.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Eclipse\Catalogue\Seeders;
44

55
use Eclipse\Catalogue\Models\Category;
6+
use Eclipse\Catalogue\Models\Group;
67
use Eclipse\Catalogue\Models\Product;
78
use Eclipse\Catalogue\Models\ProductData;
89
use Eclipse\Catalogue\Models\ProductType;
@@ -17,6 +18,7 @@ public function run(): void
1718
{
1819
$this->ensureSampleImagesExist();
1920
$this->ensureProductTypesExist();
21+
$this->ensureGroupsExist();
2022

2123
$productTypes = ProductType::all();
2224

@@ -33,7 +35,7 @@ public function run(): void
3335

3436
$products = Product::query()->latest('id')->take(100)->get();
3537

36-
foreach ($products as $product) {
38+
foreach ($products as $index => $product) {
3739
if ($tenantFK && $tenantModel && class_exists($tenantModel)) {
3840
$tenants = $tenantModel::all();
3941
foreach ($tenants as $tenant) {
@@ -50,6 +52,14 @@ public function run(): void
5052
'has_free_delivery' => false,
5153
'category_id' => $categoryId,
5254
]);
55+
56+
// Get groups for this specific tenant
57+
$tenantGroups = Group::where($tenantFK, $tenant->id)->get();
58+
$groupsToAdd = $this->determineGroupsForProduct($index, $tenantGroups);
59+
60+
foreach ($groupsToAdd as $group) {
61+
$group->addProduct($product);
62+
}
5363
}
5464
} else {
5565
$categoryId = Category::query()->inRandomOrder()->value('id');
@@ -60,10 +70,34 @@ public function run(): void
6070
'has_free_delivery' => false,
6171
'category_id' => $categoryId,
6272
]);
73+
74+
// For non-tenant scenarios, use all groups
75+
$groups = Group::all();
76+
$groupsToAdd = $this->determineGroupsForProduct($index, $groups);
77+
foreach ($groupsToAdd as $group) {
78+
$group->addProduct($product);
79+
}
6380
}
6481
}
6582
}
6683

84+
private function determineGroupsForProduct(int $productIndex, $groups): array
85+
{
86+
$groupsToAdd = [];
87+
88+
// Randomly assign 1-3 groups per product
89+
$numGroupsToAdd = rand(1, min(3, $groups->count()));
90+
91+
// Get random groups for this product
92+
$randomGroups = $groups->random($numGroupsToAdd);
93+
94+
foreach ($randomGroups as $group) {
95+
$groupsToAdd[] = $group;
96+
}
97+
98+
return $groupsToAdd;
99+
}
100+
67101
private function ensureSampleImagesExist(): void
68102
{
69103
Storage::disk('public')->makeDirectory('sample-products');
@@ -109,4 +143,13 @@ private function ensureProductTypesExist(): void
109143
$this->call(ProductTypeSeeder::class);
110144
}
111145
}
146+
147+
private function ensureGroupsExist(): void
148+
{
149+
$groups = Group::all();
150+
151+
if ($groups->isEmpty()) {
152+
$this->call(GroupSeeder::class);
153+
}
154+
}
112155
}

resources/lang/en/group.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
return [
4+
'title' => 'Groups',
5+
'fields' => [
6+
'code' => 'Code',
7+
'name' => 'Name',
8+
'is_active' => 'Active',
9+
'is_browsable' => 'Browsable',
10+
],
11+
'table' => [
12+
'columns' => [
13+
'products' => 'Products',
14+
],
15+
'actions' => [
16+
'sort_products' => 'Sort Products',
17+
],
18+
],
19+
];

resources/lang/sl/group.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
return [
4+
'title' => 'Skupine',
5+
'fields' => [
6+
'code' => 'Koda',
7+
'name' => 'Ime',
8+
'is_active' => 'Aktivna',
9+
'is_browsable' => 'Brskanje omogočeno',
10+
],
11+
'table' => [
12+
'columns' => [
13+
'products' => 'Izdelki',
14+
],
15+
'actions' => [
16+
'sort_products' => 'Razvrsti izdelke',
17+
],
18+
],
19+
];

src/Factories/GroupFactory.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Factories;
4+
5+
use Eclipse\Catalogue\Models\Group;
6+
use Illuminate\Database\Eloquent\Factories\Factory;
7+
8+
class GroupFactory extends Factory
9+
{
10+
protected $model = Group::class;
11+
12+
public function definition(): array
13+
{
14+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key', 'site_id');
15+
16+
return [
17+
$tenantFK => null, // Will be set when creating
18+
'code' => $this->faker->unique()->slug(2),
19+
'name' => $this->faker->words(2, true),
20+
'is_active' => true,
21+
'is_browsable' => false,
22+
];
23+
}
24+
25+
public function inactive(): static
26+
{
27+
return $this->state(fn (array $attributes) => [
28+
'is_active' => false,
29+
]);
30+
}
31+
32+
public function browsable(): static
33+
{
34+
return $this->state(fn (array $attributes) => [
35+
'is_browsable' => true,
36+
]);
37+
}
38+
39+
public function notBrowsable(): static
40+
{
41+
return $this->state(fn (array $attributes) => [
42+
'is_browsable' => false,
43+
]);
44+
}
45+
}

0 commit comments

Comments
 (0)