From 7f78669c5b59fdb00ac929736860daee042f383d Mon Sep 17 00:00:00 2001 From: colorlink Date: Fri, 16 Oct 2026 02:16:53 +0000 Subject: [PATCH] =?UTF-8?q?feat(batch20):=20WooCommerce=20deep=20=E2=80=94?= =?UTF-8?q?=20categories,=20gateways,=20shipping,=20tax,=20settings,=20ord?= =?UTF-8?q?er=20notes,=20refunds,=20sales=20report,=20reviews=20(guarded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q8t9GEyyLZbUQu26na1JQg --- includes/Extensions/Batch20.php | 190 ++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 includes/Extensions/Batch20.php diff --git a/includes/Extensions/Batch20.php b/includes/Extensions/Batch20.php new file mode 100644 index 0000000..67c4c74 --- /dev/null +++ b/includes/Extensions/Batch20.php @@ -0,0 +1,190 @@ + 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch20::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('wc_list_product_categories', 'List WooCommerce product categories (id, name, slug, parent, count).', + ['parent' => ['type' => 'integer'], 'search' => ['type' => 'string'], 'per_page' => ['type' => 'integer', 'default' => 100]], [], 'read', 'listCategories')); + + $registry->register($def('wc_create_product_category', 'Create a WooCommerce product category.', + ['name' => ['type' => 'string'], 'slug' => ['type' => 'string'], 'parent' => ['type' => 'integer'], 'description' => ['type' => 'string']], ['name'], 'write', 'createCategory')); + + $registry->register($def('wc_list_payment_gateways', 'List WooCommerce payment gateways and whether each is enabled.', + [], [], 'read', 'paymentGateways')); + + $registry->register($def('wc_list_shipping_zones', 'List WooCommerce shipping zones with their regions and methods.', + [], [], 'read', 'shippingZones')); + + $registry->register($def('wc_list_tax_rates', 'List WooCommerce tax rates (country, state, rate, name, class).', + ['class' => ['type' => 'string'], 'limit' => ['type' => 'integer', 'default' => 100]], [], 'read', 'taxRates')); + + $registry->register($def('wc_get_settings', 'Read a group of WooCommerce settings. group = general | products | tax | account | email. Returns the relevant woocommerce_* options.', + ['group' => ['type' => 'string', 'enum' => ['general', 'products', 'tax', 'account', 'email']]], ['group'], 'read', 'getSettings')); + + $registry->register($def('wc_add_order_note', 'Add a note to a WooCommerce order. customer_note=true emails it to the customer.', + ['order_id' => ['type' => 'integer'], 'note' => ['type' => 'string'], 'customer_note' => ['type' => 'boolean']], ['order_id', 'note'], 'write', 'addOrderNote')); + + $registry->register($def('wc_create_refund', 'Create a refund against a WooCommerce order. amount optional (full refund if omitted). reason optional.', + ['order_id' => ['type' => 'integer'], 'amount' => ['type' => 'number'], 'reason' => ['type' => 'string'], 'restock' => ['type' => 'boolean']], ['order_id'], 'write', 'createRefund')); + + $registry->register($def('wc_sales_report', 'Sales report over a date range: order count, gross total, and item count. Dates are YYYY-MM-DD (default last 30 days).', + ['date_from' => ['type' => 'string'], 'date_to' => ['type' => 'string'], 'statuses' => ['type' => 'array']], [], 'read', 'salesReport')); + + $registry->register($def('wc_list_product_reviews', 'List WooCommerce product reviews (rating, author, excerpt, product). Optionally filter by product_id.', + ['product_id' => ['type' => 'integer'], 'per_page' => ['type' => 'integer', 'default' => 50]], [], 'read', 'listReviews')); + } + + public static function listCategories(array $args): array { + $terms = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false, + 'parent' => isset($args['parent']) ? (int) $args['parent'] : '', + 'search' => $args['search'] ?? '', 'number' => (int) ($args['per_page'] ?? 100)]); + $out = []; + foreach ((array) $terms as $t) { + if (!is_object($t)) continue; + $out[] = ['id' => $t->term_id, 'name' => $t->name, 'slug' => $t->slug, 'parent' => $t->parent, 'count' => $t->count]; + } + return ['count' => count($out), 'categories' => $out]; + } + + public static function createCategory(array $args): array { + $name = trim((string) ($args['name'] ?? '')); + if ($name === '') throw new \RuntimeException('name required.'); + $res = wp_insert_term($name, 'product_cat', [ + 'slug' => $args['slug'] ?? '', 'parent' => (int) ($args['parent'] ?? 0), 'description' => $args['description'] ?? '', + ]); + if (is_wp_error($res)) throw new \RuntimeException('Create failed: ' . $res->get_error_message()); + return ['id' => $res['term_id'], 'name' => $name]; + } + + public static function paymentGateways(array $args): array { + $gw = WC()->payment_gateways() ? WC()->payment_gateways()->payment_gateways() : []; + $out = []; + foreach ((array) $gw as $id => $g) { + $out[] = ['id' => $id, 'title' => $g->get_title(), 'enabled' => ($g->enabled === 'yes'), 'method_title' => $g->get_method_title()]; + } + return ['count' => count($out), 'gateways' => $out]; + } + + public static function shippingZones(array $args): array { + if (!class_exists('WC_Shipping_Zones')) throw new \RuntimeException('Shipping zones API unavailable.'); + $zones = \WC_Shipping_Zones::get_zones(); + $out = []; + foreach ((array) $zones as $z) { + $methods = []; + foreach ((array) ($z['shipping_methods'] ?? []) as $m) { + $methods[] = ['id' => $m->id, 'title' => $m->get_title(), 'enabled' => ($m->is_enabled())]; + } + $out[] = ['id' => $z['id'] ?? $z['zone_id'] ?? null, 'name' => $z['zone_name'] ?? '', 'regions' => $z['formatted_zone_location'] ?? '', 'methods' => $methods]; + } + return ['count' => count($out), 'zones' => $out]; + } + + public static function taxRates(array $args): array { + global $wpdb; + $t = $wpdb->prefix . 'woocommerce_tax_rates'; + $limit = max(1, (int) ($args['limit'] ?? 100)); + $where = ''; + if (!empty($args['class'])) $where = $wpdb->prepare('WHERE tax_rate_class = %s', (string) $args['class']); + $rows = $wpdb->get_results("SELECT tax_rate_country, tax_rate_state, tax_rate, tax_rate_name, tax_rate_class FROM {$t} {$where} ORDER BY tax_rate_order LIMIT {$limit}", ARRAY_A); + return ['count' => count((array) $rows), 'rates' => array_map(fn($r) => [ + 'country' => $r['tax_rate_country'], 'state' => $r['tax_rate_state'], 'rate' => $r['tax_rate'], + 'name' => $r['tax_rate_name'], 'class' => $r['tax_rate_class'] ?: 'standard', + ], (array) $rows)]; + } + + public static function getSettings(array $args): array { + $group = (string) ($args['group'] ?? 'general'); + $keys = [ + 'general' => ['woocommerce_store_address', 'woocommerce_store_city', 'woocommerce_default_country', 'woocommerce_currency', 'woocommerce_currency_pos', 'woocommerce_price_decimal_sep', 'woocommerce_price_num_decimals'], + 'products' => ['woocommerce_weight_unit', 'woocommerce_dimension_unit', 'woocommerce_manage_stock', 'woocommerce_notify_low_stock_amount', 'woocommerce_hide_out_of_stock_items', 'woocommerce_cart_redirect_after_add'], + 'tax' => ['woocommerce_calc_taxes', 'woocommerce_prices_include_tax', 'woocommerce_tax_based_on', 'woocommerce_tax_display_shop', 'woocommerce_tax_display_cart'], + 'account' => ['woocommerce_enable_guest_checkout', 'woocommerce_enable_checkout_login_reminder', 'woocommerce_enable_myaccount_registration'], + 'email' => ['woocommerce_email_from_name', 'woocommerce_email_from_address', 'woocommerce_email_header_image'], + ]; + if (!isset($keys[$group])) throw new \RuntimeException('Unknown group.'); + $out = []; + foreach ($keys[$group] as $k) $out[$k] = get_option($k); + return ['group' => $group, 'settings' => $out]; + } + + public static function addOrderNote(array $args): array { + $id = (int) ($args['order_id'] ?? 0); + $order = wc_get_order($id); + if (!$order) throw new \RuntimeException('Order not found: ' . $id); + $note = (string) ($args['note'] ?? ''); + if ($note === '') throw new \RuntimeException('note required.'); + $noteId = $order->add_order_note($note, !empty($args['customer_note']) ? 1 : 0, true); + return ['order_id' => $id, 'note_id' => $noteId, 'customer_note' => !empty($args['customer_note'])]; + } + + public static function createRefund(array $args): array { + $id = (int) ($args['order_id'] ?? 0); + $order = wc_get_order($id); + if (!$order) throw new \RuntimeException('Order not found: ' . $id); + $amount = isset($args['amount']) ? (float) $args['amount'] : (float) $order->get_total(); + $refund = wc_create_refund([ + 'order_id' => $id, + 'amount' => $amount, + 'reason' => (string) ($args['reason'] ?? ''), + 'restock_items' => !empty($args['restock']), + ]); + if (is_wp_error($refund)) throw new \RuntimeException('Refund failed: ' . $refund->get_error_message()); + return ['order_id' => $id, 'refund_id' => $refund->get_id(), 'amount' => $amount]; + } + + public static function salesReport(array $args): array { + $to = !empty($args['date_to']) ? (string) $args['date_to'] : date('Y-m-d'); + $from = !empty($args['date_from']) ? (string) $args['date_from'] : date('Y-m-d', strtotime('-30 days')); + $statuses = !empty($args['statuses']) && is_array($args['statuses']) ? $args['statuses'] : ['wc-completed', 'wc-processing']; + $statuses = array_map(fn($s) => strpos($s, 'wc-') === 0 ? $s : 'wc-' . $s, $statuses); + $orders = wc_get_orders([ + 'limit' => -1, 'status' => $statuses, + 'date_created' => $from . '...' . $to, 'return' => 'objects', + ]); + $gross = 0.0; $items = 0; $n = 0; + foreach ((array) $orders as $o) { $gross += (float) $o->get_total(); $items += (int) $o->get_item_count(); $n++; } + return ['date_from' => $from, 'date_to' => $to, 'statuses' => $statuses, + 'orders' => $n, 'gross_total' => round($gross, 2), 'currency' => get_woocommerce_currency(), 'items_sold' => $items]; + } + + public static function listReviews(array $args): array { + $cargs = ['post_type' => 'product', 'status' => 'approve', 'number' => (int) ($args['per_page'] ?? 50)]; + if (!empty($args['product_id'])) $cargs['post_id'] = (int) $args['product_id']; + $comments = get_comments($cargs); + $out = []; + foreach ((array) $comments as $c) { + $out[] = [ + 'id' => (int) $c->comment_ID, + 'product_id' => (int) $c->comment_post_ID, + 'product' => get_the_title((int) $c->comment_post_ID), + 'author' => $c->comment_author, + 'rating' => (int) get_comment_meta($c->comment_ID, 'rating', true), + 'excerpt' => mb_substr(wp_strip_all_tags($c->comment_content), 0, 200), + 'date' => $c->comment_date, + ]; + } + return ['count' => count($out), 'reviews' => $out]; + } +}