|
|
@@ -0,0 +1,490 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
|
|
|
+
|
|
|
+use Exception;
|
|
|
+use Illuminate\Http\JsonResponse;
|
|
|
+use Illuminate\Http\RedirectResponse;
|
|
|
+use Illuminate\Http\Request;
|
|
|
+use Illuminate\Validation\Rule;
|
|
|
+use Illuminate\View\View;
|
|
|
+use Longyi\RewardPoints\Models\RewardActiveRule;
|
|
|
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
|
|
|
+use Webkul\Admin\Http\Controllers\Controller;
|
|
|
+use Webkul\Core\Models\Channel;
|
|
|
+use Webkul\Customer\Models\CustomerGroup;
|
|
|
+
|
|
|
+class RuleController extends Controller
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * Transaction type mapping with complete configuration
|
|
|
+ */
|
|
|
+ private const TRANSACTION_TYPES = [
|
|
|
+ 1 => ['name' => 'Order', 'icon' => 'icon-shopping-cart', 'color' => 'blue'],
|
|
|
+ 2 => ['name' => 'Registration', 'icon' => 'icon-user', 'color' => 'green'],
|
|
|
+ 3 => ['name' => 'Product Review', 'icon' => 'icon-star', 'color' => 'yellow'],
|
|
|
+ 4 => ['name' => 'Daily Sign In', 'icon' => 'icon-calendar', 'color' => 'purple'],
|
|
|
+ 5 => ['name' => 'Referral', 'icon' => 'icon-share', 'color' => 'indigo'],
|
|
|
+ 6 => ['name' => 'Birthday', 'icon' => 'icon-gift', 'color' => 'pink'],
|
|
|
+ 7 => ['name' => 'Share', 'icon' => 'icon-share-alt', 'color' => 'orange'],
|
|
|
+ 8 => ['name' => 'Subscribe', 'icon' => 'icon-envelope', 'color' => 'orange'],
|
|
|
+ ];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Valid transaction type IDs
|
|
|
+ */
|
|
|
+ private const VALID_TRANSACTION_TYPES = [1, 2, 3, 4, 5, 6, 7, 8];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Color classes mapping for transaction types
|
|
|
+ */
|
|
|
+ private const COLOR_CLASSES = [
|
|
|
+ 'blue' => 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300',
|
|
|
+ 'green' => 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300',
|
|
|
+ 'yellow' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300',
|
|
|
+ 'purple' => 'bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300',
|
|
|
+ 'indigo' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300',
|
|
|
+ 'pink' => 'bg-pink-100 dark:bg-pink-900 text-pink-700 dark:text-pink-300',
|
|
|
+ 'orange' => 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300',
|
|
|
+ 'gray' => 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400',
|
|
|
+ ];
|
|
|
+
|
|
|
+ protected RewardPointRepository $rewardPointRepository;
|
|
|
+
|
|
|
+ protected array $_config;
|
|
|
+
|
|
|
+ public function __construct(RewardPointRepository $rewardPointRepository)
|
|
|
+ {
|
|
|
+ $this->rewardPointRepository = $rewardPointRepository;
|
|
|
+ $this->_config = request('_config', []);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Display a listing of reward rules
|
|
|
+ */
|
|
|
+ public function index(): View
|
|
|
+ {
|
|
|
+ $rules = RewardActiveRule::query()
|
|
|
+ ->orderBy('rule_id', 'desc')
|
|
|
+ ->paginate(15);
|
|
|
+
|
|
|
+ $customerGroupsList = $this->getCustomerGroups();
|
|
|
+ $transactionTypes = self::TRANSACTION_TYPES;
|
|
|
+ $colorClasses = self::COLOR_CLASSES;
|
|
|
+
|
|
|
+ $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.index';
|
|
|
+
|
|
|
+ return view($view, compact('rules', 'customerGroupsList', 'transactionTypes', 'colorClasses'));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Show the form for creating a new reward rule
|
|
|
+ */
|
|
|
+ public function create(): View
|
|
|
+ {
|
|
|
+ $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.create';
|
|
|
+
|
|
|
+ return view($view, [
|
|
|
+ 'transactionTypes' => self::TRANSACTION_TYPES,
|
|
|
+ 'storeViews' => $this->getStoreViews(),
|
|
|
+ 'customerGroups' => $this->getCustomerGroups(),
|
|
|
+ 'colorClasses' => self::COLOR_CLASSES,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Show the form for editing the specified reward rule
|
|
|
+ */
|
|
|
+ public function edit(int $id): View
|
|
|
+ {
|
|
|
+ $rule = RewardActiveRule::findOrFail($id);
|
|
|
+
|
|
|
+ // 处理 Store Views 数据
|
|
|
+ $selectedStoreViews = $this->parseStoreViews($rule);
|
|
|
+
|
|
|
+ // 如果规则适用于所有店铺(store_view = '0'),则转换为 'all' 选项
|
|
|
+ $selectedStoreViewsForSelect = [];
|
|
|
+ if ($rule->store_view === '0' || empty($selectedStoreViews)) {
|
|
|
+ $selectedStoreViewsForSelect = ['all'];
|
|
|
+ } else {
|
|
|
+ $selectedStoreViewsForSelect = $selectedStoreViews;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理客户群组数据
|
|
|
+ $selectedCustomerGroupsArray = $this->parseCustomerGroups($rule);
|
|
|
+ $selectedCustomerGroupsPoints = $rule->enable_different_points_by_group
|
|
|
+ ? (json_decode($rule->customer_group_ids, true) ?: [])
|
|
|
+ : [];
|
|
|
+
|
|
|
+ $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.edit';
|
|
|
+
|
|
|
+ return view($view, [
|
|
|
+ 'rule' => $rule,
|
|
|
+ 'transactionTypes' => self::TRANSACTION_TYPES,
|
|
|
+ 'storeViews' => $this->getStoreViews(),
|
|
|
+ 'customerGroups' => $this->getCustomerGroups(),
|
|
|
+ 'selectedStoreViewsForSelect' => $selectedStoreViewsForSelect,
|
|
|
+ 'selectedCustomerGroupsArray' => $selectedCustomerGroupsArray,
|
|
|
+ 'selectedCustomerGroupsPoints' => $selectedCustomerGroupsPoints,
|
|
|
+ 'colorClasses' => self::COLOR_CLASSES,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Store a newly created reward rule
|
|
|
+ */
|
|
|
+ public function store(Request $request): RedirectResponse
|
|
|
+ {
|
|
|
+ $validated = $this->validateStoreRequest($request);
|
|
|
+
|
|
|
+ try {
|
|
|
+ $data = $this->prepareDataForSave($request, $validated);
|
|
|
+ RewardActiveRule::create($data);
|
|
|
+
|
|
|
+ session()->flash('success', 'Reward rule created successfully.');
|
|
|
+
|
|
|
+ return $this->redirectToIndex();
|
|
|
+ } catch (Exception $e) {
|
|
|
+ session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
|
|
|
+ return redirect()->back()->withInput();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update the specified reward rule
|
|
|
+ */
|
|
|
+ public function update(Request $request, int $id): RedirectResponse
|
|
|
+ {
|
|
|
+ $validated = $this->validateUpdateRequest($request);
|
|
|
+ $rule = RewardActiveRule::findOrFail($id);
|
|
|
+
|
|
|
+ try {
|
|
|
+ $data = $this->prepareDataForSave($request, $validated);
|
|
|
+
|
|
|
+ // 确保 enable_different_points_by_group 字段被正确设置
|
|
|
+ $data['enable_different_points_by_group'] = $request->boolean('enable_different_points_by_group');
|
|
|
+
|
|
|
+ $rule->update($data);
|
|
|
+
|
|
|
+ session()->flash('success', 'Reward rule updated successfully.');
|
|
|
+
|
|
|
+ return $this->redirectToIndex();
|
|
|
+ } catch (Exception $e) {
|
|
|
+ session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
|
|
|
+ return redirect()->back()->withInput();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove the specified reward rule
|
|
|
+ */
|
|
|
+ public function destroy(int $id): JsonResponse|RedirectResponse
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $rule = RewardActiveRule::findOrFail($id);
|
|
|
+ $rule->delete();
|
|
|
+
|
|
|
+ $message = 'Reward rule deleted successfully.';
|
|
|
+ session()->flash('success', $message);
|
|
|
+
|
|
|
+ if (request()->ajax()) {
|
|
|
+ return response()->json(['status' => true, 'message' => $message]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->redirectToIndex();
|
|
|
+ } catch (Exception $e) {
|
|
|
+ $message = 'Error deleting reward rule: ' . $e->getMessage();
|
|
|
+ session()->flash('error', $message);
|
|
|
+
|
|
|
+ if (request()->ajax()) {
|
|
|
+ return response()->json(['status' => false, 'message' => 'Error deleting reward rule.'], 500);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->redirectToIndex()->with('error', $message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update rule status
|
|
|
+ */
|
|
|
+ public function updateStatus(int $id, Request $request): JsonResponse
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $rule = RewardActiveRule::findOrFail($id);
|
|
|
+ $rule->status = $request->input('status', 0);
|
|
|
+ $rule->save();
|
|
|
+
|
|
|
+ return response()->json([
|
|
|
+ 'status' => true,
|
|
|
+ 'message' => 'Rule status updated successfully.',
|
|
|
+ ]);
|
|
|
+ } catch (Exception $e) {
|
|
|
+ return response()->json([
|
|
|
+ 'status' => false,
|
|
|
+ 'message' => 'Error updating rule status.',
|
|
|
+ ], 500);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Validate store request
|
|
|
+ */
|
|
|
+ private function validateStoreRequest(Request $request): array
|
|
|
+ {
|
|
|
+ $rules = [
|
|
|
+ 'rule_name' => 'required|string|max:255',
|
|
|
+ 'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
|
|
|
+ 'status' => 'required|boolean',
|
|
|
+ 'enable_different_points_by_group' => 'nullable|boolean',
|
|
|
+ 'expired_day' => 'nullable|integer|min:0',
|
|
|
+ 'comment' => 'nullable|string|max:500',
|
|
|
+ 'store_view' => 'nullable|array',
|
|
|
+ 'store_view.*' => 'nullable|string',
|
|
|
+ ];
|
|
|
+
|
|
|
+ return $request->validate($rules);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Validate update request
|
|
|
+ */
|
|
|
+ private function validateUpdateRequest(Request $request): array
|
|
|
+ {
|
|
|
+ $rules = [
|
|
|
+ 'rule_name' => 'required|string|max:255',
|
|
|
+ 'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
|
|
|
+ 'status' => 'required|boolean',
|
|
|
+ 'enable_different_points_by_group' => 'nullable|boolean',
|
|
|
+ 'default_expired' => 'nullable|boolean',
|
|
|
+ 'expired_day' => 'nullable|integer|min:0',
|
|
|
+ 'coupon_code' => 'nullable|string|max:255',
|
|
|
+ 'comment' => 'nullable|string|max:500',
|
|
|
+ 'store_view' => 'nullable|array',
|
|
|
+ 'store_view.*' => 'nullable|string',
|
|
|
+ ];
|
|
|
+
|
|
|
+ return $request->validate($rules);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Prepare data for saving
|
|
|
+ */
|
|
|
+ private function prepareDataForSave(Request $request, array $validated): array
|
|
|
+ {
|
|
|
+ $data = array_intersect_key($validated, array_flip([
|
|
|
+ 'rule_name',
|
|
|
+ 'type_of_transaction',
|
|
|
+ 'default_expired',
|
|
|
+ 'expired_day',
|
|
|
+ 'date_event',
|
|
|
+ 'comment',
|
|
|
+ 'coupon_code',
|
|
|
+ 'status',
|
|
|
+ 'enable_different_points_by_group',
|
|
|
+ ]));
|
|
|
+
|
|
|
+ // Handle store views - 修复:确保正确处理 store_view
|
|
|
+ $storeViews = $request->get('store_view', []);
|
|
|
+ $data['store_view'] = $this->formatStoreViews($storeViews);
|
|
|
+
|
|
|
+ // Handle points configuration
|
|
|
+ $this->handlePointsConfiguration($request, $data);
|
|
|
+
|
|
|
+ return $data;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Format store views for database storage
|
|
|
+ */
|
|
|
+ private function formatStoreViews($storeViews): string
|
|
|
+{
|
|
|
+ // 如果 storeViews 为空或为 '0',返回 '0' 表示所有店铺
|
|
|
+ if (empty($storeViews) || $storeViews === '0') {
|
|
|
+ return '0';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是单个值,直接返回
|
|
|
+ if (is_scalar($storeViews) && !empty($storeViews)) {
|
|
|
+ return (string)$storeViews;
|
|
|
+ }
|
|
|
+
|
|
|
+ return '0';
|
|
|
+}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handle points configuration based on group settings
|
|
|
+ */
|
|
|
+ private function handlePointsConfiguration(Request $request, array &$data): void
|
|
|
+ {
|
|
|
+ if ($request->boolean('enable_different_points_by_group')) {
|
|
|
+ $groupPoints = $request->get('group_points', []);
|
|
|
+
|
|
|
+ // 确保 $groupPoints 是数组
|
|
|
+ if (!is_array($groupPoints)) {
|
|
|
+ $groupPoints = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 过滤并处理群组积分数据,确保键是字符串或整数
|
|
|
+ $filteredPoints = [];
|
|
|
+ foreach ($groupPoints as $groupId => $points) {
|
|
|
+ // 确保键是标量类型且不为空
|
|
|
+ if (is_scalar($groupId) && !empty($groupId)) {
|
|
|
+ // 确保积分值是数字
|
|
|
+ $pointsValue = is_numeric($points) ? (int)$points : 0;
|
|
|
+ if ($pointsValue >= 0) {
|
|
|
+ $filteredPoints[(string)$groupId] = $pointsValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $data['customer_group_ids'] = !empty($filteredPoints) ? json_encode($filteredPoints) : '{}';
|
|
|
+ $data['reward_point'] = 0;
|
|
|
+ } else {
|
|
|
+ $request->validate([
|
|
|
+ 'reward_point' => 'required|integer|min:0',
|
|
|
+ ]);
|
|
|
+ $data['reward_point'] = (int)$request->get('reward_point');
|
|
|
+ $data['customer_group_ids'] = $this->formatCustomerGroups($request->get('customer_group_ids', []));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Format customer groups for database storage
|
|
|
+ */
|
|
|
+ private function formatCustomerGroups($customerGroups): string
|
|
|
+ {
|
|
|
+ if (is_array($customerGroups) && !empty($customerGroups)) {
|
|
|
+ // 过滤掉空值并确保所有值都是标量
|
|
|
+ $filtered = [];
|
|
|
+ foreach ($customerGroups as $groupId) {
|
|
|
+ if (is_scalar($groupId) && !empty($groupId)) {
|
|
|
+ $filtered[] = (string)$groupId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return !empty($filtered) ? implode(',', array_unique($filtered)) : '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parse store views from rule
|
|
|
+ */
|
|
|
+ private function parseStoreViews(RewardActiveRule $rule): array
|
|
|
+ {
|
|
|
+ if ($rule->store_view && $rule->store_view !== '0') {
|
|
|
+ $views = explode(',', $rule->store_view);
|
|
|
+ // 过滤空值
|
|
|
+ return array_filter($views, function($view) {
|
|
|
+ return !empty($view);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parse customer groups from rule
|
|
|
+ */
|
|
|
+ private function parseCustomerGroups(RewardActiveRule $rule): array
|
|
|
+ {
|
|
|
+ if ($rule->enable_different_points_by_group) {
|
|
|
+ $decoded = json_decode($rule->customer_group_ids, true);
|
|
|
+ if (is_array($decoded)) {
|
|
|
+ // 确保解码后的数据格式正确
|
|
|
+ $result = [];
|
|
|
+ foreach ($decoded as $groupId => $points) {
|
|
|
+ if (is_scalar($groupId) && !empty($groupId)) {
|
|
|
+ $result[(string)$groupId] = is_numeric($points) ? (int)$points : 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($rule->customer_group_ids && $rule->customer_group_ids !== '') {
|
|
|
+ $groups = explode(',', $rule->customer_group_ids);
|
|
|
+ // 过滤空值
|
|
|
+ return array_filter($groups, function($group) {
|
|
|
+ return !empty($group);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all store views
|
|
|
+ */
|
|
|
+ protected function getStoreViews(): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $channels = Channel::all();
|
|
|
+ $storeViews = [];
|
|
|
+
|
|
|
+ foreach ($channels as $channel) {
|
|
|
+ $storeViews[$channel->code] = $channel->name;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $storeViews;
|
|
|
+ } catch (Exception $e) {
|
|
|
+ return ['0' => 'All Stores'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get all customer groups
|
|
|
+ */
|
|
|
+ protected function getCustomerGroups(): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $groups = CustomerGroup::orderBy('id')->get();
|
|
|
+ $customerGroups = [];
|
|
|
+
|
|
|
+ foreach ($groups as $group) {
|
|
|
+ $customerGroups[$group->id] = $group->name;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $customerGroups;
|
|
|
+ } catch (Exception $e) {
|
|
|
+ return ['0' => 'All Groups'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get transaction type configuration
|
|
|
+ */
|
|
|
+ public function getTransactionTypeConfig(): array
|
|
|
+ {
|
|
|
+ return self::TRANSACTION_TYPES;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get transaction type name
|
|
|
+ */
|
|
|
+ protected function getTransactionTypeName(int $typeId): string
|
|
|
+ {
|
|
|
+ return self::TRANSACTION_TYPES[$typeId]['name'] ?? 'Unknown';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get transaction type color classes
|
|
|
+ */
|
|
|
+ protected function getTransactionTypeColorClass(int $typeId): string
|
|
|
+ {
|
|
|
+ $color = self::TRANSACTION_TYPES[$typeId]['color'] ?? 'gray';
|
|
|
+ return self::COLOR_CLASSES[$color] ?? self::COLOR_CLASSES['gray'];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Redirect to index route
|
|
|
+ */
|
|
|
+ private function redirectToIndex(): RedirectResponse
|
|
|
+ {
|
|
|
+ $route = $this->_config['redirect'] ?? 'admin.reward-points.rules.index';
|
|
|
+ return redirect()->route($route);
|
|
|
+ }
|
|
|
+}
|