|
|
@@ -4,6 +4,7 @@ namespace Longyi\FreeGift\Http\Controllers\Admin;
|
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
|
+use Longyi\FreeGift\Services\GiftProductResolver;
|
|
|
use Longyi\FreeGift\Services\GiftProductValidator;
|
|
|
use Webkul\Admin\Http\Controllers\Controller;
|
|
|
use Webkul\Product\Models\ProductFlat;
|
|
|
@@ -13,6 +14,10 @@ use Webkul\Product\Models\ProductFlat;
|
|
|
*
|
|
|
* 只读接口,空关键词返回全部商品;数据取当前渠道 + 当前语言的 product_flat,
|
|
|
* 一个商品一行(product_flat 是「商品 + 渠道 + 语言」的展开表)。
|
|
|
+ *
|
|
|
+ * 每行都会带 `giftable` + `giftable_reason`:赠品能不能真的送出去
|
|
|
+ * 取决于「启用 + 类型放行 + flexible_variant 有可售变体」,
|
|
|
+ * 提前提示可以避免运营配出一个永远送不出去的规则(见 GiftProductValidator::explain)。
|
|
|
*/
|
|
|
class ProductPickerController extends Controller
|
|
|
{
|
|
|
@@ -21,6 +26,10 @@ class ProductPickerController extends Controller
|
|
|
*/
|
|
|
protected const PER_PAGE = 20;
|
|
|
|
|
|
+ public function __construct(
|
|
|
+ protected GiftProductResolver $productResolver
|
|
|
+ ) {}
|
|
|
+
|
|
|
/**
|
|
|
* 商品列表。
|
|
|
*/
|
|
|
@@ -47,20 +56,33 @@ class ProductPickerController extends Controller
|
|
|
->orderBy('name')
|
|
|
->paginate(self::PER_PAGE);
|
|
|
|
|
|
- $products->through(fn ($product) => [
|
|
|
- 'id' => (int) $product->product_id,
|
|
|
- 'sku' => (string) $product->sku,
|
|
|
- 'name' => (string) $product->name,
|
|
|
- 'type' => (string) $product->type,
|
|
|
- 'price' => core()->formatBasePrice((float) $product->price),
|
|
|
- /*
|
|
|
- * 只有「启用 + 简单/虚拟」的商品才真的能被当赠品送出去
|
|
|
- * (判定口径见 GiftProductValidator::ALLOWED_TYPES),
|
|
|
- * 这里只是给后台一个提示,不拦着不让选。
|
|
|
- */
|
|
|
- 'giftable' => (bool) $product->status
|
|
|
- && in_array($product->type, GiftProductValidator::ALLOWED_TYPES, true),
|
|
|
- ]);
|
|
|
+ /*
|
|
|
+ * flexible_variant 还要求「至少一个启用且有货的变体」,否则赠品行写不进
|
|
|
+ * 购物车。这里按当前页批量查一次,避免逐行 N+1。
|
|
|
+ */
|
|
|
+ $flexibleIds = $products->getCollection()
|
|
|
+ ->where('type', 'flexible_variant')
|
|
|
+ ->pluck('product_id')
|
|
|
+ ->map(fn ($id) => (int) $id)
|
|
|
+ ->all();
|
|
|
+
|
|
|
+ $withSaleableVariant = $this->productResolver->productIdsWithSaleableVariant($flexibleIds);
|
|
|
+
|
|
|
+ $products->through(function ($product) use ($withSaleableVariant) {
|
|
|
+ $reason = $this->giftableReason($product, $withSaleableVariant);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => (int) $product->product_id,
|
|
|
+ 'sku' => (string) $product->sku,
|
|
|
+ 'name' => (string) $product->name,
|
|
|
+ 'type' => (string) $product->type,
|
|
|
+ 'price' => core()->formatBasePrice((float) $product->price),
|
|
|
+ 'giftable' => $reason === null,
|
|
|
+ 'giftable_reason' => $reason === null
|
|
|
+ ? null
|
|
|
+ : trans('free_gift::admin.picker-'.$reason),
|
|
|
+ ];
|
|
|
+ });
|
|
|
|
|
|
return response()->json([
|
|
|
'data' => $products->items(),
|
|
|
@@ -72,6 +94,29 @@ class ProductPickerController extends Controller
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 不能当赠品的原因(null = 可以),返回的是翻译 key 的后缀。
|
|
|
+ *
|
|
|
+ * @param array<int, true> $withSaleableVariant
|
|
|
+ */
|
|
|
+ protected function giftableReason($product, array $withSaleableVariant): ?string
|
|
|
+ {
|
|
|
+ if (! $product->status) {
|
|
|
+ return 'reason-inactive';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (! in_array($product->type, GiftProductValidator::ALLOWED_TYPES, true)) {
|
|
|
+ return 'reason-type';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($product->type === 'flexible_variant'
|
|
|
+ && ! isset($withSaleableVariant[(int) $product->product_id])) {
|
|
|
+ return 'reason-no-variant';
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 转义 LIKE 里的通配符(SKU 里下划线/百分号很常见,不转义会搜错)。
|
|
|
*/
|