|
@@ -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');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|