PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.1
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / app / Services / Filter / OrderFilter.php

OrderFilter.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.1, at app/Services/Filter/OrderFilter.php

708 lines 30.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Filter;
4
5 use Closure;
6 use FluentCart\Api\ModuleSettings;
7 use FluentCart\App\App;
8 use FluentCart\App\Helpers\AddressHelper;
9 use FluentCart\App\Helpers\Status;
10 use FluentCart\App\Models\Order;
11 use FluentCart\Framework\Database\Orm\Builder;
12 use FluentCart\Framework\Support\Arr;
13 use FluentCartPro\App\Modules\Licensing\Models\License;
14 use FluentCart\App\Modules\PaymentMethods\Core\GatewayManager;
15
16
17 class OrderFilter extends BaseFilter
18 {
19 protected static function statusFilterKeys(): array
20 {
21 return ['payment_statuses', 'order_statuses', 'shipping_statuses'];
22 }
23
24 public function applySimpleFilter(?string $search = null): void
25 {
26 $search = $search ?? $this->search;
27 $isApplied = $this->applySimpleOperatorFilter($search);
28 if ($isApplied) {
29 return;
30 }
31
32 foreach (static::statusFilterKeys() as $statusKey) {
33 $this->query->when(Arr::get($this->args, $statusKey), function ($query, $values) use ($statusKey) {
34 return $query->whereIn($statusKey, Arr::wrap($values));
35 });
36 }
37
38 if (!empty($search)) {
39 $search = trim($search);
40 $searchLike = addcslashes($search, '\\%_');
41
42 $this->query->where(function ($query) use ($search, $searchLike) {
43 $query->where('invoice_no', 'LIKE', "%{$search}%")
44 ->orWhereHas('customer', function ($customerQuery) use ($search, $searchLike) {
45 $customerQuery
46 ->where('email', 'LIKE', "%{$search}%")
47 ->orWhereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$searchLike}%"]);
48 })
49 ->orWhereHas('order_items', function ($query) use ($searchLike) {
50 $query->where('title', 'LIKE', "%{$searchLike}%");
51 $query->orWhere('post_title', 'LIKE', "%{$searchLike}%");
52 });
53 });
54 }
55 }
56
57 public function tabsMap(): array
58 {
59 return [
60 'on-hold' => 'status',
61 'paid' => 'payment_status',
62 //'unpaid' => 'payment_status',
63 'completed' => 'status',
64 'processing' => 'status',
65 'renewal' => 'type',
66 'subscription' => 'type',
67 'onetime' => 'type',
68 'refunded' => 'payment_status',
69 'partially_refunded' => 'payment_status',
70 'upgraded_to' => 'upgraded_to',
71 'upgraded_from' => 'upgraded_from',
72 'b2b_purchase' => 'b2b_purchase',
73 'reverse_charge' => 'reverse_charge',
74 ];
75 }
76
77 public function getModel(): string
78 {
79 return Order::class;
80 }
81
82 public static function getFilterName(): string
83 {
84 return 'orders';
85 }
86
87 /**
88 * Two tiers of key, and they answer two different questions.
89 *
90 * SCREEN keys — one per calling screen, loading exactly what that screen
91 * renders. This filter serves TWO routes with different permission
92 * baselines: `GET /orders` (`orders/view`) and `GET /customers/{id}/orders`
93 * (`customers/view`). A screen key can be re-scoped for its own screen
94 * without widening anybody else's payload, and each relation is gated
95 * INSIDE the callback rather than inheriting the route's bar.
96 *
97 * PUBLIC keys — the plain relation names an external consumer can
98 * reasonably ask an order endpoint for. `GET /orders` is a REST route with
99 * consumers outside this repo, and `with[]=customer` has always meant "give
100 * me the customer". These are first-class entry points, not aliases of the
101 * screen keys: renaming them would have silently narrowed a published
102 * response shape for everyone who was already sending them.
103 *
104 * Deliberately NOT allowlisted, at any depth: `transactions` (gateway ids,
105 * card metadata), `licenses` (`license_key` serializes verbatim),
106 * `customer.wpUser` (reaches the WordPress users row).
107 *
108 * @return array<string, callable>
109 */
110 protected function allowedWiths(): array
111 {
112 return [
113 'admin_order_list' => [$this, 'adminOrderList'],
114 'admin_customer_orders' => [$this, 'adminCustomerOrders'],
115
116 'order_items' => [$this, 'publicOrderItems'],
117 'customer' => [$this, 'publicCustomer'],
118 'customer.primary_billing_address' => [$this, 'publicCustomer'],
119 ];
120 }
121
122 /**
123 * `GET /orders`, sent by OrderTable.js — the line items under each row plus
124 * the customer popover.
125 *
126 * No select on any segment: OrderItem::$appends is `payment_info`,
127 * `setup_info`, `is_custom` and `variation_display_title`, which read
128 * `other_info` and walk the row's subscription data; Customer::$appends
129 * (`full_name`, `photo`, `country_name`, `formatted_address`, `user_link`)
130 * reads first_name, last_name, country, state, city, postcode and user_id;
131 * and CustomerInfoPopover.vue renders `purchase_count` plus
132 * `primary_billing_address.phone`. Narrowing the columns blanks all of that.
133 *
134 * @param \FluentCart\Framework\Database\Orm\Builder $query
135 * @return \FluentCart\Framework\Database\Orm\Builder
136 */
137 protected function adminOrderList($query)
138 {
139 // Line items belong to the order the route already authorised on
140 // `orders/view`, so they need no second bar of their own.
141 $query->with(['order_items']);
142
143 // A customer row is customer data even when it is reached from an
144 // order, so this half carries the customers bar rather than the orders
145 // one. The billing address rides in the SAME `with()` call as its
146 // parent: each call array_merges into $eagerLoad, so a second call
147 // naming `customer.x` would inject an empty closure over `customer`.
148 if ($this->userCanAny('customers/view')) {
149 $query->with([
150 'customer',
151 'customer.primary_billing_address',
152 ]);
153 }
154
155 return $query;
156 }
157
158 /**
159 * `GET /customers/{id}/orders`, sent by SingleCustomer.vue — the order
160 * history on a customer's detail screen. The screen renders the line items
161 * and nothing else; the customer is already the page.
162 *
163 * No extra permission check, and that is the decision rather than an
164 * oversight: the route is gated on `customers/view` alone and the line
165 * items carry no bar of their own today. Adding `orders/view` here would
166 * silently strip the history for a role that holds `customers/view` and
167 * nothing else. If the line items ever need `orders/view`, that is a
168 * deliberate behaviour change and this screen has to be re-tested with a
169 * customers-only role.
170 *
171 * @param \FluentCart\Framework\Database\Orm\Builder $query
172 * @return \FluentCart\Framework\Database\Orm\Builder
173 */
174 protected function adminCustomerOrders($query)
175 {
176 return $query->with(['order_items']);
177 }
178
179 /**
180 * `with[]=order_items` — a public entry point.
181 *
182 * UNGATED, deliberately. It was ungated before this allow-list existed, and
183 * the line items are the order the route already authorised. Adding a bar
184 * now would be a silent tightening hiding under a name consumers already
185 * send, which is exactly the failure mode the public tier exists to avoid.
186 *
187 * @param \FluentCart\Framework\Database\Orm\Builder $query
188 * @return \FluentCart\Framework\Database\Orm\Builder
189 */
190 protected function publicOrderItems($query)
191 {
192 return $query->with(['order_items']);
193 }
194
195 /**
196 * `with[]=customer` and `with[]=customer.primary_billing_address` — both
197 * public entry points, both served here.
198 *
199 * One callback for the two keys because the nested path can never be
200 * allowed to reach the parent row without the parent's gate: `with('a.b')`
201 * auto-injects `a`, so a separate ungated entry for the dotted path would
202 * be a way around the check below. Loading the whole subtree from one
203 * `with()` call closes that and the clobbering problem at once.
204 *
205 * Gated: the orders route's `orders/view` says nothing about customers.
206 *
207 * @param \FluentCart\Framework\Database\Orm\Builder $query
208 * @return bool|\FluentCart\Framework\Database\Orm\Builder
209 */
210 protected function publicCustomer($query)
211 {
212 if (!$this->userCanAny('customers/view')) {
213 return false;
214 }
215
216 return $query->with([
217 'customer',
218 'customer.primary_billing_address',
219 ]);
220 }
221
222 public static function parseableKeys(): array
223 {
224 return array_merge(
225 parent::parseableKeys(),
226 static::statusFilterKeys()
227 );
228 }
229
230
231 public static function b2bMetaCondition(): Closure
232 {
233 return function ($q) {
234 $q->where('meta_key', 'business_info')
235 ->whereRaw("(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(meta_value, '$.company_name')), '') IS NOT NULL OR NULLIF(JSON_UNQUOTE(JSON_EXTRACT(meta_value, '$.legal_registration_id')), '') IS NOT NULL OR NULLIF(JSON_UNQUOTE(JSON_EXTRACT(meta_value, '$.tax_number')), '') IS NOT NULL)");
236 };
237 }
238
239 public function applyActiveViewFilter(?string $activeView = null): void
240 {
241 $activeView = $activeView ?? $this->activeView;
242 $tabsMap = $this->tabsMap();
243
244 //Apply Active Tab view
245 $this->query = $this->query->when($activeView, function (Builder $query, $activeView) use ($tabsMap) {
246
247 if ($activeView === 'upgraded_to') {
248 return $query
249 ->whereRaw("JSON_EXTRACT(config, '$.upgraded_to') IS NOT NULL")
250 ->whereRaw("JSON_EXTRACT(config, '$.upgraded_to') != 0");
251 } else if ($activeView === 'upgraded_from') {
252 return $query
253 ->whereRaw("JSON_EXTRACT(config, '$.upgraded_from') IS NOT NULL")
254 ->whereRaw("JSON_EXTRACT(config, '$.upgraded_from') != 0");
255 } else if ($activeView === 'b2b_purchase') {
256 return $query->whereHas('orderMeta', static::b2bMetaCondition());
257 } else if ($activeView === 'reverse_charge') {
258 return $query->whereHas('orderTaxRates', function ($q) {
259 $q->whereRaw("JSON_EXTRACT(meta, '$.reverse_charge_applied') = true");
260 });
261 } else {
262 return $query->where(
263 $tabsMap[$activeView],
264 $activeView
265 );
266 }
267
268
269 });
270 }
271
272 public static function getSearchableFields(): array
273 {
274 $fields = [
275 'id' => [
276 'column' => 'id',
277 'description' => __('Order Id', 'fluent-cart'),
278 'type' => 'numeric',
279 'examples' => [
280 'id = 1',
281 'id > 5',
282 'id :: 1-10'
283 ]
284 ],
285 'status' => [
286 'column' => 'status',
287 'description' => __('Search by order status e.g., completed, processing, on-hold, canceled, failed', 'fluent-cart'),
288 'type' => 'string',
289 'examples' => [
290 'status = completed',
291 ]
292 ],
293 'invoice' => [
294 'column' => 'status',
295 'description' => __('Invoice Number', 'fluent-cart'),
296 'type' => 'string'
297 ],
298
299 'payment' => [
300 'column' => 'payment_status',
301 'description' => __('Search by payment status e.g., paid, pending, partially_paid, refunded, partially_refunded', 'fluent-cart'),
302 'type' => 'string',
303 'examples' => [
304 'payment = paid',
305 'payment = partially_paid',
306 'payment = partially_refunded',
307 ]
308 ],
309 'payment_by' => [
310 'column' => 'payment_method',
311 'description' => __('Search by payment method e.g., stripe, PayPal, offline_payment', 'fluent-cart'),
312 'type' => 'string',
313 'examples' => [
314 'payment_by = stripe',
315 'payment_by = paypal',
316 ]
317 ],
318
319 'customer' => [
320 'description' => __('Search by customer name or email', 'fluent-cart'),
321 'note' => __("only supports '=' operator", 'fluent-cart'),
322 'type' => 'custom',
323 'callback' => function ($query, $search) {
324 $query->whereHas('customer', function ($query) use ($search) {
325 $query->whereRaw("CONCAT(first_name, ' ', last_name) LIKE ?", ["%{$search}%"])
326 ->orWhere('email', 'like', '%' . $search . '%');
327 });
328 },
329 'examples' => [
330 'customer = jhon',
331 ]
332 ],
333 ];
334
335
336 if (class_exists(License::class)) {
337 $fields['license'] = [
338 'description' => __('Search by license key', 'fluent-cart'),
339 'note' => __("only supports '=' operator", 'fluent-cart'),
340 'type' => 'custom',
341 'callback' => function ($query, $search) {
342 $query->whereHas('licenses', function ($query) use ($search) {
343 $query->where('license_key', 'like', '%' . $search . '%');
344 });
345 },
346 'examples' => [
347 'license = ff-78d47b3fed89bda25cdc5b60d0298d60',
348 ]
349 ];
350 }
351
352 return $fields;
353 }
354
355 public static function advanceFilterOptions(): array
356 {
357 $manager = GatewayManager::getInstance();
358 $payment_routes = $manager->getRoutes();
359 $availablePaymentMethods = [];
360
361 foreach ($payment_routes as $route) {
362 $availablePaymentMethods[$route['name']] = Arr::get($route, 'meta.title', $route['name']);
363 }
364
365
366 $filters = [
367 'order' => [
368 'label' => __('Order Property', 'fluent-cart'),
369 'value' => 'order',
370 'children' => [
371 [
372 'label' => __('By Order Items', 'fluent-cart'),
373 'value' => 'order_items',
374 'column' => 'object_id',
375 'filter_type' => 'relation',
376 'relation' => 'order_items',
377 'remote_data_key' => 'product_variations',
378 'type' => 'remote_tree_select',
379 'limit' => 10,
380 ],
381 [
382 'label' => __('Order Status', 'fluent-cart'),
383 'value' => 'status',
384 'type' => 'selections',
385 'options' => [
386 'completed' => __('Completed', 'fluent-cart'),
387 'processing' => __('Processing', 'fluent-cart'),
388 'on-hold' => __('On Hold', 'fluent-cart'),
389 'canceled' => __('Canceled', 'fluent-cart')
390 ],
391 'is_multiple' => true,
392 'is_only_in' => true
393 ],
394 [
395 'label' => __('Payment Status', 'fluent-cart'),
396 'value' => 'payment_status',
397 'type' => 'selections',
398 'options' => [
399 'paid' => __('Paid', 'fluent-cart'),
400 'pending' => __('Pending', 'fluent-cart'),
401 'partially_paid' => __('Partially Paid', 'fluent-cart'),
402 'refunded' => __('Refunded', 'fluent-cart'),
403 'partially_refunded' => __('Partially Refunded', 'fluent-cart'),
404 //'authorized' => __('Authorized', 'fluent-cart')
405 ],
406 'is_multiple' => true,
407 'is_only_in' => true
408 ],
409 // [
410 // 'label' => __('Shipping Status', 'fluent-cart'),
411 // 'value' => 'shipping_status',
412 // 'type' => 'selections',
413 // 'options' => [
414 // 'fulfilled' => __('Fulfilled', 'fluent-cart'),
415 // 'unfulfilled' => __('Unfulfilled', 'fluent-cart'),
416 // 'on_hold' => __('On Hold', 'fluent-cart')
417 // ],
418 // 'is_multiple' => true,
419 // 'is_only_in' => true
420 // ],
421 [
422 'label' => __('Order Type', 'fluent-cart'),
423 'value' => 'type',
424 'type' => 'selections',
425 'options' => [
426 'payment' => __('Single Payment', 'fluent-cart'),
427 'subscription' => __('Subscription', 'fluent-cart'),
428 'renewal' => __('Renewal', 'fluent-cart'),
429 ],
430 'is_multiple' => true,
431 'is_only_in' => true
432 ],
433 [
434 'label' => __('Payment Method', 'fluent-cart'),
435 'value' => 'payment_method',
436 'type' => 'selections',
437 'column' => 'payment_method',
438 'relation' => 'transactions',
439 'filter_type' => 'relation',
440 'options' => $availablePaymentMethods,
441 'is_multiple' => true,
442 'is_only_in' => true
443 ],
444 [
445 'label' => __('Order Amount', 'fluent-cart'),
446 'value' => 'total_amount',
447 'type' => 'numeric',
448 ],
449 [
450 'label' => __('Order Hash (UUID)', 'fluent-cart'),
451 'value' => 'uuid',
452 'type' => 'text',
453 'column' => 'uuid',
454 'operators' => [
455 'equals' => __('Equals', 'fluent-cart'),
456 'not_equals' => __('Not Equals', 'fluent-cart'),
457 ]
458 ],
459 [
460 'label' => __('Order Date', 'fluent-cart'),
461 'value' => 'created_at',
462 'type' => 'dates',
463 'filter_type' => 'date',
464 ],
465 ],
466 ],
467 'customer' => [
468 'label' => __('Customer Property', 'fluent-cart'),
469 'value' => 'customer',
470
471 'children' => [
472 [
473 'label' => __('Customer Name', 'fluent-cart'),
474 'value' => 'customer_full_name',
475 'type' => 'text',
476 'filter_type' => 'custom',
477 'operators' => [
478 'like_all' => __('Contains', 'fluent-cart'),
479 'starts_with' => __('Starts With', 'fluent-cart'),
480 'ends_with' => __('Ends With', 'fluent-cart'),
481 'not_like' => __('Not Contains', 'fluent-cart'),
482 ],
483 'callback' => function ($query, $data) {
484 $query->whereHas('customer', function ($query) use ($data) {
485 $query->searchByFullName($data);
486 });
487 }
488 ],
489
490 [
491 'label' => __('Customer Email', 'fluent-cart'),
492 'value' => 'customer_email',
493 'type' => 'text',
494 'filter_type' => 'relation',
495 'column' => 'email',
496 'relation' => 'customer',
497 ]
498 ],
499 ],
500 'transactions' => [
501 'label' => __('Transactions Property', 'fluent-cart'),
502 'value' => 'transactions',
503
504 'children' => [
505 [
506 'label' => __('Transaction Id', 'fluent-cart'),
507 'value' => 'transaction_id',
508 'type' => 'text',
509 'filter_type' => 'relation',
510 'column' => 'vendor_charge_id',
511 'relation' => 'transactions',
512 ],
513 [
514 'label' => __('Transaction Status', 'fluent-cart'),
515 'value' => 'transaction_status',
516 'type' => 'selections',
517 'filter_type' => 'relation',
518 'column' => 'status',
519 'relation' => 'transactions',
520 'options' => [
521 Status::TRANSACTION_SUCCEEDED => __('Succeeded', 'fluent-cart'),
522 Status::TRANSACTION_PENDING => __('Pending', 'fluent-cart'),
523 Status::TRANSACTION_REFUNDED => __('Refunded', 'fluent-cart'),
524 Status::TRANSACTION_FAILED => __('Failed', 'fluent-cart'),
525 ],
526 'is_multiple' => true,
527 'is_only_in' => true
528 ],
529 [
530 'label' => __('Transaction Type', 'fluent-cart'),
531 'value' => 'transaction_type',
532 'type' => 'selections',
533 'filter_type' => 'relation',
534 'column' => 'transaction_type',
535 'relation' => 'transactions',
536 'options' => [
537 Status::TRANSACTION_TYPE_CHARGE => __('Charge', 'fluent-cart'),
538 Status::TRANSACTION_TYPE_REFUND => __('Refunded', 'fluent-cart'),
539 Status::TRANSACTION_TYPE_DISPUTE => __('Dispute', 'fluent-cart'),
540 ],
541 'is_multiple' => true,
542 'is_only_in' => true
543 ],
544 [
545 'label' => __('Card last 4', 'fluent-cart'),
546 'value' => 'transaction_card_last',
547 'type' => 'text',
548 'filter_type' => 'relation',
549 'column' => 'card_last_4',
550 'relation' => 'transactions',
551 ],
552 [
553 'label' => __('Card Brand', 'fluent-cart'),
554 'value' => 'transaction_card_brand',
555 'type' => 'text',
556 'filter_type' => 'relation',
557 'column' => 'card_brand',
558 'relation' => 'transactions',
559 ],
560 [
561 'label' => __('Payer email', 'fluent-cart'),
562 'value' => 'payer_email',
563 'type' => 'text',
564 'filter_type' => 'custom',
565 'operators' => [
566 'equals' => __('Equals', 'fluent-cart'),
567 'contains' => __('Contains', 'fluent-cart'),
568 'starts_with' => __('Starts With', 'fluent-cart'),
569 'ends_with' => __('Ends With', 'fluent-cart'),
570 'not_like' => __('Not Contains', 'fluent-cart')
571 ],
572 'callback' => function ($query, $data) {
573 $query->whereHas('transactions', function ($query) use ($data) {
574 $query->searchByPayerEmail($data);
575 });
576 },
577 'examples' => [
578 'payer_email = [email protected]',
579 ]
580 ]
581 ]
582 ]
583 ];
584
585 if (ModuleSettings::isActive('license') && App::isProActive()) {
586 $filters['license'] = [
587 'label' => __('License Property', 'fluent-cart'),
588 'value' => 'license',
589 'children' => [
590 [
591 'label' => __('License key', 'fluent-cart'),
592 'value' => 'license_key',
593 'type' => 'text',
594 'filter_type' => 'relation',
595 'column' => 'license_key',
596 'relation' => 'licenses',
597 ],
598 [
599 'label' => __('License Status', 'fluent-cart'),
600 'value' => 'license_status',
601 'type' => 'selections',
602 'filter_type' => 'relation',
603 'column' => 'status',
604 'relation' => 'licenses',
605 'options' => [
606 Status::LICENSE_ACTIVE => __('Active', 'fluent-cart'),
607 Status::LICENSE_DISABLED => __('Disabled', 'fluent-cart'),
608 Status::LICENSE_EXPIRED => __('Expired', 'fluent-cart'),
609 ],
610 'is_multiple' => true,
611 'is_only_in' => true
612 ]
613 ],
614 ];
615 }
616
617 $utmFilters = [
618 'utm_campaign' => __('Utm Campaign', 'fluent-cart'),
619 'utm_term' => __('Utm Term', 'fluent-cart'),
620 'utm_source' => __('Utm Source', 'fluent-cart'),
621 'utm_medium' => __('Utm Medium', 'fluent-cart'),
622 'utm_content' => __('Utm Content', 'fluent-cart'),
623 'utm_id' => __('Utm Id', 'fluent-cart'),
624 'refer_url' => __('Refer Url', 'fluent-cart'),
625 ];
626
627 $utmChildren = [];
628 foreach ($utmFilters as $key => $label) {
629 $utmChildren[] = [
630 'label' => $label,
631 'value' => $key,
632 'type' => 'text',
633 'filter_type' => 'relation',
634 'column' => $key,
635 'relation' => 'orderOperation',
636 ];
637 }
638
639 $filters['utm'] = [
640 'label' => __('Utm Property', 'fluent-cart'),
641 'value' => 'utm',
642
643 'children' => $utmChildren
644 ];
645
646 $filters['tax'] = [
647 'label' => __('Tax Property', 'fluent-cart'),
648 'value' => 'tax',
649 'children' => [
650 [
651 'label' => __('B2B Purchase', 'fluent-cart'),
652 'value' => 'b2b_purchase',
653 'filter_type' => 'custom',
654 'type' => 'selections',
655 'options' => [
656 'yes' => __('Yes', 'fluent-cart'),
657 'no' => __('No', 'fluent-cart'),
658 ],
659 'is_multiple' => false,
660 'is_only_in' => true,
661 'callback' => static function ($query, $item) {
662 $value = Arr::get($item, 'value');
663 $selected = is_array($value) ? Arr::get($value, 0, '') : $value;
664 if ($selected === 'yes') {
665 $query->whereHas('orderMeta', static::b2bMetaCondition());
666 } else {
667 $query->whereDoesntHave('orderMeta', static::b2bMetaCondition());
668 }
669 },
670 ],
671 [
672 'label' => __('Reverse Charge', 'fluent-cart'),
673 'value' => 'reverse_charge',
674 'filter_type' => 'custom',
675 'type' => 'selections',
676 'options' => [
677 'yes' => __('Yes', 'fluent-cart'),
678 'no' => __('No', 'fluent-cart'),
679 ],
680 'is_multiple' => false,
681 'is_only_in' => true,
682 'callback' => static function ($query, $item) {
683 $value = Arr::get($item, 'value');
684 $selected = is_array($value) ? Arr::get($value, 0, '') : $value;
685 if ($selected === 'yes') {
686 $query->whereHas('orderTaxRates', function ($q) {
687 $q->whereRaw("JSON_EXTRACT(meta, '$.reverse_charge_applied') = true");
688 });
689 } else {
690 $query->whereDoesntHave('orderTaxRates', function ($q) {
691 $q->whereRaw("JSON_EXTRACT(meta, '$.reverse_charge_applied') = true");
692 });
693 }
694 },
695 ],
696 ],
697 ];
698
699 return LabelFilter::advanceFilterOptionsForOther($filters);
700 }
701
702
703 public function centColumns(): array
704 {
705 return ['subtotal', 'shipping_total', 'total_amount', 'total_paid', 'total_refund'];
706 }
707 }
708