Skip to content

Conversation

@MukundC25
Copy link

@MukundC25 MukundC25 commented Dec 19, 2025

Adds a staff-only API endpoint that allows creating ticket orders directly from the backend for on-site / box-office use cases.

Changes:

  • Added POST /api/v1/organizers/{organizer}/events/{event}/orders/create_onsite/ endpoint
  • Endpoint is staff-only, requires can_change_orders permission
  • Orders are created as paid using the manual payment provider, assuming payment is completed at the counter
  • Matches existing order import and box-office workflows
  • Payment (cash/card) is assumed to be completed at the counter
  • Default sales channel is box_office
  • Email sending disabled by default (can be overridden)
  • Reuses existing OrderCreateSerializer without modifying core order logic

Fixes #1222

Summary by Sourcery

Add a staff-only API endpoint for creating paid on-site ticket orders via the backend.

New Features:

  • Introduce POST /api/v1/organizers/{organizer}/events/{event}/orders/create_onsite/ endpoint for box-office order creation.

Enhancements:

  • Ensure on-site API orders are recorded as paid using the manual payment provider with box_office sales channel and optional email sending.
  • Emit standard order lifecycle signals and logging for on-site API orders to align with existing workflows.

Tests:

  • Add API tests verifying successful on-site order creation behavior and permission enforcement for unauthorized clients.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 19, 2025

Reviewer's Guide

Implements a new staff-only on-site order creation API endpoint that reuses the existing order creation serializer, forces manual paid payments, sets box_office as the default sales channel, suppresses emails by default, and wires it into existing order and payment workflows with accompanying tests.

Sequence diagram for staff on-site order creation API

sequenceDiagram
    actor StaffUser
    participant Client as APIClient
    participant View as OrderViewSet
    participant SerCreate as OrderCreateSerializer
    participant DB as Database
    participant SerOut as OrderSerializer
    participant SigPlaced as order_placed_signal
    participant SigPaid as order_paid_signal

    StaffUser->>Client: Submit on-site order data
    Client->>View: POST /api/v1/.../orders/create_onsite/
    View->>View: Enforce staff and can_change_orders
    View->>View: Copy request.data into data
    View->>View: Set payment_provider manual
    View->>View: Set status p (paid)
    View->>View: Set sales_channel box_office (default)
    View->>View: Set send_email False (default)
    View->>SerCreate: Instantiate with data and context
    SerCreate->>SerCreate: Validate data
    SerCreate-->>View: Validated data
    View->>DB: transaction.atomic begin
    View->>View: perform_create(serializer)
    View->>DB: Insert Order row
    DB-->>View: Persisted Order
    View->>View: Ensure order.pk exists
    View->>View: order.log_action eventyay.event.order.placed
    View->>SerOut: Instantiate with order and context
    SerOut-->>View: Serialized order data
    View->>SigPlaced: order_placed.send(event, order)
    alt Order status is paid
        View->>SigPaid: order_paid.send(event, order)
    end
    View-->>Client: 201 Created with order details
    Client-->>StaffUser: Show created paid on-site order
Loading

Class diagram for updated OrderViewSet on-site creation flow

classDiagram
    class OrderViewSet {
        +create_onsite(request, **kwargs)
        +perform_create(serializer)
        +get_serializer_context()
    }

    class OrderCreateSerializer {
        +is_valid(raise_exception)
        +save()
        +instance
    }

    class OrderSerializer {
        +OrderSerializer(order, context)
        +data
    }

    class Order {
        +pk
        +code
        +status
        +total
        +locale
        +STATUS_PAID
        +log_action(action, user, auth, data)
    }

    class TaxRule {
        +SaleNotAllowed
    }

    class Signals {
        +order_placed
        +order_paid
    }

    class Transaction {
        +atomic()
    }

    class LanguageContext {
        +language(locale, region)
    }

    OrderViewSet --> OrderCreateSerializer : uses for input validation
    OrderViewSet --> OrderSerializer : uses for response data
    OrderViewSet --> Order : creates and logs actions
    OrderViewSet --> TaxRule : handles SaleNotAllowed
    OrderViewSet --> Signals : sends order_placed and order_paid
    OrderViewSet --> Transaction : wraps create_onsite in atomic
    OrderViewSet --> LanguageContext : wraps signal sending in language
Loading

File-Level Changes

Change Details Files
Add staff-only on-site order creation API endpoint that creates paid orders using the manual payment provider and box_office sales channel while reusing existing order creation logic.
  • Introduce create_onsite POST action on the order viewset that sets payment_provider to manual, marks the order as paid, defaults sales_channel to box_office, and disables email sending unless explicitly overridden.
  • Use OrderCreateSerializer for validation and creation, wrapping the operation in a database transaction and handling TaxRule.SaleNotAllowed with a translated ValidationError.
  • After creation, log an order placed action tagged as coming from the onsite_api source, emit order_placed and order_paid signals respecting the order locale and event region, and return a compact 201 response payload with key order fields and nested order data.
app/eventyay/api/views/order.py
Add automated tests for the on-site order creation endpoint, covering success path and permission enforcement.
  • Add test that posts to the new create_onsite endpoint via an authenticated token client, asserts 201 response and core response fields, then verifies in the database that the order is paid, uses the manual payment provider with a confirmed payment, has correct totals, and is assigned the box_office sales channel.
  • Add test that posts to the same endpoint using an unauthenticated or unauthorized client and asserts a 401 response to ensure only staff with appropriate permissions can access the endpoint.
src/tests/api/test_orders.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1222 Introduce a backend API endpoint that allows event staff to create new ticket orders for on-site attendees via the check-in application.
#1222 Ensure the on-site order-creation API supports last-minute registrations with on-site/manual payment handling (i.e., orders created as already paid).

Possibly linked issues

  • #: The PR implements the requested on-site order creation API, including staff restrictions and box-office payment assumptions.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@MukundC25 MukundC25 marked this pull request as ready for review December 19, 2025 14:03
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The create_onsite action currently relies on whatever permission/authorization is configured at the viewset level, but the description promises a staff-only can_change_orders restriction; consider enforcing this explicitly (e.g., with a decorator or permission check) so the behavior is guaranteed regardless of viewset reuse.
  • In create_onsite, you trust client-supplied sales_channel and send_email values after seeding defaults; if this endpoint is meant to consistently behave like box-office/import flows, you may want to either force sales_channel='box_office' and/or constrain overrides, and coerce send_email to a strict boolean to avoid unexpected truthy values from user input.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `create_onsite` action currently relies on whatever permission/authorization is configured at the viewset level, but the description promises a staff-only `can_change_orders` restriction; consider enforcing this explicitly (e.g., with a decorator or permission check) so the behavior is guaranteed regardless of viewset reuse.
- In `create_onsite`, you trust client-supplied `sales_channel` and `send_email` values after seeding defaults; if this endpoint is meant to consistently behave like box-office/import flows, you may want to either force `sales_channel='box_office'` and/or constrain overrides, and coerce `send_email` to a strict boolean to avoid unexpected truthy values from user input.

## Individual Comments

### Comment 1
<location> `src/tests/api/test_orders.py:4923-4924` </location>
<code_context>
     assert answ.answer.startswith('file://')
+
+
[email protected]_db
+def test_order_create_onsite(token_client, organizer, event, item, quota, question):
+    res = {
+        'email': '[email protected]',
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for permission behavior with authenticated users (with and without `can_change_orders`).

Currently the only negative permission test is `test_order_create_onsite_without_permission`, which checks an anonymous client gets a `401`. Please also add coverage for:

* An authenticated user without `can_change_orders` (and/or not staff) receiving the expected forbidden response (e.g. `403`).
* An authenticated staff user with `can_change_orders` successfully creating an order.

This will exercise the permission logic for authenticated roles, not just anonymous access.

Suggested implementation:

```python
@pytest.mark.django_db
def test_order_create_onsite_without_can_change_orders(api_client, organizer, event, item, quota, question, user):
    """
    Authenticated user without ``can_change_orders`` should not be allowed
    to create onsite orders.
    """
    # Make sure the user is authenticated but does not have the required permission
    user.is_staff = False
    user.user_permissions.clear()
    user.save()
    api_client.force_authenticate(user=user)

    payload = {
        'email': '[email protected]',
        'locale': 'en',
        'positions': [
            {
                'item': item.pk,
                'variation': None,
                'price': '23.00',
                'attendee_name_parts': {'full_name': 'John Doe'},
                'attendee_email': '[email protected]',
                'answers': [{'question': question.pk, 'answer': 'M', 'options': []}],
                'subevent': None,
            }
        ],
        'sales_channel': 'web',
    }

    url = f'/api/v1/organizers/{organizer.slug}/events/{event.slug}/orders/onsite/'
    resp = api_client.post(url, payload, format='json')

    assert resp.status_code == 403


@pytest.mark.django_db
def test_order_create_onsite_with_can_change_orders(api_client, organizer, event, item, quota, question, user, django_content_type):
    """
    Authenticated staff user with ``can_change_orders`` should be able to
    create onsite orders successfully.
    """
    # Grant the required permission
    from django.contrib.auth.models import Permission

    perm = Permission.objects.get(
        content_type=django_content_type('orders', 'order'),
        codename='can_change_orders',
    )
    user.is_staff = True
    user.user_permissions.add(perm)
    user.save()
    api_client.force_authenticate(user=user)

    payload = {
        'email': '[email protected]',
        'locale': 'en',
        'positions': [
            {
                'item': item.pk,
                'variation': None,
                'price': '23.00',
                'attendee_name_parts': {'full_name': 'John Doe'},
                'attendee_email': '[email protected]',
                'answers': [{'question': question.pk, 'answer': 'M', 'options': []}],
                'subevent': None,
            }
        ],
        'sales_channel': 'web',
    }

    url = f'/api/v1/organizers/{organizer.slug}/events/{event.slug}/orders/onsite/'
    resp = api_client.post(url, payload, format='json')

    assert resp.status_code in (201, 200)
    data = resp.json()
    assert data.get('email') == '[email protected]'
    assert data.get('code')


@pytest.mark.django_db
def test_order_create_onsite(token_client, organizer, event, item, quota, question):

```

Because only a fragment of the file is visible, you may need to adjust the following to integrate these tests correctly:

1. **Client fixture names**  
   - Replace `api_client` with the actual authenticated DRF client fixture used in this file (e.g. `client`, `auth_client`, or similar).
   - If you already use `token_client` for authenticated calls, you can:
     - Reuse it in these tests, or
     - Introduce a dedicated fixture that creates an authenticated user without `can_change_orders`.

2. **Permission/ContentType helpers**  
   - The helper `django_content_type('orders', 'order')` is a placeholder. If your test suite already has a way to get the `ContentType` or the permission object for `can_change_orders`, reuse that instead.
   - If you already import `Permission` elsewhere at the top of the file, remove the inline import in the test.

3. **URL and payload alignment**  
   - Ensure the URL path matches the existing onsite order creation endpoint used in `test_order_create_onsite`. If that test uses a helper (e.g. `url = api_client._get_onsite_url(event)`), reuse it instead of hardcoding.
   - Keep the payload in these new tests consistent with the one in `test_order_create_onsite` (add/remove fields as needed so the request is valid).

4. **Expected status code**  
   - If your API returns a specific success code (e.g. always `201`) for onsite order creation, narrow `assert resp.status_code in (201, 200)` to the exact expected code.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@MukundC25 MukundC25 force-pushed the feature/onsite-order-api branch from 28f114b to aaa6839 Compare December 19, 2025 14:26
@MukundC25
Copy link
Author

Thanks for the review!

I’ve addressed the feedback by:

  • Relying on OrderViewSet.write_permission = can_change_orders for staff-only access
  • Forcing sales_channel='box_office' and send_email=False to match import/box-office behavior
  • Adding explicit tests for authenticated users with and without can_change_orders

@mariobehling
Copy link
Member

This is no longer part of the initial development plan. Hence closing it.

@github-project-automation github-project-automation bot moved this from Backlog to Done in Eventyay Next Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

API to Create New Orders for On-Site Attendees

2 participants