RuleController.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. <?php
  2. namespace Longyi\RewardPoints\Http\Controllers\Admin;
  3. use Exception;
  4. use Illuminate\Http\JsonResponse;
  5. use Illuminate\Http\RedirectResponse;
  6. use Illuminate\Http\Request;
  7. use Illuminate\Validation\Rule;
  8. use Illuminate\View\View;
  9. use Longyi\RewardPoints\Models\RewardActiveRule;
  10. use Longyi\RewardPoints\Repositories\RewardPointRepository;
  11. use Webkul\Admin\Http\Controllers\Controller;
  12. use Webkul\Core\Models\Channel;
  13. use Webkul\Customer\Models\CustomerGroup;
  14. class RuleController extends Controller
  15. {
  16. /**
  17. * Transaction type mapping with complete configuration
  18. */
  19. private const TRANSACTION_TYPES = [
  20. 3 => ['name' => 'Order', 'icon' => 'icon-shopping-cart', 'color' => 'blue'],
  21. 2 => ['name' => 'Registration', 'icon' => 'icon-user', 'color' => 'green'],
  22. 4 => ['name' => 'Product Review', 'icon' => 'icon-star', 'color' => 'yellow'],
  23. 9 => ['name' => 'Login', 'icon' => 'icon-calendar', 'color' => 'purple'],
  24. 5 => ['name' => 'Referral', 'icon' => 'icon-share', 'color' => 'indigo'],
  25. 6 => ['name' => 'Birthday', 'icon' => 'icon-gift', 'color' => 'pink'],
  26. 7 => ['name' => 'Share', 'icon' => 'icon-share-alt', 'color' => 'orange'],
  27. 8 => ['name' => 'Subscribe', 'icon' => 'icon-envelope', 'color' => 'orange'],
  28. ];
  29. /**
  30. * Valid transaction type IDs
  31. */
  32. private const VALID_TRANSACTION_TYPES = [1, 2, 3, 4, 5, 6, 7, 8];
  33. /**
  34. * Color classes mapping for transaction types
  35. */
  36. private const COLOR_CLASSES = [
  37. 'blue' => 'bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300',
  38. 'green' => 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300',
  39. 'yellow' => 'bg-yellow-100 dark:bg-yellow-900 text-yellow-700 dark:text-yellow-300',
  40. 'purple' => 'bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300',
  41. 'indigo' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300',
  42. 'pink' => 'bg-pink-100 dark:bg-pink-900 text-pink-700 dark:text-pink-300',
  43. 'orange' => 'bg-orange-100 dark:bg-orange-900 text-orange-700 dark:text-orange-300',
  44. 'gray' => 'bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400',
  45. ];
  46. protected RewardPointRepository $rewardPointRepository;
  47. protected array $_config;
  48. public function __construct(RewardPointRepository $rewardPointRepository)
  49. {
  50. $this->rewardPointRepository = $rewardPointRepository;
  51. $this->_config = request('_config', []);
  52. }
  53. /**
  54. * Display a listing of reward rules
  55. */
  56. public function index(): View
  57. {
  58. $rules = RewardActiveRule::query()
  59. ->orderBy('rule_id', 'desc')
  60. ->paginate(15);
  61. $customerGroupsList = $this->getCustomerGroups();
  62. $transactionTypes = self::TRANSACTION_TYPES;
  63. $colorClasses = self::COLOR_CLASSES;
  64. $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.index';
  65. return view($view, compact('rules', 'customerGroupsList', 'transactionTypes', 'colorClasses'));
  66. }
  67. /**
  68. * Show the form for creating a new reward rule
  69. */
  70. public function create(): View
  71. {
  72. $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.create';
  73. return view($view, [
  74. 'transactionTypes' => self::TRANSACTION_TYPES,
  75. 'storeViews' => $this->getStoreViews(),
  76. 'customerGroups' => $this->getCustomerGroups(),
  77. 'colorClasses' => self::COLOR_CLASSES,
  78. ]);
  79. }
  80. /**
  81. * Show the form for editing the specified reward rule
  82. */
  83. public function edit(int $id): View
  84. {
  85. $rule = RewardActiveRule::findOrFail($id);
  86. // 处理 Store Views 数据
  87. $selectedStoreViews = $this->parseStoreViews($rule);
  88. // 如果规则适用于所有店铺(store_view = '0'),则转换为 'all' 选项
  89. $selectedStoreViewsForSelect = [];
  90. if ($rule->store_view === '0' || empty($selectedStoreViews)) {
  91. $selectedStoreViewsForSelect = ['all'];
  92. } else {
  93. $selectedStoreViewsForSelect = $selectedStoreViews;
  94. }
  95. // 处理客户群组数据
  96. $selectedCustomerGroupsArray = $this->parseCustomerGroups($rule);
  97. $selectedCustomerGroupsPoints = $rule->enable_different_points_by_group
  98. ? (json_decode($rule->customer_group_ids, true) ?: [])
  99. : [];
  100. $view = $this->_config['view'] ?? 'rewardpoints::admin.rules.edit';
  101. return view($view, [
  102. 'rule' => $rule,
  103. 'transactionTypes' => self::TRANSACTION_TYPES,
  104. 'storeViews' => $this->getStoreViews(),
  105. 'customerGroups' => $this->getCustomerGroups(),
  106. 'selectedStoreViewsForSelect' => $selectedStoreViewsForSelect,
  107. 'selectedCustomerGroupsArray' => $selectedCustomerGroupsArray,
  108. 'selectedCustomerGroupsPoints' => $selectedCustomerGroupsPoints,
  109. 'colorClasses' => self::COLOR_CLASSES,
  110. ]);
  111. }
  112. /**
  113. * Store a newly created reward rule
  114. */
  115. public function store(Request $request): RedirectResponse
  116. {
  117. $validated = $this->validateStoreRequest($request);
  118. try {
  119. $data = $this->prepareDataForSave($request, $validated);
  120. RewardActiveRule::create($data);
  121. session()->flash('success', 'Reward rule created successfully.');
  122. return $this->redirectToIndex();
  123. } catch (Exception $e) {
  124. session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
  125. return redirect()->back()->withInput();
  126. }
  127. }
  128. /**
  129. * Update the specified reward rule
  130. */
  131. public function update(Request $request, int $id): RedirectResponse
  132. {
  133. $validated = $this->validateUpdateRequest($request);
  134. $rule = RewardActiveRule::findOrFail($id);
  135. try {
  136. $data = $this->prepareDataForSave($request, $validated);
  137. // 确保 enable_different_points_by_group 字段被正确设置
  138. $data['enable_different_points_by_group'] = $request->boolean('enable_different_points_by_group');
  139. $rule->update($data);
  140. session()->flash('success', 'Reward rule updated successfully.');
  141. return $this->redirectToIndex();
  142. } catch (Exception $e) {
  143. session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
  144. return redirect()->back()->withInput();
  145. }
  146. }
  147. /**
  148. * Remove the specified reward rule
  149. */
  150. public function destroy(int $id): JsonResponse|RedirectResponse
  151. {
  152. try {
  153. $rule = RewardActiveRule::findOrFail($id);
  154. $rule->delete();
  155. $message = 'Reward rule deleted successfully.';
  156. session()->flash('success', $message);
  157. if (request()->ajax()) {
  158. return response()->json(['status' => true, 'message' => $message]);
  159. }
  160. return $this->redirectToIndex();
  161. } catch (Exception $e) {
  162. $message = 'Error deleting reward rule: ' . $e->getMessage();
  163. session()->flash('error', $message);
  164. if (request()->ajax()) {
  165. return response()->json(['status' => false, 'message' => 'Error deleting reward rule.'], 500);
  166. }
  167. return $this->redirectToIndex()->with('error', $message);
  168. }
  169. }
  170. /**
  171. * Update rule status
  172. */
  173. public function updateStatus(int $id, Request $request): JsonResponse
  174. {
  175. try {
  176. $rule = RewardActiveRule::findOrFail($id);
  177. $rule->status = $request->input('status', 0);
  178. $rule->save();
  179. return response()->json([
  180. 'status' => true,
  181. 'message' => 'Rule status updated successfully.',
  182. ]);
  183. } catch (Exception $e) {
  184. return response()->json([
  185. 'status' => false,
  186. 'message' => 'Error updating rule status.',
  187. ], 500);
  188. }
  189. }
  190. /**
  191. * Validate store request
  192. */
  193. private function validateStoreRequest(Request $request): array
  194. {
  195. $rules = [
  196. 'rule_name' => 'required|string|max:255',
  197. 'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
  198. 'status' => 'required|boolean',
  199. 'enable_different_points_by_group' => 'nullable|boolean',
  200. 'expired_day' => 'nullable|integer|min:0',
  201. 'comment' => 'nullable|string|max:500',
  202. 'store_view' => 'nullable|array',
  203. 'store_view.*' => 'nullable|string',
  204. ];
  205. return $request->validate($rules);
  206. }
  207. /**
  208. * Validate update request
  209. */
  210. private function validateUpdateRequest(Request $request): array
  211. {
  212. $rules = [
  213. 'rule_name' => 'required|string|max:255',
  214. 'type_of_transaction' => ['required', 'integer', Rule::in(self::VALID_TRANSACTION_TYPES)],
  215. 'status' => 'required|boolean',
  216. 'enable_different_points_by_group' => 'nullable|boolean',
  217. 'default_expired' => 'nullable|boolean',
  218. 'expired_day' => 'nullable|integer|min:0',
  219. 'coupon_code' => 'nullable|string|max:255',
  220. 'comment' => 'nullable|string|max:500',
  221. 'store_view' => 'nullable|array',
  222. 'store_view.*' => 'nullable|string',
  223. ];
  224. return $request->validate($rules);
  225. }
  226. /**
  227. * Prepare data for saving
  228. */
  229. private function prepareDataForSave(Request $request, array $validated): array
  230. {
  231. $data = array_intersect_key($validated, array_flip([
  232. 'rule_name',
  233. 'type_of_transaction',
  234. 'default_expired',
  235. 'expired_day',
  236. 'date_event',
  237. 'comment',
  238. 'coupon_code',
  239. 'status',
  240. 'enable_different_points_by_group',
  241. ]));
  242. // Handle store views - 修复:确保正确处理 store_view
  243. $storeViews = $request->get('store_view', []);
  244. $data['store_view'] = $this->formatStoreViews($storeViews);
  245. // Handle points configuration
  246. $this->handlePointsConfiguration($request, $data);
  247. return $data;
  248. }
  249. /**
  250. * Format store views for database storage
  251. */
  252. private function formatStoreViews($storeViews): string
  253. {
  254. // 如果 storeViews 为空或为 '0',返回 '0' 表示所有店铺
  255. if (empty($storeViews) || $storeViews === '0') {
  256. return '0';
  257. }
  258. // 如果是单个值,直接返回
  259. if (is_scalar($storeViews) && !empty($storeViews)) {
  260. return (string)$storeViews;
  261. }
  262. return '0';
  263. }
  264. /**
  265. * Handle points configuration based on group settings
  266. */
  267. private function handlePointsConfiguration(Request $request, array &$data): void
  268. {
  269. if ($request->boolean('enable_different_points_by_group')) {
  270. $groupPoints = $request->get('group_points', []);
  271. // 确保 $groupPoints 是数组
  272. if (!is_array($groupPoints)) {
  273. $groupPoints = [];
  274. }
  275. // 过滤并处理群组积分数据,确保键是字符串或整数
  276. $filteredPoints = [];
  277. foreach ($groupPoints as $groupId => $points) {
  278. // 确保键是标量类型且不为空
  279. if (is_scalar($groupId) && !empty($groupId)) {
  280. // 确保积分值是数字
  281. $pointsValue = is_numeric($points) ? (int)$points : 0;
  282. if ($pointsValue >= 0) {
  283. $filteredPoints[(string)$groupId] = $pointsValue;
  284. }
  285. }
  286. }
  287. $data['customer_group_ids'] = !empty($filteredPoints) ? json_encode($filteredPoints) : '{}';
  288. $data['reward_point'] = 0;
  289. } else {
  290. $request->validate([
  291. 'reward_point' => 'required|integer|min:0',
  292. ]);
  293. $data['reward_point'] = (int)$request->get('reward_point');
  294. $data['customer_group_ids'] = $this->formatCustomerGroups($request->get('customer_group_ids', []));
  295. }
  296. }
  297. /**
  298. * Format customer groups for database storage
  299. */
  300. private function formatCustomerGroups($customerGroups): string
  301. {
  302. if (is_array($customerGroups) && !empty($customerGroups)) {
  303. // 过滤掉空值并确保所有值都是标量
  304. $filtered = [];
  305. foreach ($customerGroups as $groupId) {
  306. if (is_scalar($groupId) && !empty($groupId)) {
  307. $filtered[] = (string)$groupId;
  308. }
  309. }
  310. return !empty($filtered) ? implode(',', array_unique($filtered)) : '';
  311. }
  312. return '';
  313. }
  314. /**
  315. * Parse store views from rule
  316. */
  317. private function parseStoreViews(RewardActiveRule $rule): array
  318. {
  319. if ($rule->store_view && $rule->store_view !== '0') {
  320. $views = explode(',', $rule->store_view);
  321. // 过滤空值
  322. return array_filter($views, function($view) {
  323. return !empty($view);
  324. });
  325. }
  326. return [];
  327. }
  328. /**
  329. * Parse customer groups from rule
  330. */
  331. private function parseCustomerGroups(RewardActiveRule $rule): array
  332. {
  333. if ($rule->enable_different_points_by_group) {
  334. $decoded = json_decode($rule->customer_group_ids, true);
  335. if (is_array($decoded)) {
  336. // 确保解码后的数据格式正确
  337. $result = [];
  338. foreach ($decoded as $groupId => $points) {
  339. if (is_scalar($groupId) && !empty($groupId)) {
  340. $result[(string)$groupId] = is_numeric($points) ? (int)$points : 0;
  341. }
  342. }
  343. return $result;
  344. }
  345. return [];
  346. }
  347. if ($rule->customer_group_ids && $rule->customer_group_ids !== '') {
  348. $groups = explode(',', $rule->customer_group_ids);
  349. // 过滤空值
  350. return array_filter($groups, function($group) {
  351. return !empty($group);
  352. });
  353. }
  354. return [];
  355. }
  356. /**
  357. * Get all store views
  358. */
  359. protected function getStoreViews(): array
  360. {
  361. try {
  362. $channels = Channel::all();
  363. $storeViews = [];
  364. foreach ($channels as $channel) {
  365. $storeViews[$channel->code] = $channel->name;
  366. }
  367. return $storeViews;
  368. } catch (Exception $e) {
  369. return ['0' => 'All Stores'];
  370. }
  371. }
  372. /**
  373. * Get all customer groups
  374. */
  375. protected function getCustomerGroups(): array
  376. {
  377. try {
  378. $groups = CustomerGroup::orderBy('id')->get();
  379. $customerGroups = [];
  380. foreach ($groups as $group) {
  381. $customerGroups[$group->id] = $group->name;
  382. }
  383. return $customerGroups;
  384. } catch (Exception $e) {
  385. return ['0' => 'All Groups'];
  386. }
  387. }
  388. /**
  389. * Get transaction type configuration
  390. */
  391. public function getTransactionTypeConfig(): array
  392. {
  393. return self::TRANSACTION_TYPES;
  394. }
  395. /**
  396. * Get transaction type name
  397. */
  398. protected function getTransactionTypeName(int $typeId): string
  399. {
  400. return self::TRANSACTION_TYPES[$typeId]['name'] ?? 'Unknown';
  401. }
  402. /**
  403. * Get transaction type color classes
  404. */
  405. protected function getTransactionTypeColorClass(int $typeId): string
  406. {
  407. $color = self::TRANSACTION_TYPES[$typeId]['color'] ?? 'gray';
  408. return self::COLOR_CLASSES[$color] ?? self::COLOR_CLASSES['gray'];
  409. }
  410. /**
  411. * Redirect to index route
  412. */
  413. private function redirectToIndex(): RedirectResponse
  414. {
  415. $route = $this->_config['redirect'] ?? 'admin.reward-points.rules.index';
  416. return redirect()->route($route);
  417. }
  418. }