chengwl 1 dzień temu
rodzic
commit
0bc03c4fcf

+ 229 - 0
packages/Webkul/BagistoApi/src/Models/ProductVariant.php

@@ -0,0 +1,229 @@
+<?php
+
+namespace Webkul\BagistoApi\Models;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GraphQl\Query;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Longyi\Core\Models\ProductVariant as BaseProductVariant;
+use Spatie\MediaLibrary\MediaCollections\Models\Media;
+use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
+
+/**
+ * BagistoApi wrapper around the Longyi1 flexible variant model so that
+ * ApiPlatform can expose variants (and their images) as part of the
+ * single product query.
+ */
+#[ApiResource(
+    routePrefix: '/api/shop',
+    shortName: 'ProductVariant',
+    operations: [],
+    graphQlOperations: [
+        new Query(resolver: BaseQueryItemResolver::class),
+    ]
+)]
+class ProductVariant extends BaseProductVariant
+{
+   
+    protected $appends = [
+        'effective_price',
+        'variant_images',
+        'option_values',
+    ];
+
+    #[ApiProperty(identifier: true, writable: false)]
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getProduct_id(): ?int
+    {
+        return $this->product_id;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getSku(): ?string
+    {
+        return $this->sku;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getPrice(): ?float
+    {
+        return $this->price !== null ? (float) $this->price : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getCompare_price(): ?float
+    {
+        return $this->compare_price !== null ? (float) $this->compare_price : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getSpecial_price(): ?float
+    {
+        return $this->special_price !== null ? (float) $this->special_price : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getSpecial_price_from(): ?string
+    {
+        return $this->special_price_from ? $this->special_price_from->toDateString() : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getSpecial_price_to(): ?string
+    {
+        return $this->special_price_to ? $this->special_price_to->toDateString() : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getCost(): ?float
+    {
+        return $this->cost !== null ? (float) $this->cost : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getWeight(): ?float
+    {
+        return $this->weight !== null ? (float) $this->weight : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getQuantity(): ?int
+    {
+        return $this->quantity !== null ? (int) $this->quantity : null;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getStatus(): ?bool
+    {
+        return (bool) $this->status;
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getSort_order(): ?int
+    {
+        return $this->sort_order !== null ? (int) $this->sort_order : null;
+    }
+
+    /**
+     * Effective price considering variant-level special_price window.
+     */
+    public function getEffectivePriceAttribute(): float
+    {
+        return $this->getBasicEffectivePrice();
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getEffective_price(): ?float
+    {
+        return $this->getEffectivePriceAttribute();
+    }
+
+    #[ApiProperty(writable: false, readable: true, required: false)]
+    public function getIs_saleable(): bool
+    {
+        return $this->isSaleable();
+    }
+
+    /**
+     * Variant images stored in the Spatie media table via product_variant_images pivot.
+     */
+    public function variant_images(): BelongsToMany
+    {
+        return $this->belongsToMany(
+            Media::class,
+            'product_variant_images',
+            'product_variant_id',
+            'media_id'
+        )->withPivot('position')
+         ->orderByPivot('position');
+    }
+
+    /**
+     * Serialize variant images inline as a JSON string to avoid IRI generation
+     * (Spatie Media is not an ApiResource).
+     *
+     * Must be named getVariantImagesAttribute so that ApiPlatform's
+     * EloquentPropertyNameCollectionMetadataFactory picks it up as a virtual
+     * attribute (snake_cased: 'variant_images', camelCase in GraphQL:
+     * 'variantImages').
+     *
+     * Returns: '[{"id":1,"url":"https://...","position":0}, ...]' or null.
+     */
+    public function getVariantImagesAttribute(): ?string
+    {
+        if (! $this->relationLoaded('variant_images')) {
+            $this->load('variant_images');
+        }
+
+        $payload = ($this->getRelation('variant_images') ?? collect())
+            ->map(fn (Media $m) => [
+                'id'       => $m->id,
+                'url'      => $m->getUrl(),
+                'position' => $m->pivot->position ?? 0,
+            ])
+            ->values()
+            ->all();
+
+        return empty($payload) ? null : json_encode($payload);
+    }
+
+    /**
+     * Option values attached to the variant (e.g. Red + Small).
+     */
+    public function values(): BelongsToMany
+    {
+        return $this->belongsToMany(
+            \Longyi\Core\Models\ProductOptionValue::class,
+            'product_variant_option_values',
+            'product_variant_id',
+            'product_option_value_id'
+        )->withTimestamps();
+    }
+
+    /**
+     * Serialize option values inline as a JSON string to avoid registering a
+     * separate ApiResource for ProductOptionValue.
+     *
+     * Named getOptionValuesAttribute so ApiPlatform picks it up as a virtual
+     * attribute: snake_cased 'option_values', GraphQL field 'optionValues'.
+     *
+     * Returns: '[{"id":1,"label":"Red","code":"red","option_id":5,"option_code":"color"}, ...]' or null.
+     */
+    public function getOptionValuesAttribute(): ?string
+    {
+        if (! $this->relationLoaded('values')) {
+            $this->load('values.option');
+        }
+
+        $payload = ($this->getRelation('values') ?? collect())
+            ->map(fn ($value) => [
+                'id'          => $value->id,
+                'label'       => $value->label,
+                'code'        => $value->code,
+                'option_id'   => $value->product_option_id,
+                'option_code' => $value->option?->code,
+            ])
+            ->values()
+            ->all();
+
+        return empty($payload) ? null : json_encode($payload);
+    }
+
+    public function product(): BelongsTo
+    {
+        return $this->belongsTo(Product::class, 'product_id');
+    }
+}