__( 'Reasons', 'patchwork-order-split' ), 'orders' => __( 'Orders, shipping and stock', 'patchwork-order-split' ), 'backorders' => __( 'Backorders', 'patchwork-order-split' ), 'emails' => __( 'Customer emails', 'patchwork-order-split' ), 'access' => __( 'Access', 'patchwork-order-split' ), ] ); } public function pro_features(): array { return [ __( 'Choose the status of each new split order — for example On hold until stock arrives.', 'patchwork-order-split' ), __( 'Your own order statuses, such as "Awaiting stock", with bulk actions on the orders list.', 'patchwork-order-split' ), __( 'Merge open orders from the same customer into one, so they ship together — with payments and stock kept straight.', 'patchwork-order-split' ), __( 'Split backordered items into their own order automatically when an order is paid, plus a bulk action for existing orders.', 'patchwork-order-split' ), ]; } public function settings_fields(): array { return (array) apply_filters( 'patchwork_split_settings_fields', [ [ 'id' => 'patchwork_split_split_reasons', 'section' => 'reasons', 'type' => 'lines', 'label' => __( 'Split reasons', 'patchwork-order-split' ), 'description' => __( 'One per line. Shown when an item is marked Split.', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_unable_reasons', 'section' => 'reasons', 'type' => 'lines', 'label' => __( 'Unable to fulfil reasons', 'patchwork-order-split' ), 'description' => __( 'One per line. Shown when an item is marked Unable.', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_shipping_mode', 'section' => 'orders', 'type' => 'select', 'default' => 'keep', 'label' => __( 'Shipping on split orders', 'patchwork-order-split' ), 'options' => [ 'keep' => __( 'Keep shipping on the original order', 'patchwork-order-split' ), 'zero' => __( 'Add the same method to the new order, free of charge', 'patchwork-order-split' ), 'move' => __( 'Move shipping to the new order', 'patchwork-order-split' ), ], 'description' => __( 'The default for each split; it can be changed in the manager. When every item leaves the original order, shipping always goes with it.', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_parent_when_empty', 'section' => 'orders', 'type' => 'select', 'default' => 'cancel', 'label' => __( 'When every item has left the original order', 'patchwork-order-split' ), 'options' => [ 'cancel' => __( 'Cancel it', 'patchwork-order-split' ), 'on-hold' => __( 'Set it to On hold', 'patchwork-order-split' ), 'leave' => __( 'Leave its status as is', 'patchwork-order-split' ), ], 'description' => __( 'Payments that are authorised but not yet captured are never cancelled (that would void them) — the order goes On hold instead.', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_restock_unable', 'section' => 'orders', 'type' => 'checkbox', 'default' => '1', 'label' => __( 'Stock', 'patchwork-order-split' ), 'checkbox_label' => __( 'Put items marked Unable back into stock', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_email_on_split', 'section' => 'emails', 'type' => 'checkbox', 'default' => '0', 'label' => __( 'Split', 'patchwork-order-split' ), 'checkbox_label' => __( 'Tell the customer when items are split into a new order', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_email_on_unable', 'section' => 'emails', 'type' => 'checkbox', 'default' => '1', 'label' => __( 'Unable', 'patchwork-order-split' ), 'checkbox_label' => __( 'Tell the customer when items are marked Unable', 'patchwork-order-split' ), 'description' => __( 'Sent as a WooCommerce customer note on the original order. WooCommerce\'s own order emails are not sent for split orders.', 'patchwork-order-split' ), ], [ 'id' => Backorders::OPT_MODE, 'section' => 'backorders', 'type' => 'select', 'default' => 'suggest', 'label' => __( 'When an order has items on backorder', 'patchwork-order-split' ), 'options' => Backorders::modes(), 'description' => __( 'Suggest: the order gets a "⚠ on backorder" badge and a notice with a one-click split, and you decide — including what to do with the postage.', 'patchwork-order-split' ), ], [ 'id' => 'patchwork_split_roles', 'section' => 'access', 'type' => 'custom', 'label' => __( 'Who can split orders', 'patchwork-order-split' ), 'render' => [ $this, 'render_roles_field' ], 'save' => [ $this, 'save_roles_field' ], ], ] ); } public function render_roles_field(): void { echo '
' . esc_html__( 'Users also need permission to edit orders. Administrators always have access.', 'patchwork-order-split' ) . '
'; } public function save_roles_field(): void { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified by Patchwork Core before saving $chosen = isset( $_POST['patchwork_split_roles'] ) && is_array( $_POST['patchwork_split_roles'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['patchwork_split_roles'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing : []; foreach ( wp_roles()->role_objects as $role_key => $role ) { if ( $role_key === 'administrator' || in_array( $role_key, $chosen, true ) ) { $role->add_cap( Activator::CAP ); } else { $role->remove_cap( Activator::CAP ); } } } // ─── Dashboard ──────────────────────────────────────────────────────────── public function dashboard_card(): array { $since = time() - 30 * DAY_IN_SECONDS; $split_orders = wc_get_orders( [ 'created_via' => 'patchwork-split', 'date_created' => '>' . $since, 'meta_query' => [ [ 'key' => '_pwsplit_type', 'value' => 'split' ] ], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'limit' => 1, 'paginate' => true, 'return' => 'ids', ] ); $batches = \Patchwork\Core\Activity::count_since( $this->id(), gmdate( 'Y-m-d H:i:s', $since ) ); return [ 'stats' => [ [ 'label' => __( 'Orders changed, 30 days', 'patchwork-order-split' ), 'value' => $batches ], [ 'label' => __( 'New split orders, 30 days', 'patchwork-order-split' ), 'value' => (int) $split_orders->total ], ], 'notice' => Settings::current_user_can_split() ? '' : __( 'Your role can\'t split orders. Change this under Access in the settings.', 'patchwork-order-split' ), 'tone' => 'warning', ]; } // ─── Orders column ──────────────────────────────────────────────────────── public function order_badges( \WC_Order $order ): array { $badges = []; $parent_id = (int) $order->get_meta( '_pwsplit_parent_id' ); if ( $parent_id ) { $unable = $order->get_meta( '_pwsplit_type' ) === 'unable'; $parent = wc_get_order( $parent_id ); $number = $parent ? $parent->get_order_number() : $parent_id; $badges[] = [ /* translators: %s: original order number */ 'label' => sprintf( $unable ? __( '✕ Unable from #%s', 'patchwork-order-split' ) : __( '↳ From #%s', 'patchwork-order-split' ), $number ), 'url' => $parent ? $parent->get_edit_order_url() : '', 'title' => $unable ? __( 'Items that could not be fulfilled from this order', 'patchwork-order-split' ) : __( 'Split from this order', 'patchwork-order-split' ), 'tone' => $unable ? 'danger' : 'info', ]; } $children = array_filter( array_map( 'absint', Splitter::meta_list( $order, '_pwsplit_children' ) ) ); if ( $children ) { $badges[] = [ /* translators: %d: number of split orders */ 'label' => sprintf( _n( '⇂ %d split', '⇂ %d splits', count( $children ), 'patchwork-order-split' ), count( $children ) ), 'title' => implode( ', ', array_map( function( $id ) { return '#' . $id; }, $children ) ), 'tone' => 'success', ]; } return (array) apply_filters( 'patchwork_split_order_badges', $badges, $order ); } // ─── Order box section ──────────────────────────────────────────────────── public function order_panel( \WC_Order $order ): string { $out = ''; $parent_id = (int) $order->get_meta( '_pwsplit_parent_id' ); if ( $parent_id ) { $parent = wc_get_order( $parent_id ); $link = $parent ? sprintf( '#%s', esc_url( $parent->get_edit_order_url() ), esc_html( $parent->get_order_number() ) ) : '#' . $parent_id; $out .= '' . ( $order->get_meta( '_pwsplit_type' ) === 'unable'
/* translators: %s: link to the original order */
? sprintf( esc_html__( 'Unfulfilled items from %s', 'patchwork-order-split' ), $link )
/* translators: %s: link to the original order */
: sprintf( esc_html__( 'Split from %s', 'patchwork-order-split' ), $link ) );
$reason = (string) $order->get_meta( '_pwsplit_reason' );
if ( $reason !== '' ) $out .= '
';
$out .= '