PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 trunk 1.2.0 All 47 releases
fluent-cart / app / Services / Filter / OrderFilter.php

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

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