瀏覽代碼

Merge branch 'dev-rewardPoints' into dev

bianjunhui 1 周之前
父節點
當前提交
d8f74aabbf
共有 19 個文件被更改,包括 263 次插入5 次删除
  1. 5 0
      packages/Longyi/Gift/src/Listeners/ApplepayBreakdownHandler.php
  2. 6 2
      packages/Longyi/Gift/src/Repositories/CustomInvoiceRepository.php
  3. 6 2
      packages/Longyi/Gift/src/Repositories/CustomOrderRepository.php
  4. 40 0
      packages/Longyi/RewardPoints/src/Database/Migrations/2026_08_21_000001_add_reward_points_fields_to_orders_table.php
  5. 54 0
      packages/Longyi/RewardPoints/src/Database/Migrations/2026_08_21_000002_add_reward_points_invoiced_fields.php
  6. 4 0
      packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php
  7. 2 0
      packages/Longyi/RewardPoints/src/Resources/lang/en/rewardpoints.php
  8. 2 0
      packages/Longyi/RewardPoints/src/Resources/lang/zh_CN/rewardpoints.php
  9. 12 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderDetailDto.php
  10. 8 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderListDto.php
  11. 15 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductAttributeDto.php
  12. 8 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductDetailDto.php
  13. 6 0
      packages/Webkul/BagistoApi/src/Models/CustomerOrder.php
  14. 68 0
      packages/Webkul/BagistoApi/src/Models/Product.php
  15. 2 0
      packages/Webkul/BagistoApi/src/State/ProductDetailProvider.php
  16. 5 0
      packages/Webkul/BagistoApi/src/Transformers/ExpressOrderResource.php
  17. 4 1
      packages/Webkul/Checkout/src/Models/Cart.php
  18. 11 0
      packages/Webkul/Sales/src/Repositories/RefundRepository.php
  19. 5 0
      packages/Webkul/Sales/src/Transformers/OrderResource.php

+ 5 - 0
packages/Longyi/Gift/src/Listeners/ApplepayBreakdownHandler.php

@@ -20,5 +20,10 @@ class ApplepayBreakdownHandler
         if (! empty($order->giftcard_amount)) {
             $event->breakdown->addDiscount((float) $order->giftcard_amount);
         }
+
+        // 积分抵扣金额 -> discount(正数递减总额)
+        if (! empty($order->reward_points_amount)) {
+            $event->breakdown->addDiscount((float) $order->reward_points_amount);
+        }
     }
 }

+ 6 - 2
packages/Longyi/Gift/src/Repositories/CustomInvoiceRepository.php

@@ -19,6 +19,7 @@ class CustomInvoiceRepository extends BaseInvoiceRepository
         $invoice->vip_plus_amount = $invoice->base_vip_plus_amount = 0;
         $invoice->vip_discount_amount = $invoice->base_vip_discount_amount = 0;
         $invoice->shipping_insurance_amount = $invoice->base_shipping_insurance_amount = 0;
+        $invoice->reward_points_amount = $invoice->base_reward_points_amount = 0;
 
         foreach ($invoice->items as $item) {
             $invoice->tax_amount += $item->tax_amount;
@@ -55,6 +56,9 @@ class CustomInvoiceRepository extends BaseInvoiceRepository
         $invoice->shipping_insurance_amount += $invoice->order->shipping_insurance_amount;
         $invoice->base_shipping_insurance_amount += $invoice->order->base_shipping_insurance_amount;
 
+        $invoice->reward_points_amount += $invoice->order->reward_points_amount;
+        $invoice->base_reward_points_amount += $invoice->order->base_reward_points_amount;
+
         if ($invoice->order->shipping_tax_amount) {
             $invoice->shipping_tax_amount = $invoice->order->shipping_tax_amount;
 
@@ -89,8 +93,8 @@ class CustomInvoiceRepository extends BaseInvoiceRepository
             }
         }
 
-        $invoice->grand_total = $invoice->sub_total + $invoice->tax_amount + $invoice->shipping_amount - $invoice->discount_amount - $invoice->giftcard_amount + $invoice->vip_plus_amount - $invoice->vip_discount_amount + $invoice->shipping_insurance_amount;
-        $invoice->base_grand_total = $invoice->base_sub_total + $invoice->base_tax_amount + $invoice->base_shipping_amount - $invoice->base_discount_amount - $invoice->base_giftcard_amount + $invoice->base_vip_plus_amount - $invoice->base_vip_discount_amount + $invoice->base_shipping_insurance_amount;
+        $invoice->grand_total = $invoice->sub_total + $invoice->tax_amount + $invoice->shipping_amount - $invoice->discount_amount - $invoice->giftcard_amount + $invoice->vip_plus_amount - $invoice->vip_discount_amount + $invoice->shipping_insurance_amount - $invoice->reward_points_amount;
+        $invoice->base_grand_total = $invoice->base_sub_total + $invoice->base_tax_amount + $invoice->base_shipping_amount - $invoice->base_discount_amount - $invoice->base_giftcard_amount + $invoice->base_vip_plus_amount - $invoice->base_vip_discount_amount + $invoice->base_shipping_insurance_amount - $invoice->base_reward_points_amount;
 
         $invoice->save();
         return $invoice;

+ 6 - 2
packages/Longyi/Gift/src/Repositories/CustomOrderRepository.php

@@ -17,6 +17,7 @@ class CustomOrderRepository extends BaseOrderRepository
         $order->vip_plus_invoiced = $order->base_vip_plus_invoiced = 0;
         $order->vip_discount_invoiced = $order->base_vip_discount_invoiced = 0;
         $order->shipping_insurance_invoiced = $order->base_shipping_insurance_invoiced = 0;
+        $order->reward_points_invoiced = $order->base_reward_points_invoiced = 0;
 
         foreach ($order->invoices as $invoice) {
             $order->sub_total_invoiced += $invoice->sub_total;
@@ -42,10 +43,13 @@ class CustomOrderRepository extends BaseOrderRepository
 
             $order->shipping_insurance_invoiced += $invoice->shipping_insurance_amount;
             $order->base_shipping_insurance_invoiced += $invoice->base_shipping_insurance_amount;
+
+            $order->reward_points_invoiced += $invoice->reward_points_amount;
+            $order->base_reward_points_invoiced += $invoice->base_reward_points_amount;
         }
 
-        $order->grand_total_invoiced = $order->sub_total_invoiced + $order->shipping_invoiced + $order->tax_amount_invoiced - $order->discount_invoiced - $order->giftcard_invoiced + $order->vip_plus_invoiced - $order->vip_discount_invoiced + $order->shipping_insurance_invoiced;
-        $order->base_grand_total_invoiced = $order->base_sub_total_invoiced + $order->base_shipping_invoiced + $order->base_tax_amount_invoiced - $order->base_discount_invoiced - $order->base_giftcard_invoiced + $order->base_vip_plus_invoiced - $order->base_vip_discount_invoiced + $order->base_shipping_insurance_invoiced;
+        $order->grand_total_invoiced = $order->sub_total_invoiced + $order->shipping_invoiced + $order->tax_amount_invoiced - $order->discount_invoiced - $order->giftcard_invoiced + $order->vip_plus_invoiced - $order->vip_discount_invoiced + $order->shipping_insurance_invoiced - $order->reward_points_invoiced;
+        $order->base_grand_total_invoiced = $order->base_sub_total_invoiced + $order->base_shipping_invoiced + $order->base_tax_amount_invoiced - $order->base_discount_invoiced - $order->base_giftcard_invoiced + $order->base_vip_plus_invoiced - $order->base_vip_discount_invoiced + $order->base_shipping_insurance_invoiced - $order->base_reward_points_invoiced;
 
         // order refund total
         $order->sub_total_refunded = $order->base_sub_total_refunded = 0;

+ 40 - 0
packages/Longyi/RewardPoints/src/Database/Migrations/2026_08_21_000001_add_reward_points_fields_to_orders_table.php

@@ -0,0 +1,40 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        if (! Schema::hasColumn('orders', 'reward_points_used')) {
+            Schema::table('orders', function (Blueprint $table) {
+                $table->integer('reward_points_used')->default(0)->after('base_vip_discount_amount')->comment('下单使用的积分数');
+                $table->decimal('reward_points_amount', 12, 4)->default(0)->after('reward_points_used')->comment('积分抵扣金额');
+                $table->decimal('base_reward_points_amount', 12, 4)->default(0)->after('reward_points_amount')->comment('基础币种积分抵扣金额');
+                $table->integer('reward_points_earned')->default(0)->after('base_reward_points_amount')->comment('下单赚取的积分数');
+            });
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        if (Schema::hasColumn('orders', 'reward_points_used')) {
+            Schema::table('orders', function (Blueprint $table) {
+                $table->dropColumn([
+                    'reward_points_used',
+                    'reward_points_amount',
+                    'base_reward_points_amount',
+                    'reward_points_earned',
+                ]);
+            });
+        }
+    }
+};

+ 54 - 0
packages/Longyi/RewardPoints/src/Database/Migrations/2026_08_21_000002_add_reward_points_invoiced_fields.php

@@ -0,0 +1,54 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        // 订单表:积分抵扣已开票金额
+        if (! Schema::hasColumn('orders', 'reward_points_invoiced')) {
+            Schema::table('orders', function (Blueprint $table) {
+                $table->decimal('reward_points_invoiced', 12, 4)->default(0)->after('base_reward_points_amount')->comment('积分抵扣已开票金额');
+                $table->decimal('base_reward_points_invoiced', 12, 4)->default(0)->after('reward_points_invoiced')->comment('基础币种积分抵扣已开票金额');
+            });
+        }
+
+        // 发票表:单张发票的积分抵扣金额
+        if (! Schema::hasColumn('invoices', 'reward_points_amount')) {
+            Schema::table('invoices', function (Blueprint $table) {
+                $table->decimal('reward_points_amount', 12, 4)->default(0)->after('base_shipping_insurance_amount')->comment('积分抵扣金额');
+                $table->decimal('base_reward_points_amount', 12, 4)->default(0)->after('reward_points_amount')->comment('基础币种积分抵扣金额');
+            });
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        if (Schema::hasColumn('orders', 'reward_points_invoiced')) {
+            Schema::table('orders', function (Blueprint $table) {
+                $table->dropColumn([
+                    'reward_points_invoiced',
+                    'base_reward_points_invoiced',
+                ]);
+            });
+        }
+
+        if (Schema::hasColumn('invoices', 'reward_points_amount')) {
+            Schema::table('invoices', function (Blueprint $table) {
+                $table->dropColumn([
+                    'reward_points_amount',
+                    'base_reward_points_amount',
+                ]);
+            });
+        }
+    }
+};

+ 4 - 0
packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php

@@ -72,6 +72,10 @@ class OrderEvents
                 $rule
             );
 
+            // 将赚取的积分写回订单表
+            $order->reward_points_earned = $points;
+            $order->save();
+
             Log::info('Order points added as PENDING', [
                 'order_id' => $order->id,
                 'customer_id' => $order->customer_id,

+ 2 - 0
packages/Longyi/RewardPoints/src/Resources/lang/en/rewardpoints.php

@@ -1,6 +1,8 @@
 <?php
 
 return [
+    'reward-points-amount' => 'Reward Points Amount',
+
     'customer' => [
         'my-reward-points' => 'My Reward Points',
         'available-points' => 'Available Points',

+ 2 - 0
packages/Longyi/RewardPoints/src/Resources/lang/zh_CN/rewardpoints.php

@@ -1,6 +1,8 @@
 <?php
 
 return [
+    'reward-points-amount' => '积分抵扣金额',
+
     'customer' => [
         'my-reward-points' => '我的积分',
         'available-points' => '可用积分',

+ 12 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderDetailDto.php

@@ -148,6 +148,18 @@ class CustomerOrderDetailDto
 
     public ?string $applied_cart_rule_ids = null;
 
+    public ?int $reward_points_used = null;
+
+    public ?float $reward_points_amount = null;
+
+    public ?float $base_reward_points_amount = null;
+
+    public ?int $reward_points_earned = null;
+
+    public ?float $reward_points_invoiced = null;
+
+    public ?float $base_reward_points_invoiced = null;
+
     public ?string $created_at = null;
 
     public ?string $updated_at = null;

+ 8 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderListDto.php

@@ -66,6 +66,14 @@ class CustomerOrderListDto
 
     public ?string $order_currency_code = null;
 
+    public ?int $reward_points_used = null;
+
+    public ?float $reward_points_amount = null;
+
+    public ?float $base_reward_points_amount = null;
+
+    public ?int $reward_points_earned = null;
+
     public ?string $created_at = null;
 
     public ?string $updated_at = null;

+ 15 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductAttributeDto.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ProductAttributeDto
+{
+    public function __construct(
+        public ?string $code = null,
+        public ?string $label = null,
+        public mixed $value = null,
+    ) {}
+}

+ 8 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductDetailDto.php

@@ -25,6 +25,14 @@ class ProductDetailDto
 
     public ?string $short_description = null;
 
+    /**
+     * Customer Features HTML (textarea attribute with code "customer_features").
+     * Returns null when the attribute is missing or empty.
+     */
+    /** @var ProductAttributeDto[] All custom attribute values for this product. */
+    #[ApiProperty(readableLink: true)]
+    public array $attributes = [];
+
     public ?float $price = null;
 
     public ?float $special_price = null;

+ 6 - 0
packages/Webkul/BagistoApi/src/Models/CustomerOrder.php

@@ -130,6 +130,12 @@ class CustomerOrder extends Model
         'customer_id'                   => 'int',
         'channel_id'                    => 'int',
         'applied_cart_rule_ids'         => 'string',
+        'reward_points_used'            => 'int',
+        'reward_points_amount'          => 'float',
+        'base_reward_points_amount'     => 'float',
+        'reward_points_earned'          => 'int',
+        'reward_points_invoiced'        => 'float',
+        'base_reward_points_invoiced'   => 'float',
         'created_at'                    => 'datetime',
         'updated_at'                    => 'datetime',
     ];

+ 68 - 0
packages/Webkul/BagistoApi/src/Models/Product.php

@@ -503,6 +503,74 @@ class Product extends BaseProduct
         return $this->hasMany(AttributeValue::class, 'product_id');
     }
 
+    /**
+     * Get all custom attribute values for this product.
+     *
+     * Aggregates every `attribute_values` row into a flat list of
+     * `{ code, label, value }` entries (resolved for the current
+     * locale/channel). Exposed to GraphQL/REST as `attributes` so any
+     * attribute added later in the Attributes table is surfaced
+     * automatically without code changes.
+     *
+     * @return \Webkul\BagistoApi\Dto\ProductDetail\ProductAttributeDto[]
+     */
+    public function getAttributesAttribute(): array
+    {
+        if (! $this->relationLoaded('attribute_values')) {
+            $this->load('attribute_values.attribute');
+        } elseif (! $this->attribute_values->first()?->relationLoaded('attribute')) {
+            $this->load('attribute_values.attribute');
+        }
+
+        $currentLocale = $this->locale ?? app()->getLocale();
+        $currentChannel = $this->channel ?? (core()->getCurrentChannel()->code ?? 'default');
+
+        $localeVariants = array_values(array_unique(array_filter([
+            $currentLocale,
+            core()->getCurrentChannel()->default_locale?->code,
+            null,
+        ])));
+
+        $channelVariants = [$currentChannel, null];
+
+        $seen = [];
+        $result = [];
+
+        foreach ($this->attribute_values as $av) {
+            $code = $av->attribute?->code;
+
+            if (! $code) {
+                continue;
+            }
+
+            // Only keep the best (most specific) match per attribute code.
+            if (isset($seen[$code])) {
+                continue;
+            }
+
+            $locale = $av->locale;
+            $channel = $av->channel;
+
+            $localeRank = array_search($locale, $localeVariants, true);
+            $channelRank = array_search($channel, $channelVariants, true);
+
+            // Ignore rows that don't match the current locale/channel scope.
+            if ($localeRank === false || $channelRank === false) {
+                continue;
+            }
+
+            $seen[$code] = true;
+
+            $result[] = new \Webkul\BagistoApi\Dto\ProductDetail\ProductAttributeDto(
+                code: $code,
+                label: $av->attribute?->admin_name ?? $av->attribute?->name ?? $code,
+                value: $av->value,
+            );
+        }
+
+        return $result;
+    }
+
     /**
      * Get locale context.
      */

+ 2 - 0
packages/Webkul/BagistoApi/src/State/ProductDetailProvider.php

@@ -38,6 +38,7 @@ class ProductDetailProvider implements ProviderInterface
         $product = Product::with([
             'attribute_family',
             'attribute_values',
+            'attribute_values.attribute',
             'categories',
             'channels',
             'images',
@@ -76,6 +77,7 @@ class ProductDetailProvider implements ProviderInterface
         $dto->status = (bool) $product->status;
         $dto->description = $product->description;
         $dto->short_description = $product->short_description;
+        $dto->attributes = $product->attributes;
         $dto->price = $product->price !== null ? (float) $product->price : null;
         $dto->special_price = $product->special_price !== null ? (float) $product->special_price : null;
         $dto->new = (bool) $product->new;

+ 5 - 0
packages/Webkul/BagistoApi/src/Transformers/ExpressOrderResource.php

@@ -73,6 +73,11 @@ class ExpressOrderResource extends JsonResource
                 'shipping_insurance_amount'      => $this->shipping_insurance_amount,
                 'base_shipping_insurance_amount' => $this->base_shipping_insurance_amount,
             ]),
+            $this->mergeWhen($this->reward_points_used && $this->reward_points_used > 0, [
+                'reward_points_used'             => $this->reward_points_used,
+                'reward_points_amount'           => $this->reward_points_amount,
+                'base_reward_points_amount'      => $this->base_reward_points_amount,
+            ]),
             'billing_address'          => $billing,
             'shipping_address'         => $shipping,
             'payment'                  => (new OrderPaymentResource($this->payment))->jsonSerialize(),

+ 4 - 1
packages/Webkul/Checkout/src/Models/Cart.php

@@ -38,7 +38,10 @@ class Cart extends Model implements CartContract
      * @var array
      */
     protected $casts = [
-        'additional' => 'json',
+        'additional'                => 'json',
+        'reward_points_used'        => 'integer',
+        'reward_points_amount'      => 'float',
+        'base_reward_points_amount' => 'float',
     ];
 
     /**

+ 11 - 0
packages/Webkul/Sales/src/Repositories/RefundRepository.php

@@ -231,6 +231,9 @@ class RefundRepository extends Repository
         $vipDiscountAmount = round($order->vip_discount_amount * $subtotalRatio, 2);
         $baseVipDiscountAmount = round($order->base_vip_discount_amount * $subtotalRatio, 2);
 
+        $rewardPointsAmount = round($order->reward_points_amount * $subtotalRatio, 2);
+        $baseRewardPointsAmount = round($order->base_reward_points_amount * $subtotalRatio, 2);
+
         $refund->grand_total = $refund->sub_total
             + $refund->tax_amount
             + $refund->shipping_amount
@@ -238,6 +241,7 @@ class RefundRepository extends Repository
             - $giftcardAmount
             + $vipPlusAmount
             - $vipDiscountAmount
+            - $rewardPointsAmount
             + $refund->adjustment_refund
             - $refund->adjustment_fee
             - $refund->discount_amount;
@@ -249,6 +253,7 @@ class RefundRepository extends Repository
             - $baseGiftcardAmount
             + $baseVipPlusAmount
             - $baseVipDiscountAmount
+            - $baseRewardPointsAmount
             + $refund->base_adjustment_refund
             - $refund->base_adjustment_fee
             - $refund->base_discount_amount;
@@ -329,6 +334,11 @@ class RefundRepository extends Repository
             'label'      => trans('member::app.member.vip_discount'),
         ];
 
+        $totals['reward_points'] = [
+            'price'      => round($order->base_reward_points_amount * $subtotalRatio, 2),
+            'label'      => trans('rewardpoints::rewardpoints.reward-points-amount'),
+        ];
+
         $totals['grand_total']['price'] += $totals['subtotal']['price']
             + $totals['tax']['price']
             + $totals['shipping']['price']
@@ -336,6 +346,7 @@ class RefundRepository extends Repository
             - $totals['giftcard']['price']
             + $totals['vip_plus']['price']
             - $totals['vip_discount']['price']
+            - $totals['reward_points']['price']
             + $data['adjustment_refund']
             - $data['adjustment_fee']
             - $totals['discount']['price'];

+ 5 - 0
packages/Webkul/Sales/src/Transformers/OrderResource.php

@@ -90,6 +90,11 @@ class OrderResource extends JsonResource
                 'shipping_insurance_amount'      => $this->shipping_insurance_amount,
                 'base_shipping_insurance_amount' => $this->base_shipping_insurance_amount,
             ]),
+            $this->mergeWhen($this->reward_points_used && $this->reward_points_used > 0, [
+                'reward_points_used'             => $this->reward_points_used,
+                'reward_points_amount'           => $this->reward_points_amount,
+                'base_reward_points_amount'      => $this->base_reward_points_amount,
+            ]),
         ];
     }
 }