PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
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
← All changes | app/Modules/MCP/Tools/OrderTools.php +70 -14 1.5.5 → 1.6.5 View file →
@@ -35,18 +35,19 @@
35 35 class OrderTools
36 36 {
37 37 public static function definitions()
38 38 {
39 - $orderStatuses = ContextTools::ENUMS['order_statuses'];
40 - $paymentStatuses = ContextTools::ENUMS['payment_statuses'];
41 - $shippingStatuses = ContextTools::ENUMS['shipping_statuses'];
39 + $enums = ContextTools::enums();
40 + $orderStatuses = $enums['order_statuses'];
41 + $paymentStatuses = $enums['payment_statuses'];
42 + $shippingStatuses = $enums['shipping_statuses'];
42 43 // change-order-status cannot set an order back to "no shipping required".
43 44 $shippingWritable = array_values(array_diff($shippingStatuses, ['none']));
44 - // Only the statuses core actually accepts for a manual change — a subset
45 - // of the full order_statuses enum used for filtering (no draft/pending/
46 - // refunded/partial-refund: those are reached via payment/refund flows).
45 + // Only the statuses core accepts for a manual change — a subset of the
46 + // order_statuses enum used for filtering ('failed' is reached through a
47 + // payment flow, not a manual set).
47 48 $orderWritable = array_keys(Status::getEditableOrderStatuses());
48 - $orderTypes = ContextTools::ENUMS['order_types'];
49 + $orderTypes = $enums['order_types'];
49 50
50 51 return [
51 52 'fluent-cart/list-orders' => [
52 53 'label' => __('List Orders', 'fluent-cart'),
@@ -53,10 +54,10 @@
53 54 'description' => __('Find and filter orders. Returns compact rows (id, number, customer, total, statuses, date, plus an items list: each line item\'s product, title and quantity) — call get-order for the full money/refund breakdown. All filters optional; combine freely. For one customer\'s orders, pass customer_email or customer_id here. Money filters are in store currency (e.g. 49.99), not cents. For conditions these flat filters cannot express (OR groups, relative dates, transaction/UTM/label/license properties) pass advanced_filters — call get-search-schema entity=orders first (Pro).', 'fluent-cart'),
54 55 'input_schema' => [
55 56 'type' => 'object',
56 57 'properties' => [
57 - 'status' => ['type' => 'string', 'enum' => $orderStatuses, 'description' => 'Order fulfillment/lifecycle status.'],
58 - 'payment_status' => ['type' => 'string', 'enum' => $paymentStatuses],
58 + 'status' => ['type' => 'string', 'enum' => $orderStatuses, 'description' => 'Order fulfillment/lifecycle status. To find refunded orders use payment_status (refunded / partially_refunded), NOT this field: refunds are recorded as payment state, and status=refunded only ever appears on stores migrated from WooCommerce. pending here means an unpaid store-managed renewal invoice, which is not the same as payment_status=pending; a COD order sits at status=on-hold with payment_status=pending.'],
59 + 'payment_status' => ['type' => 'string', 'enum' => $paymentStatuses, 'description' => 'Money state of the order — this is where refunded, partially_refunded, authorized and payment_scheduled live.'],
59 60 'shipping_status' => ['type' => 'string', 'enum' => $shippingStatuses],
60 61 'type' => ['type' => 'string', 'enum' => $orderTypes, 'description' => 'payment = first purchase, renewal = subscription renewal.'],
61 62 'customer_id' => ['type' => 'integer'],
62 63 'customer_email' => ['type' => 'string', 'description' => 'Exact email — the most reliable customer filter.'],
@@ -86,9 +87,13 @@
86 87 ],
87 88
88 89 'fluent-cart/get-order' => [
89 90 'label' => __('Get Order', 'fluent-cart'),
90 - 'description' => __('Full detail for one order: money breakdown, line items, and customer by default. Add include[] for transactions, refunds, addresses, coupons, subscriptions. Identify the order by order_id (numeric, from list-orders) OR uuid OR invoice_no.', 'fluent-cart'),
91 + 'description' => sprintf(
92 + /* translators: %1$s: comma-separated include[] section names */
93 + __('Full detail for one order: money breakdown, line items, and customer by default. Add include[] for any of: %1$s. Identify the order by order_id (numeric, from list-orders) OR uuid OR invoice_no.', 'fluent-cart'),
94 + implode(', ', self::includeSections())
95 + ),
91 96 'input_schema' => [
92 97 'type' => 'object',
93 98 'properties' => [
94 99 'order_id' => ['type' => 'integer', 'description' => 'Numeric order id as returned by list-orders.'],
@@ -96,9 +101,9 @@
96 101 'invoice_no' => ['type' => 'string'],
97 102 'include' => [
98 103 'type' => 'array',
99 104 'description' => 'Optional heavier sections. items + customer are always included.',
100 - 'items' => ['type' => 'string', 'enum' => ['transactions', 'refunds', 'addresses', 'coupons', 'subscriptions']],
105 + 'items' => ['type' => 'string', 'enum' => self::includeSections()],
101 106 ],
102 107 'fields' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Optional: return only these top-level keys to shrink the payload (order_id is always kept). e.g. status, payment_status, totals, items, customer. Applies after include[]. Omit for the full record.'],
103 108 ],
104 109 ],
@@ -396,9 +401,14 @@
396 401 $customer = ($order->relationLoaded('customer') && $order->customer) ? $order->customer : null;
397 402
398 403 return [
399 404 'order_id' => (int) $order->id,
400 - 'number' => $order->invoice_no ? $order->invoice_no : (string) $order->id,
405 + // null, not the raw id: an invoice number is only assigned once the
406 + // order is paid, and echoing the id here made unpaid orders look like
407 + // they had a number in a different format from every other row (and
408 + // disagreed with get-order, which already returns null). order_id is
409 + // right above it for referencing the record.
410 + 'number' => $order->invoice_no ? $order->invoice_no : null,
401 411 'label' => self::label($order, $customer),
402 412 'status' => $order->status,
403 413 'payment_status' => $order->payment_status,
404 414 'shipping_status' => self::shippingStatusOut($order),
@@ -473,10 +483,13 @@
473 483
474 484 $data = [
475 485 'order_id' => (int) $order->id,
476 486 'uuid' => $order->uuid,
477 - 'number' => $order->invoice_no,
478 - 'receipt_number' => $order->receipt_number,
487 + // Normalized to null when unassigned — the column stores '' for an
488 + // order that has not been invoiced yet, and an empty string reads as
489 + // "the number is blank" rather than "there is no number".
490 + 'number' => $order->invoice_no ? $order->invoice_no : null,
491 + 'receipt_number' => $order->receipt_number ? $order->receipt_number : null,
479 492 'status' => $order->status,
480 493 'payment_status' => $order->payment_status,
481 494 'shipping_status' => self::shippingStatusOut($order),
482 495 'type' => $order->type,
@@ -505,13 +518,56 @@
505 518 if (in_array('subscriptions', $include, true)) {
506 519 $data['subscriptions'] = self::subscriptionsBlock($order);
507 520 }
508 521
522 + /**
523 + * The assembled get-order payload, for add-on sections registered through
524 + * fluent_cart/mcp_order_include_sections. Listeners should add their key
525 + * only when it is present in $context['include'].
526 + *
527 + * @since 1.0.0
528 + *
529 + * @param array $data the order payload
530 + * @param array $context { order: Order, include: string[] }
531 + */
532 + $data = apply_filters('fluent_cart/mcp_order_data', $data, [
533 + 'order' => $order,
534 + 'include' => $include,
535 + ]);
536 +
509 537 // fields projection runs last, so it can trim both the base record and any
510 538 // include[] sections; order_id is always kept.
511 539 $fields = isset($params['fields']) ? $params['fields'] : null;
512 540
513 541 return MCPHelper::envelope(self::label($order, $order->customer), MCPHelper::pickFields($data, $fields, ['order_id']));
542 + }
543 +
544 + /**
545 + * The sections get-order's include[] accepts. Filterable so an integration
546 + * that owns order-adjacent context (the CRM contact behind the buyer, for
547 + * one) can offer it as an include rather than leaving the agent to guess
548 + * which other tool holds it.
549 + *
550 + * A section added here MUST be populated by a listener on
551 + * fluent_cart/mcp_order_data — an include the schema advertises but nothing
552 + * fills is worse than no include at all.
553 + *
554 + * @return array
555 + */
556 + private static function includeSections()
557 + {
558 + $sections = ['transactions', 'refunds', 'addresses', 'coupons', 'subscriptions'];
559 +
560 + /**
561 + * Extra include[] section names for get-order.
562 + *
563 + * @since 1.0.0
564 + *
565 + * @param array $sections section names offered in the include[] enum
566 + */
567 + $sections = apply_filters('fluent_cart/mcp_order_include_sections', $sections);
568 +
569 + return array_values(array_unique(array_map('strval', (array) $sections)));
514 570 }
515 571
516 572 private static function resolveOrder($params)
517 573 {