Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c8dcadd
feat(product-types): add migration and prototype model
SlimDeluxe Aug 8, 2025
a717256
Merge remote-tracking branch 'origin/main' into feat/types
KilianTrunk Aug 12, 2025
9b7c71b
feat(product-types): implement product types
KilianTrunk Aug 14, 2025
c662b9a
Merge remote-tracking branch 'origin/main' into chore/types
KilianTrunk Aug 16, 2025
ea64105
chore(types): fix product type policy
KilianTrunk Aug 16, 2025
da7413b
feat: add product data table and more attributes
SlimDeluxe Aug 16, 2025
c273a65
chore(types): product types improvements
KilianTrunk Aug 17, 2025
2ace684
Merge branch 'chore/types' into chore/product_data
KilianTrunk Aug 17, 2025
1453812
feat(product-data): implement product data & product improvements
KilianTrunk Aug 18, 2025
d332f93
refactor: improve tenancy oriented classes
KilianTrunk Aug 18, 2025
cae9182
chore(product-data): testing fixes
KilianTrunk Aug 18, 2025
5153051
fix: fix tenancy issues & refactor
KilianTrunk Aug 18, 2025
d23cf63
feat: add product price model and migration
KilianTrunk Aug 18, 2025
1af2ec6
feat(product-prices): implement prices
KilianTrunk Aug 18, 2025
d47fcfe
Merge branch 'main' into feat/product_prices
KilianTrunk Aug 28, 2025
4489d62
fix: resolve product resource after merging
KilianTrunk Aug 28, 2025
0c53dba
Merge branch 'main' into feat/product_prices
KilianTrunk Aug 29, 2025
2b89d15
fix: format
KilianTrunk Aug 29, 2025
83afeb4
Merge branch 'main' into feat/product_prices
KilianTrunk Sep 10, 2025
29f2bb1
style: fix code style
KilianTrunk Sep 10, 2025
b02706f
fix: resolve tenant fields error
KilianTrunk Sep 10, 2025
6c991b1
chore: ui improvements for setting prices
KilianTrunk Sep 10, 2025
69b1f5c
Merge branch 'main' into fork/KilianTrunk/feat/product_prices
SlimDeluxe Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('catalogue_product_prices', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')
->constrained('catalogue_products')
->cascadeOnUpdate()
->cascadeOnDelete();
$table->foreignId('price_list_id')
->constrained('pim_price_lists')
->cascadeOnUpdate()
->cascadeOnDelete();
$table->date('valid_from');
$table->date('valid_to')->nullable();
$table->decimal('price', 20, 5);
$table->boolean('tax_included');
$table->timestamps();
$table->unique(['product_id', 'price_list_id', 'valid_from'], 'uq_cpp_pid_plid_vf');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('catalogue_product_prices');
}
};
19 changes: 19 additions & 0 deletions resources/lang/en/product.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,23 @@
'available_from_date' => 'Date/time when the product becomes available',
'sorting_label' => 'Optional label used to influence sorting within lists',
],

'price' => [
'section' => 'Product price list',
'list' => 'Price list',
'fields' => [
'price_list' => 'Price list',
'price' => 'Price',
'tax_included' => 'Incl. tax',
'valid_from' => 'Valid from',
'valid_to' => 'Valid to',
],
'actions' => [
'add' => 'Add price',
],
'validation' => [
'unique_title' => 'Duplicate price',
'unique_body' => 'A price with the same Price List, Price and Valid From already exists in the form.',
],
],
];
19 changes: 19 additions & 0 deletions resources/lang/sl/product.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,23 @@
'available_from_date' => 'Datum/čas, ko bo izdelek na voljo',
'sorting_label' => 'Neobvezna oznaka, ki vpliva na razvrščanje v seznamih',
],

'price' => [
'section' => 'Cenik izdelka',
'list' => 'Cenik',
'fields' => [
'price_list' => 'Cenik',
'price' => 'Cena',
'tax_included' => 'Z DDV',
'valid_from' => 'Velja od',
'valid_to' => 'Velja do',
],
'actions' => [
'add' => 'Dodaj ceno',
],
'validation' => [
'unique_title' => 'Podvojen vnos cene',
'unique_body' => 'Cena z enakim cenikom, ceno in datumom "Velja od" že obstaja v obrazcu.',
],
],
];
78 changes: 78 additions & 0 deletions src/Filament/Resources/ProductResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@
use Eclipse\Catalogue\Forms\Components\GenericTenantFieldsComponent;
use Eclipse\Catalogue\Models\Category;
use Eclipse\Catalogue\Models\Group;
use Eclipse\Catalogue\Models\PriceList;
use Eclipse\Catalogue\Models\Product;
use Eclipse\Catalogue\Models\Property;
use Eclipse\Catalogue\Traits\HandlesTenantData;
use Eclipse\Catalogue\Traits\HasTenantFields;
use Eclipse\World\Models\Country;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Radio;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
Expand Down Expand Up @@ -174,6 +179,7 @@ public static function form(Form $form): Form
->searchable()
->preload()
->placeholder(__('eclipse-catalogue::product.placeholders.category_id')),

Select::make("tenant_data.{$tenantId}.groups")
->label('Groups')
->multiple()
Expand All @@ -191,6 +197,7 @@ public static function form(Form $form): Form
TextInput::make("tenant_data.{$tenantId}.sorting_label")
->label(__('eclipse-catalogue::product.fields.sorting_label'))
->maxLength(255),

\Filament\Forms\Components\DateTimePicker::make("tenant_data.{$tenantId}.available_from_date")
->label(__('eclipse-catalogue::product.fields.available_from_date')),
];
Expand All @@ -200,6 +207,77 @@ public static function form(Form $form): Form
),
]),

Tabs\Tab::make('Prices')
->schema([
Section::make(__('eclipse-catalogue::product.price.section'))
->schema([
Repeater::make('prices')
->hiddenLabel()
->relationship('prices')
->schema([
Hidden::make('id'),

Select::make('price_list_id')
->label(__('eclipse-catalogue::product.price.fields.price_list'))
->relationship('priceList', 'name')
->required()
->preload()
->searchable()
->live()
->afterStateUpdated(function ($state, callable $set) {
if (! $state) {
return;
}
$pl = PriceList::query()->select('id', 'tax_included')->find($state);
if ($pl) {
$set('tax_included', (bool) $pl->tax_included);
}
}),

TextInput::make('price')
->label(__('eclipse-catalogue::product.price.fields.price'))
->numeric()
->rule('decimal:0,5')
->required(),

Checkbox::make('tax_included')
->label(__('eclipse-catalogue::product.price.fields.tax_included'))
->inline(false)
->default(false),

DatePicker::make('valid_from')
->label(__('eclipse-catalogue::product.price.fields.valid_from'))
->native(false)
->required(),

DatePicker::make('valid_to')
->label(__('eclipse-catalogue::product.price.fields.valid_to'))
->native(false)
->nullable(),
])
->minItems(0)
->reorderable(false)
->columns(5)
->createItemButtonLabel(__('eclipse-catalogue::product.price.actions.add'))
->rule(function (Get $get) {
return function (string $attribute, $value, $fail) {
$seen = [];
foreach ($value as $row) {
if (! $row['price_list_id'] || ! $row['valid_from']) {
continue;
}
$key = $row['price_list_id'].'_'.$row['valid_from'];
if (isset($seen[$key])) {
$fail(__('eclipse-catalogue::product.price.validation.unique_body'));
}
$seen[$key] = true;
}
};
}),
])
->compact(),
]),

Tabs\Tab::make('Properties')
->schema([
Section::make('Product Type Selection')
Expand Down
19 changes: 18 additions & 1 deletion src/Forms/Components/GenericTenantFieldsComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,26 @@ protected static function makeTenantFields(int $tenantId, string $tenantName, ar
if ($extraFieldsBuilder) {
$extra = $extraFieldsBuilder($tenantId, $tenantName);
if (is_array($extra)) {
// For extra fields, also persist on change if supported
$enhancedExtra = [];
foreach ($extra as $component) {
// Ensure extra components push state immediately while editing
if (method_exists($component, 'live')) {
$component = $component->live();
}
if (method_exists($component, 'afterStateUpdated')) {
$component = $component->afterStateUpdated(function ($state, callable $set, callable $get) use ($tenantId) {
$currentData = $get("tenant_data.{$tenantId}") ?? [];
$allTenantData = $get('all_tenant_data') ?? [];
$allTenantData[$tenantId] = $currentData;
$set('all_tenant_data', $allTenantData);
});
}
$enhancedExtra[] = $component;
}
$schema = [
...$schema,
...$extra,
...$enhancedExtra,
];
}
}
Expand Down
26 changes: 18 additions & 8 deletions src/Models/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Product extends Model implements HasMedia
'gross_weight',
'name',
'product_type_id',
'category_id',
'short_description',
'description',
'origin_country_id',
Expand All @@ -54,6 +55,7 @@ class Product extends Model implements HasMedia
'meta_title' => 'array',
'meta_description' => 'array',
'deleted_at' => 'datetime',
'category_id' => 'integer',
'product_type_id' => 'integer',
'available_from_date' => 'datetime',
'is_active' => 'boolean',
Expand Down Expand Up @@ -100,14 +102,6 @@ public function originCountry(): BelongsTo
return $this->belongsTo(Country::class, 'origin_country_id', 'id');
}

/**
* Get all per-tenant data rows for this product.
*/
public function productData(): HasMany
{
return $this->hasMany(ProductData::class, 'product_id');
}

public function groups(): BelongsToMany
{
return $this->belongsToMany(Group::class, 'pim_group_has_product', 'product_id', 'group_id')
Expand All @@ -124,6 +118,22 @@ public function getHasFreeDeliveryAttribute(): bool
return $this->getTenantFlagValue('has_free_delivery');
}

/**
* Get all per-tenant data rows for this product.
*/
public function productData(): HasMany
{
return $this->hasMany(ProductData::class, 'product_id');
}

/**
* Prices relationship.
*/
public function prices(): HasMany
{
return $this->hasMany(\Eclipse\Catalogue\Models\Product\Price::class);
}

public function getAvailableFromDateAttribute()
{
return $this->currentTenantData()?->available_from_date;
Expand Down
42 changes: 42 additions & 0 deletions src/Models/Product/Price.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Eclipse\Catalogue\Models\Product;

use Eclipse\Catalogue\Models\PriceList;
use Eclipse\Catalogue\Models\Product;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Price extends Model
{
protected $table = 'catalogue_product_prices';

protected $fillable = [
'id',
'product_id',
'price_list_id',
'valid_from',
'valid_to',
'price',
'tax_included',
];

public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}

public function priceList(): BelongsTo
{
return $this->belongsTo(PriceList::class);
}

protected function casts(): array
{
return [
'valid_from' => 'date',
'valid_to' => 'date',
'tax_included' => 'boolean',
];
}
}
Loading