diff --git a/doc/api/resources/checkinlists.rst b/doc/api/resources/checkinlists.rst index d0c0c0233..07664f60c 100644 --- a/doc/api/resources/checkinlists.rst +++ b/doc/api/resources/checkinlists.rst @@ -604,6 +604,8 @@ Order position endpoints :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 404: The requested order position or check-in list does not exist. +.. _`rest-checkin-redeem`: + .. http:post:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/(id)/redeem/ Tries to redeem an order position, identified by its internal ID, i.e. checks the attendee in. This endpoint diff --git a/doc/development/algorithms/checkin.rst b/doc/development/algorithms/checkin.rst new file mode 100644 index 000000000..1eeaf2916 --- /dev/null +++ b/doc/development/algorithms/checkin.rst @@ -0,0 +1,28 @@ +.. spelling: libpretixsync + +Check-in algorithms +=================== + +When a ticket is scanned at the entrance or exit of an event, we follow a series of steps to determine whether +the check-in is allowed or not. To understand some of the terms in the following diagrams, you should also check +out the documentation of the :ref:`ticket redemption API endpoint `. + +Server-side +----------- + +The following diagram shows the series of checks executed on the server when a ticket is redeemed through the API. +Some simplifications have been made, for example the de-duplication mechanism based on the ``nonce`` parameter +to prevent re-uploads of the same scan is not shown. + +.. image:: /images/checkin_online.png + +Client-side +----------- + +The process of verifying tickets offline is a little different. There are two different approaches, +depending on whether we have information about all tickets in the local database. The following diagram shows +the algorithm as currently implemented in recent versions of `libpretixsync`_. + +.. image:: /images/checkin_offline.png + +.. _libpretixsync: https://github.com/pretix/libpretixsync diff --git a/doc/development/algorithms/index.rst b/doc/development/algorithms/index.rst new file mode 100644 index 000000000..76937b5b1 --- /dev/null +++ b/doc/development/algorithms/index.rst @@ -0,0 +1,13 @@ +Algorithms +========== + +The business logic inside pretix is full of complex algorithms making decisions based on all the hundreds of settings +and input parameters available. Some of them are documented here as graphs, either because fully understanding them is very +when working on features close to them, or because they also need to be re-implemented by client-side components like our +ticket scanning apps and we want to ensure the implementations are as similar as possible to avoid confusion. + +.. toctree:: + :maxdepth: 2 + + checkin + layouts diff --git a/doc/development/algorithms/layouts.rst b/doc/development/algorithms/layouts.rst new file mode 100644 index 000000000..e2a486b60 --- /dev/null +++ b/doc/development/algorithms/layouts.rst @@ -0,0 +1,15 @@ +.. spelling: pretixPOS + +Ticket layout +============= + +When a ticket is exported to PDF, the system needs to decide which of multiple PDF layouts to use. The +following diagram shows the steps of the decision, showing both the implementation in pretix itself as +well as the implementation in `pretixPOS`_. + +The process can be influenced by plugins, which is demonstrated with the example of the shipping plugin. + +.. image:: /images/ticket_layouts.png + + +.. _pretixPOS: https://pretix.eu/about/en/pos diff --git a/doc/development/index.rst b/doc/development/index.rst index 2bf5807ec..14e785ed0 100644 --- a/doc/development/index.rst +++ b/doc/development/index.rst @@ -8,6 +8,7 @@ Developer documentation setup contribution/index implementation/index - translation/index + algorithms/index api/index structure + translation/index diff --git a/doc/images/checkin_offline.png b/doc/images/checkin_offline.png new file mode 100644 index 000000000..bd01744e6 Binary files /dev/null and b/doc/images/checkin_offline.png differ diff --git a/doc/images/checkin_offline.puml b/doc/images/checkin_offline.puml new file mode 100644 index 000000000..daa96fe6b --- /dev/null +++ b/doc/images/checkin_offline.puml @@ -0,0 +1,146 @@ +@startuml + + +partition "data-based check" { + "Check based on local database" --> "Is the order in status PAID or PENDING\nand is the position not canceled?" + --> if "" then + -right->[no] "Return error CANCELED" + else + -down->[yes] "Is the product part of the check-in list?" + --> if "" then + -right->[no] "Return error PRODUCT" + else + -down->[yes] "Is the subevent part of the check-in list?" + --> if "" then + -right->[no] "Return error INVALID" + note bottom: TODO\ninconsistent\nwith online\ncheck + else + -down->[yes] "Is the order in status PAID?" + --> if "" then + -right->[no] "Does the check-in list include pending orders?" + --> if "" then + -right->[no] "Return error UNPAID " + else + -down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)" + --> if "" then + -right->[no] "Return error UNPAID " + else + -down->[yes] "Is this an entry or exit?" + endif + endif + else + -down->[yes] "Is this an entry or exit?" + endif + endif + endif + endif + + "Is this an entry or exit?" --> if "" then + -right->[entry] Evaluate custom logic (rules) + --> if "" then + -right->[error] "Return error RULES" + else + -down->[ok] "Are all required questions answered?" + --> if "" then + -right->[no] "Return error INCOMPLETE" + else + -down->[yes] "Does the check-in list allow multi-entry?" + endif + endif + else + -->[exit] "Return OK " + endif + + "Does the check-in list allow multi-entry?" --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Is this the first checkin\nfor this ticket on this list?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Are all previous checkins\nfor this ticket on this list exits?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Does the check-in list\n allow entry after exit\nand is the last checkin\nan exit?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Return error ALREADY_REDEEMED" + endif + endif + endif + endif +} + +partition "dataless check" { + "Check based on secret content" --> "Does the secret decode with\nany supported scheme\nand has a valid signature?" + + --> if "" then + -down->[yes] "Is the ticket secret on the revocation list?" + --> if "" then + -right->[yes] "Return error REVOKED" + else + -down->[no] "Is the product part of the check-in list? " + --> if "" then + -right->[no] "Return error PRODUCT " + else + -down->[yes] "Is the subevent part of the check-in list? " + --> if "" then + -right->[no] "Return error INVALID " + note bottom: TODO\ninconsistent\nwith online\ncheck + else + --> "Is this an entry or exit? " + endif + endif + endif + else + -right>[no] "Return error INVALID " + endif + + "Is this an entry or exit? " --> if "" then + -right->[entry] "Evaluate custom logic (rules) " + --> if "" then + -right->[error] "Return error RULES " + else + -down->[ok] "Are all required questions answered? " + --> if "" then + -right->[no] "Return error INCOMPLETE " + else + -down->[yes] "Does the check-in list allow multi-entry? " + endif + endif + else + -->[exit] " Return OK " + endif + + "Does the check-in list allow multi-entry? " --> if "" then + -right->[yes] " Return OK " + else + -down->[no] "Are any locally queued checkins for\nthis ticket of this list known?" + --> if "" then + -right->[no] " Return OK " + else + -down->[yes] "Are all locally queued checkins\nfor this ticket on this list exits? " + --> if "" then + -right->[yes] " Return OK " + else + -down->[no] "Does the check-in list\n allow entry after exit\nand is the last locally\nqueued checkin\nan exit? " + --> if "" then + -right->[yes] " Return OK " + else + -down->[no] "Return error ALREADY_REDEEMED " + endif + endif + endif + endif +} + +(*) --> "Check if order position with\nscanned ticket secret exists" +--> if "" then + -down->[yes] "Check based on local database" +else + -->[no] "Check based on secret content" +endif + +@enduml diff --git a/doc/images/checkin_online.png b/doc/images/checkin_online.png new file mode 100644 index 000000000..0d630620c Binary files /dev/null and b/doc/images/checkin_online.png differ diff --git a/doc/images/checkin_online.puml b/doc/images/checkin_online.puml new file mode 100644 index 000000000..1c1e9b866 --- /dev/null +++ b/doc/images/checkin_online.puml @@ -0,0 +1,92 @@ +@startuml + +(*) --> "Check if order position with\nscanned ticket secret exists" +--> if "" then + -down->[yes] ===CHECK=== +else + -->[no] "Check if secret exists\nin revocation list" + --> if "" then + --> "Is this a forced upload?" + --> if "" then + -->[yes] ===CHECK=== + else + -right->[no] "Return error REVOKED" + endif + else + -right->[no] "Return error INVALID" + endif + +endif + + +===CHECK=== -down-> "Is the order in status PAID or PENDING\nand is the position not canceled?" +--> if "" then + -right->[no] "Return error CANCELED" +else + -down->[yes] "Is the product part of the check-in list?" + --> if "" then + -right->[no] "Return error PRODUCT" + else + -down->[yes] "Is the subevent part of the check-in list?" + --> if "" then + -right->[no] "Return error PRODUCT " + else + -down->[yes] "Is the order in status PAID\nor is this a forced upload?" + --> if "" then + -right->[no] "Does the check-in list include pending orders?" + --> if "" then + -right->[no] "Return error UNPAID " + else + -down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)" + --> if "" then + -right->[no] "Return error UNPAID " + else + -down->[yes] "Is this an entry or exit?\nIs the upload forced?" + endif + endif + else + -down->[yes] "Is this an entry or exit?\nIs the upload forced?" + endif + endif + endif +endif + +"Is this an entry or exit?\nIs the upload forced?" --> if "" then + -right->[entry && not force] Evaluate custom logic (rules) + --> if "" then + -right->[error] "Return error RULES" + else + -down->[ok] "Are all required questions answered?" + --> if "" then + -right->[no && questions_supported] "Return error INCOMPLETE" + else + -down->[yes || not questions_supported] "Does the check-in list allow multi-entry?" + endif + endif +else + -->[exit || force=true] "Return OK " +endif + +"Does the check-in list allow multi-entry?" --> if "" then + -right->[yes] "Return OK" +else + -down->[no] "Is this the first checkin\nfor this ticket on this list?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Are all previous checkins\nfor this ticket on this list exits?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Does the check-in list\n allow entry after exit\nand is the last checkin\nan exit?" + --> if "" then + -right->[yes] "Return OK" + else + -down->[no] "Return error ALREADY_REDEEMED" + endif + endif + endif +endif + + +@enduml diff --git a/doc/images/ticket_layouts.png b/doc/images/ticket_layouts.png new file mode 100644 index 000000000..382e277aa Binary files /dev/null and b/doc/images/ticket_layouts.png differ diff --git a/doc/images/ticket_layouts.puml b/doc/images/ticket_layouts.puml new file mode 100644 index 000000000..55e803036 --- /dev/null +++ b/doc/images/ticket_layouts.puml @@ -0,0 +1,52 @@ +@startuml + +(*) --> "Which implementation?" +--> if "" then + -down->[pretixPOS] "Check for TicketLayoutItem with\nsales_channel=pretixpos [libpretixsync]" + --> if "" then + --> (*) + else + -->[not found] "Check for TicketLayoutItem with\nsales_channel=web [libpretixsync]" + --> if "" then + --> (*) + else + -->[not found] "Use event default [libpretixsync]" + --> (*) + endif + endif + +else + -right->[pretix] "Check for TicketLayoutItem with\nsales_channel=order.sales_channel" + --> if "" then + -right-> "Run override_layout plugin signal on result" + else + -down->[not found] "Check for TicketLayoutItem with\nsales_channel=web" + --> if "" then + --> "Run override_layout plugin signal on result" + else + -->[not found] "Use event default" + --> "Run override_layout plugin signal on result" + endif + endif +endif + + +"Run override_layout plugin signal on result" -> (*) + + +partition pretix_shipping { + "Run override_layout plugin signal on result" --> "Check for ShippingLayoutItem with\nmethod=order.shipping_method" +--> if "" then + --> (*) +else + -down->[not found] "Check for ShippingMethod.layout" + --> if "" then + --> (*) + else + -down->[not found] "Keep original layout" + --> (*) + endif +endif +} + +@enduml diff --git a/src/pretix/base/services/checkin.py b/src/pretix/base/services/checkin.py index 55667eaff..bd0f80e49 100644 --- a/src/pretix/base/services/checkin.py +++ b/src/pretix/base/services/checkin.py @@ -597,6 +597,11 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, :param nonce: A random nonce to prevent race conditions. :param datetime: The datetime of the checkin, defaults to now. """ + + # !!!!!!!!! + # Update doc/images/checkin_online.puml if you make substantial changes here! + # !!!!!!!!! + dt = datetime or now() if op.canceled or op.order.status not in (Order.STATUS_PAID, Order.STATUS_PENDING):