feat: add prices relationship to ProductVariant model and update related services and UI for improved product pricing management

This commit is contained in:
Yoga Pangestu 2026-06-21 00:09:23 +07:00
parent ab03d5f0b9
commit 256b13ad73
6 changed files with 93 additions and 5 deletions

View File

@ -0,0 +1,45 @@
<?php
namespace App\Models;
use App\Enums\PriceType;
use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Guarded(['id'])]
#[Appends(['price_formatted', 'price_input'])]
class ProductPrice extends Model
{
use HasFactory;
protected function casts(): array
{
return [
'type' => PriceType::class,
'price' => 'integer',
];
}
public function variant(): BelongsTo
{
return $this->belongsTo(ProductVariant::class, 'variant_id');
}
public function priceFormatted(): Attribute
{
return Attribute::make(
get: fn () => 'Rp '.number_format($this->price, 0, ',', '.'),
);
}
public function priceInput(): Attribute
{
return Attribute::make(
get: fn () => (string) $this->price,
);
}
}

View File

@ -42,6 +42,11 @@ public function cuttingResultPrices(): HasMany
return $this->hasMany(CuttingResultPrice::class);
}
public function prices(): HasMany
{
return $this->hasMany(ProductPrice::class, 'variant_id');
}
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);

View File

@ -27,7 +27,7 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $ca
->with([
'categories',
'variants' => fn ($query) => $query
->with('media')
->with(['media', 'prices' => fn ($query) => $query->orderBy('type')])
->orderBy('created_at'),
])
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {

View File

@ -0,0 +1,33 @@
<?php
use App\Enums\PriceType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('product_prices', function (Blueprint $table) {
$table->id();
$table->foreignId('variant_id')->constrained('product_variants')->cascadeOnDelete();
$table->enum('type', array_column(PriceType::cases(), 'value'));
$table->unsignedInteger('price');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->softDeletes();
$table->unique(['variant_id', 'type']);
$table->index('variant_id');
});
}
public function down(): void
{
Schema::dropIfExists('product_prices');
}
};

View File

@ -2,6 +2,7 @@
import { Link } from '@inertiajs/vue3';
import { computed } from 'vue';
import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue';
import { formatRupiah } from '@/lib/rupiah';
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -103,6 +104,11 @@ function rowNumber(index: number): number {
{{ product.variants.reduce((acc, v) => acc + v.stock, 0) }}
</strong>
</span>
<span>
Total harga <strong class="text-primary"> Rp
{{ formatRupiah(product.variants.reduce((acc, v) => acc + (v.prices?.reduce((s, p) => s + p.price, 0) ?? 0), 0)) }}
</strong>
</span>
</div>
</div>
</div>
@ -154,7 +160,7 @@ function rowNumber(index: number): number {
</div>
</div>
</div>
<span v-else class="text-xs text-muted-foreground">Belum ada harga cutting</span>
<span v-else class="text-xs text-muted-foreground">Belum ada harga</span>
</TableCell>
</TableRow>
</TableBody>

View File

@ -11,13 +11,12 @@ export type CategoryOption = {
};
export type ProductPriceItem = {
id: number;
variant_id: number;
type: string;
type_label: string;
price: number;
price_formatted: string;
price_input: string;
cost_per_unit?: number;
cost_per_unit_formatted?: string;
};
export type ProductCategoryItem = {