mirror of
https://github.com/pretix/pretix.git
synced 2026-05-16 17:03:58 +00:00
Fix tests
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
@@ -522,6 +523,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
textobject.textLine(self._normalize(self._upper(pgettext('invoice', 'Event'))))
|
textobject.textLine(self._normalize(self._upper(pgettext('invoice', 'Event'))))
|
||||||
canvas.drawText(textobject)
|
canvas.drawText(textobject)
|
||||||
|
|
||||||
|
def _date_range_in_header(self):
|
||||||
|
if self.invoice.event.has_subevents or not self.invoice.event.settings.show_dates_on_frontpage:
|
||||||
|
return None, None
|
||||||
|
tz = self.invoice.event.timezone
|
||||||
|
show_end_date = (
|
||||||
|
self.invoice.event.settings.show_date_to and
|
||||||
|
self.invoice.event.date_to and
|
||||||
|
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date()
|
||||||
|
)
|
||||||
|
if show_end_date:
|
||||||
|
return self.invoice.event.date_from.astimezone(tz).date(), self.invoice.event.date_to.astimezone(tz).date
|
||||||
|
else:
|
||||||
|
return self.invoice.event.date_from.astimezone(tz).date(), None
|
||||||
|
|
||||||
def _draw_event(self, canvas):
|
def _draw_event(self, canvas):
|
||||||
def shorten(txt):
|
def shorten(txt):
|
||||||
txt = str(txt)
|
txt = str(txt)
|
||||||
@@ -535,25 +550,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
p_size = p.wrap(self.event_width, self.event_height)
|
p_size = p.wrap(self.event_width, self.event_height)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
|
d_from, d_to = self._date_range_in_header()
|
||||||
tz = self.invoice.event.timezone
|
if d_from and d_to:
|
||||||
show_end_date = (
|
p_str = (
|
||||||
self.invoice.event.settings.show_date_to and
|
shorten(self.invoice.event.name) + '\n' +
|
||||||
self.invoice.event.date_to and
|
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
|
||||||
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date()
|
from_date=date_format(d_from, "DATE_FORMAT"),
|
||||||
|
to_date=date_format(d_to, "DATE_FORMAT"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if show_end_date:
|
elif d_from:
|
||||||
p_str = (
|
p_str = shorten(self.invoice.event.name) + '\n' + date_format(d_from, "DATE_FORMAT")
|
||||||
shorten(self.invoice.event.name) + '\n' +
|
|
||||||
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
|
|
||||||
from_date=self.invoice.event.get_date_from_display(show_times=False),
|
|
||||||
to_date=self.invoice.event.get_date_to_display(show_times=False)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
p_str = (
|
|
||||||
shorten(self.invoice.event.name) + '\n' + self.invoice.event.get_date_from_display(show_times=False)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
p_str = shorten(self.invoice.event.name)
|
p_str = shorten(self.invoice.event.name)
|
||||||
|
|
||||||
@@ -657,6 +664,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
def _get_story(self, doc):
|
def _get_story(self, doc):
|
||||||
has_taxes = any(il.tax_value for il in self.invoice.lines.all()) or self.invoice.reverse_charge
|
has_taxes = any(il.tax_value for il in self.invoice.lines.all()) or self.invoice.reverse_charge
|
||||||
|
header_dates = self._date_range_in_header()
|
||||||
|
tz = self.invoice.event.timezone
|
||||||
|
|
||||||
story = [
|
story = [
|
||||||
NextPageTemplate('FirstPage'),
|
NextPageTemplate('FirstPage'),
|
||||||
@@ -700,15 +709,68 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
def _group_key(line):
|
def _group_key(line):
|
||||||
return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent_id,
|
return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent,
|
||||||
line.event_date_from, line.event_date_to)
|
line.period_start, line.period_end)
|
||||||
|
|
||||||
|
def day(dt: datetime.datetime) -> datetime.date:
|
||||||
|
if dt is None:
|
||||||
|
return None
|
||||||
|
return dt.astimezone(tz).date()
|
||||||
|
|
||||||
total = Decimal('0.00')
|
total = Decimal('0.00')
|
||||||
for (description, tax_rate, tax_name, net_value, gross_value, *ignored), lines in addon_aware_groupby(
|
for (description, tax_rate, tax_name, net_value, gross_value, subevent, period_start, period_end), lines in addon_aware_groupby(
|
||||||
self.invoice.lines.all(),
|
self.invoice.lines.all(),
|
||||||
key=_group_key,
|
key=_group_key,
|
||||||
is_addon=lambda l: l.description.startswith(" +"),
|
is_addon=lambda l: l.description.startswith(" +"),
|
||||||
):
|
):
|
||||||
|
# Try to be clever and figure out when organizers would want to show the period. This heuristic is
|
||||||
|
# not perfect and the only "fully correct" way would be to include the period on every line always,
|
||||||
|
# however this will cause confusion (a) due to useless repetition of the same date all over the invoice
|
||||||
|
# (b) due to not respecting the show_date_to setting of events in cases where we could have respected it.
|
||||||
|
# Still, we want to show the date explicitly if its different to the event or invoice date.
|
||||||
|
if period_start and period_end and day(period_end) != day(period_start):
|
||||||
|
# It's a multi-day period, such as the validity of the ticket or an event date period
|
||||||
|
|
||||||
|
if day(period_start) == header_dates[0] and day(period_end) == header_dates[1]:
|
||||||
|
# This is the exact event period we already printed in the header, no need to repeat it.
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
elif (self.event.has_subevents and subevent and day(subevent.date_from) == day(period_start) and
|
||||||
|
day(subevent.date_to) == day(period_end)):
|
||||||
|
# For subevents, build_invoice already includes the date in the description in the event-default format.
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
else:
|
||||||
|
period_line = f"\n{date_format(day(period_start), 'SHORT_DATE_FORMAT')} – {date_format(day(period_end), 'SHORT_END_FORMAT')}"
|
||||||
|
|
||||||
|
elif period_start or period_end:
|
||||||
|
# It's a single-day period
|
||||||
|
|
||||||
|
delivery_day = day(period_end or period_start)
|
||||||
|
if delivery_day in (header_dates[0], header_dates[1]):
|
||||||
|
# This is the event date we already printed in the header, no need to repeat it.
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
elif self.event.has_subevents and subevent and delivery_day in (day(subevent.date_from), day(subevent.date_to)):
|
||||||
|
# For subevents, build_invoice already includes the date in the description in the event-default format.
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
elif (delivery_day == self.invoice.date) and header_dates[0] is None:
|
||||||
|
# This is a shop that doesn't show the date of the event in the header, and the period is the invoice
|
||||||
|
# date. We assume that this is an 'everything is executed immediately' situation and do not want to
|
||||||
|
# confuse with showing additional dates on the invoice. This is the case that is not guaranteed to be
|
||||||
|
# correct in all cases and might need to change in the future. If customers have legal concerns, a
|
||||||
|
# quick fix is including a sentence like "Delivery date is the invoice date unless otherwise indicated:"
|
||||||
|
# in a custom text on the invoice.
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
else:
|
||||||
|
period_line = f"\n{date_format(day(delivery_day), 'SHORT_DATE_FORMAT')}"
|
||||||
|
else:
|
||||||
|
# No period known
|
||||||
|
period_line = ""
|
||||||
|
|
||||||
|
description += period_line
|
||||||
lines = list(lines)
|
lines = list(lines)
|
||||||
if has_taxes:
|
if has_taxes:
|
||||||
if len(lines) > 1:
|
if len(lines) > 1:
|
||||||
@@ -717,6 +779,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
gross_price=money_filter(gross_value, self.invoice.event.currency),
|
gross_price=money_filter(gross_value, self.invoice.event.currency),
|
||||||
)
|
)
|
||||||
description = description + "\n" + single_price_line
|
description = description + "\n" + single_price_line
|
||||||
|
|
||||||
tdata.append((
|
tdata.append((
|
||||||
FontFallbackParagraph(
|
FontFallbackParagraph(
|
||||||
self._clean_text(description, tags=['br']),
|
self._clean_text(description, tags=['br']),
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
|
|
||||||
lp = invoice.order.payments.last()
|
lp = invoice.order.payments.last()
|
||||||
|
|
||||||
|
min_period_start = None
|
||||||
|
max_period_end = None
|
||||||
|
|
||||||
with (language(invoice.locale, invoice.event.settings.region)):
|
with (language(invoice.locale, invoice.event.settings.region)):
|
||||||
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
|
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
|
||||||
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
||||||
@@ -208,7 +211,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
positions = list(
|
positions = list(
|
||||||
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate(
|
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate(
|
||||||
addon_c=Count('addons')
|
addon_c=Count('addons')
|
||||||
).prefetch_related('answers', 'answers__options', 'answers__question').order_by('positionid', 'id')
|
).prefetch_related(
|
||||||
|
'answers', 'answers__options', 'answers__question', 'granted_memberships',
|
||||||
|
).order_by('positionid', 'id')
|
||||||
)
|
)
|
||||||
|
|
||||||
reverse_charge = False
|
reverse_charge = False
|
||||||
@@ -267,6 +272,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
location=_location_oneliner(location)
|
location=_location_oneliner(location)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
period_start, period_end = _service_period_for_position(invoice, p)
|
||||||
|
min_period_start = min(min_period_start or period_start, period_start)
|
||||||
|
max_period_end = min(max_period_end or period_end, period_end)
|
||||||
|
|
||||||
InvoiceLine.objects.create(
|
InvoiceLine.objects.create(
|
||||||
position=i,
|
position=i,
|
||||||
invoice=invoice,
|
invoice=invoice,
|
||||||
@@ -277,8 +286,8 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
item=p.item,
|
item=p.item,
|
||||||
variation=p.variation,
|
variation=p.variation,
|
||||||
attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None,
|
attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None,
|
||||||
period_start=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from,
|
period_start=period_start,
|
||||||
period_end=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to,
|
period_end=period_end,
|
||||||
event_location=location if invoice.event.settings.invoice_event_location else None,
|
event_location=location if invoice.event.settings.invoice_event_location else None,
|
||||||
tax_rate=p.tax_rate,
|
tax_rate=p.tax_rate,
|
||||||
tax_code=p.tax_code,
|
tax_code=p.tax_code,
|
||||||
@@ -301,13 +310,29 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
fee_title = _(fee.get_fee_type_display())
|
fee_title = _(fee.get_fee_type_display())
|
||||||
if fee.description:
|
if fee.description:
|
||||||
fee_title += " - " + fee.description
|
fee_title += " - " + fee.description
|
||||||
|
|
||||||
|
if min_period_start and max_period_end:
|
||||||
|
# Consider fees to have the same service period as the products sold
|
||||||
|
period_start = min_period_start
|
||||||
|
period_end = max_period_end
|
||||||
|
else:
|
||||||
|
# Usually can only happen if everything except a cancellation fee is removed
|
||||||
|
if invoice.event.settings.invoice_period in ("auto", "auto_no_event", "event_date") and not invoice.event.has_subevents:
|
||||||
|
# Non-series event, let's be backwards-compatible and tag everything with the event period
|
||||||
|
period_start = invoice.event.date_from
|
||||||
|
period_end = invoice.event.date_to
|
||||||
|
else:
|
||||||
|
# We could try to work from the canceled positions, but it doesn't really make sense. A cancellation
|
||||||
|
# fee is not "delivered" at the event date, it is rather effective right now.
|
||||||
|
period_start = period_end = now()
|
||||||
|
|
||||||
InvoiceLine.objects.create(
|
InvoiceLine.objects.create(
|
||||||
position=i + offset,
|
position=i + offset,
|
||||||
invoice=invoice,
|
invoice=invoice,
|
||||||
description=fee_title,
|
description=fee_title,
|
||||||
gross_value=fee.value,
|
gross_value=fee.value,
|
||||||
period_start=None if invoice.event.has_subevents else invoice.event.date_from,
|
period_start=period_start,
|
||||||
period_end=None if invoice.event.has_subevents else invoice.event.date_to,
|
period_end=period_end,
|
||||||
event_location=(
|
event_location=(
|
||||||
None if invoice.event.has_subevents
|
None if invoice.event.has_subevents
|
||||||
else (str(invoice.event.location)
|
else (str(invoice.event.location)
|
||||||
@@ -351,6 +376,43 @@ def build_cancellation(invoice: Invoice):
|
|||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
|
||||||
|
def _service_period_for_position(invoice, position):
|
||||||
|
if invoice.event.settings.invoice_period in ("auto", "auto_no_event"):
|
||||||
|
if position.valid_from or position.valid_until:
|
||||||
|
period_start = position.valid_from or now()
|
||||||
|
period_end = position.valid_until
|
||||||
|
elif memberships := list(position.granted_memberships.all()):
|
||||||
|
period_start = min(m.date_start for m in memberships)
|
||||||
|
period_end = max(m.date_end for m in memberships)
|
||||||
|
elif invoice.event.has_subevents and position.subevent:
|
||||||
|
period_start = position.subevent.date_from
|
||||||
|
period_end = position.subevent.date_to
|
||||||
|
elif invoice.event.settings.invoice_period == "auto_no_event":
|
||||||
|
period_start = now()
|
||||||
|
period_end = now()
|
||||||
|
else:
|
||||||
|
period_start = invoice.event.date_from
|
||||||
|
period_end = invoice.event.date_to
|
||||||
|
elif invoice.event.settings.invoice_period == "order_date":
|
||||||
|
period_start = invoice.order.datetime
|
||||||
|
period_end = invoice.order.datetime
|
||||||
|
elif invoice.event.settings.invoice_period == "event_date":
|
||||||
|
if position.subevent:
|
||||||
|
period_start = position.subevent.date_from
|
||||||
|
period_end = position.subevent.date_to
|
||||||
|
else:
|
||||||
|
period_start = invoice.event.date_from
|
||||||
|
period_end = invoice.event.date_to
|
||||||
|
elif invoice.event.settings.invoice_period == "invoice_date":
|
||||||
|
period_start = period_end = now()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid invoice period setting '{invoice.event.settings.invoice_period}'")
|
||||||
|
|
||||||
|
if not period_end:
|
||||||
|
period_end = period_start
|
||||||
|
return period_start, period_end
|
||||||
|
|
||||||
|
|
||||||
def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||||
if invoice.canceled:
|
if invoice.canceled:
|
||||||
raise ValueError("Invoice should not be canceled twice.")
|
raise ValueError("Invoice should not be canceled twice.")
|
||||||
@@ -456,6 +518,12 @@ def build_preview_invoice_pdf(event):
|
|||||||
if not locale or locale == '__user__':
|
if not locale or locale == '__user__':
|
||||||
locale = event.settings.locale
|
locale = event.settings.locale
|
||||||
|
|
||||||
|
if event.settings.invoice_period in ("auto", "auto_no_event", "event_date"):
|
||||||
|
period_start = event.date_from
|
||||||
|
period_end = event.date_to or event.date_from
|
||||||
|
else:
|
||||||
|
period_start = period_end = timezone.now()
|
||||||
|
|
||||||
with rolledback_transaction(), language(locale, event.settings.region):
|
with rolledback_transaction(), language(locale, event.settings.region):
|
||||||
order = event.orders.create(
|
order = event.orders.create(
|
||||||
status=Order.STATUS_PENDING, datetime=timezone.now(),
|
status=Order.STATUS_PENDING, datetime=timezone.now(),
|
||||||
@@ -506,8 +574,8 @@ def build_preview_invoice_pdf(event):
|
|||||||
invoice=invoice, description=_("Sample product {}").format(i + 1),
|
invoice=invoice, description=_("Sample product {}").format(i + 1),
|
||||||
gross_value=tax.gross, tax_value=tax.tax,
|
gross_value=tax.gross, tax_value=tax.tax,
|
||||||
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
|
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
|
||||||
period_start=event.date_from,
|
period_start=period_start,
|
||||||
period_end=event.date_to,
|
period_end=period_end,
|
||||||
event_location=event.settings.invoice_event_location,
|
event_location=event.settings.invoice_event_location,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -515,8 +583,8 @@ def build_preview_invoice_pdf(event):
|
|||||||
InvoiceLine.objects.create(
|
InvoiceLine.objects.create(
|
||||||
invoice=invoice, description=_("Sample product A"),
|
invoice=invoice, description=_("Sample product A"),
|
||||||
gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
|
gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
|
||||||
period_start=event.date_from,
|
period_start=period_start,
|
||||||
period_end=event.date_to,
|
period_end=period_end,
|
||||||
event_location=event.settings.invoice_event_location,
|
event_location=event.settings.invoice_event_location,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1105,8 +1105,9 @@ DEFAULTS = {
|
|||||||
'serializer_class': serializers.ChoiceField,
|
'serializer_class': serializers.ChoiceField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
choices=(
|
choices=(
|
||||||
('auto', _('Ticket-specific validity or event series date or event date')),
|
('auto', _('Automatic based on ticket-specific validity, membership validity, event series date, or event date)')),
|
||||||
('auto_no_event', _('Ticket-specific validity or event series date or invoice date')),
|
('auto_no_event', _('Automatic, but prefer invoice date over event date')),
|
||||||
|
('event_date', _('Event date')),
|
||||||
('order_date', _('Order date')),
|
('order_date', _('Order date')),
|
||||||
('invoice_date', _('Invoice date')),
|
('invoice_date', _('Invoice date')),
|
||||||
),
|
),
|
||||||
@@ -1115,13 +1116,15 @@ DEFAULTS = {
|
|||||||
label=_("Date of service"),
|
label=_("Date of service"),
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
choices=(
|
choices=(
|
||||||
('auto', _('Ticket-specific validity or event series date or event date')),
|
('auto', _('Automatic based on ticket-specific validity, membership validity, event series date, or event date)')),
|
||||||
('auto_no_event', _('Ticket-specific validity or event series date or invoice date')),
|
('auto_no_event', _('Automatic, but prefer invoice date over event date')),
|
||||||
|
('event_date', _('Event date')),
|
||||||
('order_date', _('Order date')),
|
('order_date', _('Order date')),
|
||||||
('invoice_date', _('Invoice date')),
|
('invoice_date', _('Invoice date')),
|
||||||
),
|
),
|
||||||
help_text=_("This controls what dates are shown on the invoice, but is especially important for "
|
help_text=_("This controls what dates are shown on the invoice, but is especially important for "
|
||||||
"electronic invoicing."),
|
"electronic invoicing."),
|
||||||
|
required=True,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'invoice_reissue_after_modify': {
|
'invoice_reissue_after_modify': {
|
||||||
|
|||||||
@@ -231,9 +231,9 @@ TEST_INVOICE_RES = {
|
|||||||
"description": "Budget Ticket<br />Attendee: Peter",
|
"description": "Budget Ticket<br />Attendee: Peter",
|
||||||
'subevent': None,
|
'subevent': None,
|
||||||
'event_date_from': '2017-12-27T10:00:00Z',
|
'event_date_from': '2017-12-27T10:00:00Z',
|
||||||
'event_date_to': None,
|
'event_date_to': '2017-12-27T10:00:00Z',
|
||||||
'period_start': '2017-12-27T10:00:00Z',
|
'period_start': '2017-12-27T10:00:00Z',
|
||||||
'period_end': None,
|
'period_end': '2017-12-27T10:00:00Z',
|
||||||
'event_location': None,
|
'event_location': None,
|
||||||
'attendee_name': 'Peter',
|
'attendee_name': 'Peter',
|
||||||
'item': None,
|
'item': None,
|
||||||
@@ -251,9 +251,9 @@ TEST_INVOICE_RES = {
|
|||||||
"description": "Payment fee",
|
"description": "Payment fee",
|
||||||
'subevent': None,
|
'subevent': None,
|
||||||
'event_date_from': '2017-12-27T10:00:00Z',
|
'event_date_from': '2017-12-27T10:00:00Z',
|
||||||
'event_date_to': None,
|
'event_date_to': '2017-12-27T10:00:00Z',
|
||||||
'period_start': '2017-12-27T10:00:00Z',
|
'period_start': '2017-12-27T10:00:00Z',
|
||||||
'period_end': None,
|
'period_end': '2017-12-27T10:00:00Z',
|
||||||
'event_location': None,
|
'event_location': None,
|
||||||
'attendee_name': None,
|
'attendee_name': None,
|
||||||
'fee_type': "payment",
|
'fee_type': "payment",
|
||||||
|
|||||||
@@ -608,9 +608,9 @@ def test_order_create_invoice(token_client, organizer, event, order):
|
|||||||
'description': 'Budget Ticket<br />Attendee: Peter',
|
'description': 'Budget Ticket<br />Attendee: Peter',
|
||||||
'subevent': None,
|
'subevent': None,
|
||||||
'event_date_from': '2017-12-27T10:00:00Z',
|
'event_date_from': '2017-12-27T10:00:00Z',
|
||||||
'event_date_to': None,
|
'event_date_to': '2017-12-27T10:00:00Z',
|
||||||
'period_start': '2017-12-27T10:00:00Z',
|
'period_start': '2017-12-27T10:00:00Z',
|
||||||
'period_end': None,
|
'period_end': '2017-12-27T10:00:00Z',
|
||||||
'event_location': None,
|
'event_location': None,
|
||||||
'fee_type': None,
|
'fee_type': None,
|
||||||
'fee_internal_type': None,
|
'fee_internal_type': None,
|
||||||
@@ -628,9 +628,9 @@ def test_order_create_invoice(token_client, organizer, event, order):
|
|||||||
'description': 'Payment fee',
|
'description': 'Payment fee',
|
||||||
'subevent': None,
|
'subevent': None,
|
||||||
'event_date_from': '2017-12-27T10:00:00Z',
|
'event_date_from': '2017-12-27T10:00:00Z',
|
||||||
'event_date_to': None,
|
'event_date_to': '2017-12-27T10:00:00Z',
|
||||||
'period_start': '2017-12-27T10:00:00Z',
|
'period_start': '2017-12-27T10:00:00Z',
|
||||||
'period_end': None,
|
'period_end': '2017-12-27T10:00:00Z',
|
||||||
'event_location': None,
|
'event_location': None,
|
||||||
'fee_type': "payment",
|
'fee_type': "payment",
|
||||||
'fee_internal_type': None,
|
'fee_internal_type': None,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||||
#
|
#
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from datetime import date, timedelta
|
from datetime import date, datetime, timedelta, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -42,6 +41,7 @@ from django.utils.itercompat import is_iterable
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_countries.fields import Country
|
from django_countries.fields import Country
|
||||||
from django_scopes import scope, scopes_disabled
|
from django_scopes import scope, scopes_disabled
|
||||||
|
from i18nfield.strings import LazyI18nString
|
||||||
|
|
||||||
from pretix.base.invoice import addon_aware_groupby
|
from pretix.base.invoice import addon_aware_groupby
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
@@ -62,7 +62,8 @@ def env():
|
|||||||
with scope(organizer=o):
|
with scope(organizer=o):
|
||||||
event = Event.objects.create(
|
event = Event.objects.create(
|
||||||
organizer=o, name='Dummy', slug='dummy',
|
organizer=o, name='Dummy', slug='dummy',
|
||||||
date_from=now(), plugins='pretix.plugins.banktransfer'
|
date_from=datetime(2024, 12, 1, 9, 0, 0, tzinfo=timezone.utc),
|
||||||
|
plugins='pretix.plugins.banktransfer'
|
||||||
)
|
)
|
||||||
o = Order.objects.create(
|
o = Order.objects.create(
|
||||||
code='FOO', event=event, email='dummy@dummy.test',
|
code='FOO', event=event, email='dummy@dummy.test',
|
||||||
@@ -660,3 +661,154 @@ def test_addon_aware_groupby():
|
|||||||
[True, 102, 3.00],
|
[True, 102, 3.00],
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("period", ["auto", "event_date"])
|
||||||
|
def test_period_from_event_start(env, period):
|
||||||
|
event, order = env
|
||||||
|
event.settings.invoice_period = period
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == event.date_from
|
||||||
|
assert l1.period_end == event.date_from
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("period", ["auto", "event_date"])
|
||||||
|
def test_period_from_event_range(env, period):
|
||||||
|
event, order = env
|
||||||
|
event.date_to = event.date_from + timedelta(days=1)
|
||||||
|
event.settings.invoice_period = period
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == event.date_from
|
||||||
|
assert l1.period_end == event.date_to
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
|
||||||
|
def test_period_from_ticket_validity(env, period):
|
||||||
|
event, order = env
|
||||||
|
p1 = order.positions.first()
|
||||||
|
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
|
||||||
|
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
|
||||||
|
p1.save()
|
||||||
|
event.date_to = event.date_from + timedelta(days=1)
|
||||||
|
event.settings.invoice_period = period
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == p1.valid_from
|
||||||
|
assert l1.period_end == p1.valid_until
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
|
||||||
|
def test_period_from_subevent(env, period):
|
||||||
|
event, order = env
|
||||||
|
event.has_subevents = True
|
||||||
|
event.save()
|
||||||
|
se1 = event.subevents.create(
|
||||||
|
name=event.name,
|
||||||
|
active=True,
|
||||||
|
date_from=datetime((now().year + 1), 7, 31, 9, 0, 0, tzinfo=timezone.utc),
|
||||||
|
date_to=datetime((now().year + 1), 7, 31, 17, 0, 0, tzinfo=timezone.utc),
|
||||||
|
)
|
||||||
|
p1 = order.positions.first()
|
||||||
|
p1.subevent = se1
|
||||||
|
p1.save()
|
||||||
|
event.date_to = event.date_from + timedelta(days=1)
|
||||||
|
event.settings.invoice_period = period
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == se1.date_from
|
||||||
|
assert l1.period_end == se1.date_to
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
|
||||||
|
def test_period_from_memberships(env, period):
|
||||||
|
event, order = env
|
||||||
|
event.date_to = event.date_from + timedelta(days=1)
|
||||||
|
event.settings.invoice_period = period
|
||||||
|
p1 = order.positions.first()
|
||||||
|
membershiptype = event.organizer.membership_types.create(
|
||||||
|
name=LazyI18nString({"en": "Week pass"}),
|
||||||
|
transferable=True,
|
||||||
|
allow_parallel_usage=False,
|
||||||
|
max_usages=15,
|
||||||
|
)
|
||||||
|
customer = event.organizer.customers.create(
|
||||||
|
identifier="8WSAJCJ",
|
||||||
|
email="foo@example.org",
|
||||||
|
name_parts={"_legacy": "Foo"},
|
||||||
|
name_cached="Foo",
|
||||||
|
is_verified=False,
|
||||||
|
)
|
||||||
|
m = customer.memberships.create(
|
||||||
|
membership_type=membershiptype,
|
||||||
|
granted_in=p1,
|
||||||
|
date_start=datetime(2021, 4, 1, 0, 0, 0, 0, tzinfo=timezone.utc),
|
||||||
|
date_end=datetime(2021, 4, 8, 23, 59, 59, 999999, tzinfo=timezone.utc),
|
||||||
|
attendee_name_parts={
|
||||||
|
"_scheme": "given_family",
|
||||||
|
'given_name': 'John',
|
||||||
|
'family_name': 'Doe',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == m.date_start
|
||||||
|
assert l1.period_end == m.date_end
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_period_auto_no_event_from_invoice(env):
|
||||||
|
event, order = env
|
||||||
|
event.settings.invoice_period = "auto_no_event"
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert abs(l1.period_start - now()) < timedelta(seconds=10)
|
||||||
|
assert abs(l1.period_end - now()) < timedelta(seconds=10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_period_always_invoice_date(env):
|
||||||
|
event, order = env
|
||||||
|
p1 = order.positions.first()
|
||||||
|
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
|
||||||
|
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
|
||||||
|
p1.save()
|
||||||
|
event.settings.invoice_period = "invoice_date"
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert abs(l1.period_start - now()) < timedelta(seconds=10)
|
||||||
|
assert abs(l1.period_end - now()) < timedelta(seconds=10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_period_always_event_date(env):
|
||||||
|
event, order = env
|
||||||
|
p1 = order.positions.first()
|
||||||
|
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
|
||||||
|
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
|
||||||
|
p1.save()
|
||||||
|
event.settings.invoice_period = "event_date"
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == event.date_from
|
||||||
|
assert l1.period_end == event.date_from
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_period_always_order_date(env):
|
||||||
|
event, order = env
|
||||||
|
p1 = order.positions.first()
|
||||||
|
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
|
||||||
|
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
|
||||||
|
p1.save()
|
||||||
|
event.settings.invoice_period = "order_date"
|
||||||
|
inv = generate_invoice(order)
|
||||||
|
l1 = inv.lines.first()
|
||||||
|
assert l1.period_start == order.datetime
|
||||||
|
assert l1.period_end == order.datetime
|
||||||
|
|||||||
Reference in New Issue
Block a user