Skip to content

Architecture

Class overview

AsyncBulkOrderCompletionLoader   (entry point, wires all modules via Module trait)
├── Backend\Application          (injects <order-completion-app> web component into wp-admin footer)
├── Backend\Orders               (enqueues JS, shows pending-completion admin notice on order edit screen)
├── Backend\OrderActions         (registers & handles "Change status to completed" bulk action)
├── Backend\Scheduler            (Action Scheduler listener; processes batches, auto-retries on failure)
├── RESTndpointsndpoints     (registers REST namespace + routes)
│   └── RESTndpoints\Jobs      (GET /wp-json/async-bulk-order-completion/v1/jobs)
└── Data\
    ├── Database                 (creates custom tables on activation)
    ├── Models\Job               (job entity: id, user_id, completed, created_at, completed_at)
    ├── Models\JobOrder          (order-in-job entity: id, job_id, order_id, completed_at)
    ├── Stores\JobStore          (CRUD for order_completion_jobs)
    └── Stores\JobOrderStore     (CRUD for order_completion_orders)

The front-end is a compiled Vue web component (src/assets/js/order-completion-app.min.js) that polls the REST API and displays job/order status in the WooCommerce admin footer.

Database schema

erDiagram
    WP_USERS {
        bigint ID PK
        string user_login
        string display_name
    }

    ORDER_COMPLETION_JOBS {
        int id PK
        bigint user_id FK
        tinyint completed
        datetime created_at
        datetime completed_at
    }

    ORDER_COMPLETION_ORDERS {
        int id PK
        int job_id FK
        int order_id
        datetime completed_at
    }

    WC_ORDERS_OR_POSTS {
        int ID PK
        string status
    }

    WP_USERS ||--o{ ORDER_COMPLETION_JOBS : "created_by"
    ORDER_COMPLETION_JOBS ||--o{ ORDER_COMPLETION_ORDERS : "contains"
    ORDER_COMPLETION_ORDERS }o--|| WC_ORDERS_OR_POSTS : "references"

Tables are prefixed with $wpdb->prefix. The foreign key from order_completion_orders.job_id to order_completion_jobs.id uses ON DELETE CASCADE.

Async completion flow

sequenceDiagram
    actor Admin
    participant WC_Admin as WooCommerce Admin
    participant OrderActions
    participant functions.php
    participant ActionScheduler as Action Scheduler
    participant Scheduler

    Admin->>WC_Admin: Select orders → bulk action "Change status to completed"
    WC_Admin->>OrderActions: handle_bulk_actions()
    OrderActions->>functions.php: create_async_order_completion_batch($order_ids)
    functions.php->>functions.php: INSERT job into order_completion_jobs
    functions.php->>functions.php: INSERT each order into order_completion_orders
    functions.php->>ActionScheduler: as_enqueue_async_action("batch-order-completion", {job_id})
    ActionScheduler-->>Admin: Redirect back (immediate)

    Note over ActionScheduler,Scheduler: Background processing
    ActionScheduler->>Scheduler: on_batch_order_completion($job_id)
    loop For each pending JobOrder
        Scheduler->>Scheduler: Fetch WC order
        Scheduler->>Scheduler: update_status("completed")
        Scheduler->>Scheduler: Mark JobOrder completed_at
    end
    Scheduler->>Scheduler: Mark Job completed

Failure recovery

If Action Scheduler marks the batch-order-completion action as failed, Scheduler::maybe_restart_batch_job() listens on action_scheduler_failed_action and resets the action status back to pending, causing it to be retried automatically.