|
@@ -0,0 +1,103 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Longyi\FreeGift\Services;
|
|
|
|
|
+
|
|
|
|
|
+use Longyi\Core\Models\ProductVariant;
|
|
|
|
|
+use Longyi\Core\Repositories\ProductVariantRepository;
|
|
|
|
|
+use Webkul\Product\Repositories\ProductRepository;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 把赠品 SKU 解析成「商品 + 变体」。
|
|
|
|
|
+ *
|
|
|
|
|
+ * `free_gift_rules.sku` 里存的可能是:
|
|
|
|
|
+ * - simple / virtual 商品的 SKU → variant 为 null
|
|
|
|
|
+ * - flexible_variant 父商品的 SKU(后台选择器给的就是这个)→ 取一个默认变体
|
|
|
|
|
+ * - flexible_variant 某个变体的 SKU → 直接用该变体
|
|
|
|
|
+ *
|
|
|
|
|
+ * 单请求内按 SKU 缓存:一次 collectTotals 里 syncGiftItems / autoAddGiftItems
|
|
|
|
|
+ * 会反复问同一个 SKU。
|
|
|
|
|
+ */
|
|
|
|
|
+class GiftProductResolver
|
|
|
|
|
+{
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @var array<string, array{product: object, variant: ?ProductVariant}|null>
|
|
|
|
|
+ */
|
|
|
|
|
+ protected array $cache = [];
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct(
|
|
|
|
|
+ protected ProductRepository $productRepository,
|
|
|
|
|
+ protected ProductVariantRepository $variantRepository
|
|
|
|
|
+ ) {}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return array{product: object, variant: ?ProductVariant}|null
|
|
|
|
|
+ */
|
|
|
|
|
+ public function resolve(string $sku): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ $sku = trim($sku);
|
|
|
|
|
+
|
|
|
|
|
+ if ($sku === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (! array_key_exists($sku, $this->cache)) {
|
|
|
|
|
+ $this->cache[$sku] = $this->doResolve($sku);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $this->cache[$sku];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析失败(赠品被下架/删除、SKU 写错)返回 null,调用方按「本次送不出去」处理。
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function doResolve(string $sku): ?array
|
|
|
|
|
+ {
|
|
|
|
|
+ // 1) 先当商品 SKU 找(simple / virtual / flexible_variant 的父商品)
|
|
|
|
|
+ $product = $this->productRepository->findOneWhere(['sku' => $sku]);
|
|
|
|
|
+
|
|
|
|
|
+ if ($product) {
|
|
|
|
|
+ if ($product->type === 'flexible_variant') {
|
|
|
|
|
+ $variant = $this->defaultVariant((int) $product->id);
|
|
|
|
|
+
|
|
|
|
|
+ return $variant ? ['product' => $product, 'variant' => $variant] : null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ['product' => $product, 'variant' => null];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2) 再当变体 SKU 找(flexible_variant 的行)
|
|
|
|
|
+ $variant = $this->variantRepository->findBySku($sku);
|
|
|
|
|
+
|
|
|
|
|
+ if (! $variant || ! $variant->product) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ['product' => $variant->product, 'variant' => $variant];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 配置的是父商品 SKU 时挑一个变体:优先「启用且有货」的第一条(按 sort_order),
|
|
|
|
|
+ * 全都无货时退回「启用」的第一条 —— 让库存校验去决定放不放行。
|
|
|
|
|
+ */
|
|
|
|
|
+ public function defaultVariant(int $productId): ?ProductVariant
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->variantRepository->getSaleableByProduct($productId)->first()
|
|
|
|
|
+ ?: $this->variantRepository->getByProduct($productId, true)->first();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 拿变体上的最终成交价(含特价/目录促销/客户组价)。
|
|
|
|
|
+ */
|
|
|
|
|
+ public function variantPrice(?ProductVariant $variant): float
|
|
|
|
|
+ {
|
|
|
|
|
+ if (! $variant) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ return (float) $variant->getEffectivePrice();
|
|
|
|
|
+ } catch (\Throwable) {
|
|
|
|
|
+ return (float) ($variant->special_price ?: $variant->price ?: 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|