Raphael MichelandGitHub 0067c3537d Fix invalid orders being created in a complex situation (#4054)
This was a bug that took days to find. The story goes like this: A cart
is created with four positions that each include four bundled positions.
A discount is applied, changing the price of *one* of the four top-level
positions to a reduced value. The list of position IDs gets passed to
`perform_order()`, which later passes it on to `transform_cart_positions()`.
`transform_cart_positions()`, however, receives the positions in an order
that has the first-level product *after* the bundled products that
belong to it. Therefore, it can't properly assign the parent-child
relationship between the positions.

The main reason is that cart positions are processed in "database order"
in a number of places, i.e. we make `SELECT` queries without an explicit
`ORDER BY` statement, leading the database to respond in unspecified
order. This is the case for `get_cart()` and hence for `CartMixin.positions`,
and hence for the list of position IDs that is passed to `perform_order()`
and hence for the order in which discounts are processed.

Therefore, if this "databse order" of the cart positions changes, the
discount compuation in `_check_positions()` might make a different choice
of *which* cart position should receive the discount than the CartManager
originally did. That's not nice, but most customers would not even
notice that a different one of their four (otherwise identical) tickets
is now discounted than the cart originally showed.

This leads to `_check_positions()` changing the price on two of the
cart positions. However, it only changes the price on the copy of
the CartPosition object that is directly part of the positions array,
while the `addon_to` attribute of its bundled positions contain a
*different* representation of the same cart position, that is not
refreshed to have the updated price now in the database.

This causes the `CartPosition.sort_key` of the bundled products to be
significantly different from the one of their parent products, which can
cause `transform_cart_positions()` to try to insert them before their
respective parent product, which is how the bug leads to the nasty end
result.

Now, I'm still not sure why this has happened *now* for the first time,
but I suspect it *might* even have something to do with our operations
team tuning our autovacuum parameters on our production installation,
which might make it *more likely* that newly created cart positions are
arbitrarily  stored on PostgreSQL disk pages in a different order than
they were inserted than before.

This commit now fixes the bug now in two ways, each of which would be
sufficient to fix it for now, but together they make it hopefully more
stable in the future:

- `perform_order` no longer respects the order of the position IDs it
  gets passed in, but instead uses the order last displayed in the cart.
  Additionally, both `CartManager` and `_check_positions()` now sort
  positions by their `pk` value before applying discounts to ensure
  consistent choice of which position is discounted (using  `sort_key`
  here does not make much sense since it includes sorting by price,
  which is about to change).

- `_check_positions()` makes sure that after its completion, only one
  copy of the same `CartPosition` is in use that has the current price.

Additionally, this commit makes sure `sort_key` cache is cleared after
e.g. a price change.

It was hard to write a regression test, since "database order" is, by
definition, unreliable, but I tried my best.
2024-04-08 16:55:54 +02:00
2024-04-05 17:28:44 +02:00
2017-02-16 10:33:08 +01:00
2020-12-14 13:11:19 +01:00
2016-02-16 16:17:04 +01:00
2022-10-10 11:47:00 +02:00
2021-04-23 13:09:09 +02:00
2023-04-26 15:54:15 +02:00

pretix
======

.. image:: https://img.shields.io/pypi/v/pretix.svg
   :target: https://pypi.python.org/pypi/pretix

.. image:: https://github.com/pretix/pretix/workflows/Documentation/badge.svg
   :target: https://docs.pretix.eu/en/latest/

.. image:: https://github.com/pretix/pretix/workflows/Tests/badge.svg

.. image:: https://codecov.io/gh/pretix/pretix/branch/master/graph/badge.svg
   :target: https://codecov.io/gh/pretix/pretix



Reinventing ticket presales, one ticket at a time.

Project status & release cycle
------------------------------

While there is always a lot to do and improve on, pretix by now has been in use for thousands of events
conferences that sold millions of tickets combined. We therefore think of pretix as being stable and ready to use.

If you want to use or extend pretix, we strongly recommend to follow our `blog`_. We will announce all
releases there. You can always find the latest stable version on PyPI or in the ``release/X.Y`` branch of
this repository. The ``master`` branch contains a development version that we also try to keep stable in
the sense that it does not break your data,  but its APIs might change without prior notice.

To get started using pretix on your own server, look at the `installation guide`_ in our documentation.

Support
-------

This project is 100 percent free and open source software. You are welcome to ask questions in the GitHub
repository. Private support via email or phone is only offered to customers of our pretix Hosted or pretix
Enterprise offerings. If you are interested in commercial support, hosting services or supporting this project
financially, please go to `pretix.eu`_ or contact us at support@pretix.eu.

Contributing
------------
If you want to contribute to pretix, please read the `developer documentation`_
in our documentation. If you have any further questions, please do not hesitate to ask!

.. image:: https://translate.pretix.eu/widgets/pretix/-/pretix/multi-blue.svg
   :target: https://translate.pretix.eu/engage/pretix/

Code of Conduct
---------------
We have a `Code of Conduct`_ in place that applies to all project contributions,
including issues, pull requests, etc.

License
-------

The code in this repository is covered by different licenses. Most of it is available to everyone under the terms of
the GNU AGPL license v3 with additional terms. See the LICENSE file for the complete license details.

.. _installation guide: https://docs.pretix.eu/en/latest/admin/installation/index.html
.. _developer documentation: https://docs.pretix.eu/en/latest/development/index.html
.. _Code of Conduct: https://docs.pretix.eu/en/latest/development/contribution/codeofconduct.html
.. _pretix.eu: https://pretix.eu
.. _blog: https://pretix.eu/about/en/blog/
S
Description
Ticket shop application for conferences, festivals, concerts, tech events, shows, exhibitions, workshops, barcamps, etc.
Readme Multiple Licenses
918 MiB
Languages
Python 77.8%
HTML 12.8%
JavaScript 4.9%
SCSS 3.2%
Vue 0.8%
Other 0.4%