From 256b13ad7373525953aac531c56ba5215aa3d3b4 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 21 Jun 2026 00:09:23 +0700 Subject: [PATCH] feat: add prices relationship to ProductVariant model and update related services and UI for improved product pricing management --- app/Models/ProductPrice.php | 45 +++++++++++++++++++ app/Models/ProductVariant.php | 5 +++ app/Services/Master/ProductService.php | 2 +- ..._20_235900_create_product_prices_table.php | 33 ++++++++++++++ .../products/table/ProductGroupedTable.vue | 8 +++- resources/js/types/product.ts | 5 +-- 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 app/Models/ProductPrice.php create mode 100644 database/migrations/2026_06_20_235900_create_product_prices_table.php diff --git a/app/Models/ProductPrice.php b/app/Models/ProductPrice.php new file mode 100644 index 0000000..d3cfdf2 --- /dev/null +++ b/app/Models/ProductPrice.php @@ -0,0 +1,45 @@ + 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, + ); + } +} diff --git a/app/Models/ProductVariant.php b/app/Models/ProductVariant.php index 1b25121..7d34270 100644 --- a/app/Models/ProductVariant.php +++ b/app/Models/ProductVariant.php @@ -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); diff --git a/app/Services/Master/ProductService.php b/app/Services/Master/ProductService.php index 7047cc3..351b5cb 100644 --- a/app/Services/Master/ProductService.php +++ b/app/Services/Master/ProductService.php @@ -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 { diff --git a/database/migrations/2026_06_20_235900_create_product_prices_table.php b/database/migrations/2026_06_20_235900_create_product_prices_table.php new file mode 100644 index 0000000..632db6b --- /dev/null +++ b/database/migrations/2026_06_20_235900_create_product_prices_table.php @@ -0,0 +1,33 @@ +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'); + } +}; diff --git a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue index ca932cf..dbd04f1 100644 --- a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue +++ b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue @@ -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) }} + + Total harga Rp + {{ formatRupiah(product.variants.reduce((acc, v) => acc + (v.prices?.reduce((s, p) => s + p.price, 0) ?? 0), 0)) }} + + @@ -154,7 +160,7 @@ function rowNumber(index: number): number { - Belum ada harga cutting + Belum ada harga diff --git a/resources/js/types/product.ts b/resources/js/types/product.ts index b7dc596..24b9f92 100644 --- a/resources/js/types/product.ts +++ b/resources/js/types/product.ts @@ -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 = {