Skip to content

Commit a34ae4c

Browse files
feat(price-lists): implement price list management (#10)
* feat(price-lists): implement price list management * chore(price-lists): format seeder * chore(workflows): fix workflows for contributors * chore(testbench): degrade orchestra/testbench package * chore(price-lists): price list fixes and improvements * refactor(price-lists): refactor price list resource pages * chore(price-lists): fix tenancy issues & refactor * refactor(price-lists): more refactoring * fix(price-lists): fix tests with updated logic * chore(composer): try to re-up the testbench version --------- Co-authored-by: Omer Šabić <omer@datalinx.si>
1 parent b26eab3 commit a34ae4c

21 files changed

Lines changed: 2132 additions & 0 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"bezhansalleh/filament-shield": "^3.3",
4949
"datalinx/php-utils": "^2.5",
5050
"eclipsephp/common": "dev-main",
51+
"eclipsephp/world-plugin": "dev-main",
5152
"filament/filament": "^3.3",
5253
"filament/spatie-laravel-media-library-plugin": "^3.2",
5354
"filament/spatie-laravel-translatable-plugin": "^3.2",
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Factories;
4+
5+
use Eclipse\Catalogue\Models\PriceList;
6+
use Eclipse\Catalogue\Models\PriceListData;
7+
use Illuminate\Database\Eloquent\Factories\Factory;
8+
9+
/**
10+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Eclipse\Catalogue\Models\PriceListData>
11+
*/
12+
class PriceListDataFactory extends Factory
13+
{
14+
protected $model = PriceListData::class;
15+
16+
public function definition(): array
17+
{
18+
$definition = [
19+
'price_list_id' => PriceList::factory(),
20+
'is_active' => $this->faker->boolean(80), // 80% chance of being active
21+
'is_default' => false,
22+
'is_default_purchase' => false,
23+
];
24+
25+
// Add tenant foreign key if configured
26+
if (config('eclipse-catalogue.tenancy.foreign_key')) {
27+
$tenantModel = config('eclipse-catalogue.tenancy.model');
28+
if (class_exists($tenantModel)) {
29+
$definition[config('eclipse-catalogue.tenancy.foreign_key')] = $tenantModel::factory();
30+
}
31+
}
32+
33+
return $definition;
34+
}
35+
36+
public function active(): static
37+
{
38+
return $this->state(fn (array $attributes) => [
39+
'is_active' => true,
40+
]);
41+
}
42+
43+
public function inactive(): static
44+
{
45+
return $this->state(fn (array $attributes) => [
46+
'is_active' => false,
47+
]);
48+
}
49+
50+
public function defaultSelling(): static
51+
{
52+
return $this->state(fn (array $attributes) => [
53+
'is_default' => true,
54+
'is_default_purchase' => false,
55+
]);
56+
}
57+
58+
public function defaultPurchase(): static
59+
{
60+
return $this->state(fn (array $attributes) => [
61+
'is_default' => false,
62+
'is_default_purchase' => true,
63+
]);
64+
}
65+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Factories;
4+
5+
use Eclipse\Catalogue\Models\PriceList;
6+
use Eclipse\World\Models\Currency;
7+
use Illuminate\Database\Eloquent\Factories\Factory;
8+
9+
/**
10+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Eclipse\Catalogue\Models\PriceList>
11+
*/
12+
class PriceListFactory extends Factory
13+
{
14+
protected $model = PriceList::class;
15+
16+
public function definition(): array
17+
{
18+
// Get existing currencies or create a default one
19+
$currencies = Currency::all();
20+
if ($currencies->isEmpty()) {
21+
Currency::create(['id' => 'EUR', 'name' => 'Euro', 'is_active' => true]);
22+
$currencies = Currency::all();
23+
}
24+
25+
return [
26+
'currency_id' => $currencies->random()->id,
27+
'name' => $this->faker->words(2, true).' Price List',
28+
'code' => strtoupper($this->faker->unique()->lexify('PL???')),
29+
'tax_included' => $this->faker->boolean(),
30+
'notes' => $this->faker->optional()->sentence(),
31+
];
32+
}
33+
34+
public function withTaxIncluded(): static
35+
{
36+
return $this->state(fn (array $attributes) => [
37+
'tax_included' => true,
38+
]);
39+
}
40+
41+
public function withoutTax(): static
42+
{
43+
return $this->state(fn (array $attributes) => [
44+
'tax_included' => false,
45+
]);
46+
}
47+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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_price_lists', function (Blueprint $table) {
12+
$table->id();
13+
$table->string('currency_id', 3);
14+
$table->foreign('currency_id')
15+
->references('id')
16+
->on('world_currencies')
17+
->cascadeOnUpdate()
18+
->restrictOnDelete();
19+
$table->string('name');
20+
$table->string('code')->nullable();
21+
$table->boolean('tax_included');
22+
$table->string('notes')->nullable();
23+
$table->timestamps();
24+
$table->softDeletes();
25+
});
26+
27+
// More price list data, optionally in tenant-scope
28+
Schema::create('pim_price_list_data', function (Blueprint $table) {
29+
$table->id();
30+
$table->foreignId('price_list_id')
31+
->constrained('pim_price_lists')
32+
->cascadeOnUpdate()
33+
->cascadeOnDelete();
34+
35+
// Add foreign key for tenant if it's configured in the catalogue config
36+
if (config('eclipse-catalogue.tenancy.model')) {
37+
$tenantClass = config('eclipse-catalogue.tenancy.model');
38+
/** @var \Illuminate\Database\Eloquent\Model $tenant */
39+
$tenant = new $tenantClass;
40+
$table->foreignId(config('eclipse-catalogue.tenancy.foreign_key'))
41+
->constrained($tenant->getTable(), $tenant->getKeyName())
42+
->cascadeOnUpdate()
43+
->cascadeOnDelete();
44+
}
45+
46+
$table->boolean('is_active')->default(true);
47+
$table->boolean('is_default')->default(false);
48+
$table->boolean('is_default_purchase')->default(false);
49+
});
50+
}
51+
52+
public function down(): void
53+
{
54+
Schema::dropIfExists('pim_price_list_data');
55+
Schema::dropIfExists('pim_price_lists');
56+
}
57+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Seeders;
4+
5+
use Eclipse\Catalogue\Models\PriceList;
6+
use Eclipse\Catalogue\Models\PriceListData;
7+
use Eclipse\World\Models\Currency;
8+
use Illuminate\Database\Seeder;
9+
10+
class PriceListSeeder extends Seeder
11+
{
12+
/**
13+
* Run the database seeds.
14+
*/
15+
public function run(): void
16+
{
17+
// Ensure we have at least one currency
18+
if (Currency::count() === 0) {
19+
Currency::create(['id' => 'EUR', 'name' => 'Euro', 'is_active' => true]);
20+
}
21+
22+
// Create price lists using factory
23+
$priceLists = PriceList::factory()
24+
->count(3)
25+
->create();
26+
27+
// Create price list data for each price list
28+
foreach ($priceLists as $index => $priceList) {
29+
// Create data for all tenants if tenancy is enabled
30+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key');
31+
$tenantModel = config('eclipse-catalogue.tenancy.model');
32+
33+
if ($tenantFK && $tenantModel && class_exists($tenantModel)) {
34+
$tenants = $tenantModel::all();
35+
36+
foreach ($tenants as $tenant) {
37+
PriceListData::factory()->create([
38+
'price_list_id' => $priceList->id,
39+
$tenantFK => $tenant->id,
40+
'is_active' => true,
41+
'is_default' => $index === 0, // First price list is default selling
42+
'is_default_purchase' => false,
43+
]);
44+
}
45+
} else {
46+
// No tenancy - create single record
47+
PriceListData::factory()->create([
48+
'price_list_id' => $priceList->id,
49+
'is_active' => true,
50+
'is_default' => $index === 0, // First price list is default selling
51+
'is_default_purchase' => false,
52+
]);
53+
}
54+
}
55+
}
56+
}

resources/lang/en/price-list.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
return [
4+
'singular' => 'Price List',
5+
'plural' => 'Price Lists',
6+
7+
'fields' => [
8+
'name' => 'Name',
9+
'code' => 'Code',
10+
'currency' => 'Currency',
11+
'tax_included' => 'Tax Included',
12+
'notes' => 'Notes',
13+
'is_active' => 'Active',
14+
'is_default' => 'Default Selling',
15+
'is_default_purchase' => 'Default Purchase',
16+
'tenant' => 'Tenant',
17+
],
18+
19+
'sections' => [
20+
'information' => 'Price List Information',
21+
'information_description' => 'Basic information about the price list',
22+
'settings' => 'Settings',
23+
'settings_description' => 'Configure price list behavior',
24+
'tenant_settings' => 'Tenant Settings',
25+
'tenant_settings_description' => 'Configure price list settings for each tenant/site',
26+
'default_settings' => 'Default Settings',
27+
],
28+
29+
'placeholders' => [
30+
'name' => 'Enter price list name',
31+
'code' => 'Optional code for identification',
32+
'currency' => 'Select currency',
33+
'notes' => 'Optional notes about this price list',
34+
],
35+
36+
'help_text' => [
37+
'tax_included' => 'Whether prices include tax',
38+
'is_active' => 'Enable this price list',
39+
'is_active_tenant' => 'Enable this price list for :tenant',
40+
'is_default' => 'Use as default selling price list',
41+
'is_default_tenant' => 'Use as default selling price list for :tenant',
42+
'is_default_purchase' => 'Use as default purchase price list',
43+
'is_default_purchase_tenant' => 'Use as default purchase price list for :tenant',
44+
],
45+
46+
'table' => [
47+
'columns' => [
48+
'id' => 'ID',
49+
'name' => 'Name',
50+
'code' => 'Code',
51+
'currency' => 'Currency',
52+
'tax_included' => 'Tax Included',
53+
'is_active' => 'Active',
54+
'is_default' => 'Default Selling',
55+
'is_default_purchase' => 'Default Purchase',
56+
'created_at' => 'Created Date',
57+
'updated_at' => 'Last Modified Date',
58+
],
59+
],
60+
61+
'notifications' => [
62+
'conflict_resolved_title' => 'Conflict Resolved',
63+
'conflict_resolved_selling_disabled' => 'A price list cannot be both default selling and default purchase for :tenant. Default selling has been disabled.',
64+
'conflict_resolved_purchase_disabled' => 'A price list cannot be both default selling and default purchase for :tenant. Default purchase has been disabled.',
65+
'conflict_resolved_selling_disabled_simple' => 'A price list cannot be both default selling and default purchase. Default selling has been disabled.',
66+
'conflict_resolved_purchase_disabled_simple' => 'A price list cannot be both default selling and default purchase. Default purchase has been disabled.',
67+
],
68+
69+
'validation' => [
70+
'cannot_be_both_defaults' => 'A price list cannot be both default selling and default purchase.',
71+
'cannot_be_both_defaults_tenant' => 'A price list cannot be both default selling and default purchase for :tenant.',
72+
],
73+
74+
'messages' => [
75+
'default_selling_help' => 'Only one price list can be set as default selling per tenant.',
76+
'default_purchase_help' => 'Only one price list can be set as default purchase per tenant.',
77+
'cannot_delete_default' => 'Cannot delete a default price list.',
78+
],
79+
80+
'labels' => [
81+
'current' => 'Current',
82+
'tenant_switcher' => 'Tenant Switcher',
83+
],
84+
];
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<div class="tenant-switcher">
2+
<div class="mb-4">
3+
<label for="tenant-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
4+
{{ $label }}
5+
</label>
6+
<select
7+
id="tenant-select"
8+
wire:model.live="selectedTenant"
9+
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
10+
>
11+
<option value="">{{ $placeholder }}</option>
12+
@foreach($tenants as $tenant)
13+
<option value="{{ $tenant->id }}" @if($tenant->id === $currentTenant?->id) selected @endif>
14+
{{ $tenant->name }}
15+
@if($tenant->id === $currentTenant?->id)
16+
({{ __('eclipse-catalogue::price-list.labels.current') }})
17+
@endif
18+
</option>
19+
@endforeach
20+
</select>
21+
</div>
22+
</div>

src/CatalogueServiceProvider.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function configurePackage(Package $package): void
1818
->hasViews()
1919
->hasConfigFile()
2020
->hasTranslations()
21+
->hasViews()
2122
->discoversMigrations()
2223
->runsMigrations()
2324
->hasAssets();
@@ -36,4 +37,14 @@ public function register()
3637

3738
Config::set('scout.typesense.model-settings', $settings);
3839
}
40+
41+
public function boot()
42+
{
43+
parent::boot();
44+
45+
// Register Livewire components
46+
if (class_exists(\Livewire\Livewire::class)) {
47+
\Livewire\Livewire::component('eclipse-catalogue::tenant-switcher', \Eclipse\Catalogue\Livewire\TenantSwitcher::class);
48+
}
49+
}
3950
}

0 commit comments

Comments
 (0)