mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Pluggable invoice transmission methods (#5020)
* Flexible invoice transmission
* UI work
* Add peppol and output
* API support
* Profile integration
* Simplify form for individuals
* Remove sent_to_customer usage
* more steps
* Revert "Bank transfer: Allow to send the invoice direclty to the accounting department (#2975)"
This reverts commit cea6c340be.
* minor fixes
* Fixes after rebase
* update stati
* Backend view
* Transmit and show status
* status, retransmission
* API retransmission
* More fields
* API docs
* Plugin docs
* Update migration
* Add missing license headers
* Remove dead code, fix current tests
* Run isort
* Update regex
* Rebase migration
* Fix migration
* Add tests, fix bugs
* Rebase migration
* Apply suggestion from @luelista
Co-authored-by: luelista <weller@rami.io>
* Apply suggestion from @luelista
Co-authored-by: luelista <weller@rami.io>
* Apply suggestion from @luelista
Co-authored-by: luelista <weller@rami.io>
* Apply suggestion from @luelista
Co-authored-by: luelista <weller@rami.io>
* Apply suggestion from @luelista
Co-authored-by: luelista <weller@rami.io>
* Make migration reversible
* Add TransmissionType.enforce_transmission
* Fix registries API usage after rebase
* Remove code I forgot to delete
* Update transmission status display depending on type
* Add testmode_supported
* Update src/pretix/static/pretixbase/js/addressform.js
Co-authored-by: luelista <weller@rami.io>
* Update src/pretix/static/pretixbase/js/addressform.js
Co-authored-by: luelista <weller@rami.io>
* Update src/pretix/static/pretixbase/js/addressform.js
Co-authored-by: luelista <weller@rami.io>
* New mechanism for non-required invoice forms
* Update src/pretix/base/invoicing/transmission.py
Co-authored-by: luelista <weller@rami.io>
* Declare testmode_supported for email
* Make transmission_email_other an implementation detail
* Fix failing tests and add new ones
* Update src/pretix/base/services/invoices.py
Co-authored-by: luelista <weller@rami.io>
* Add emails to email history
* Fix comma error
* More generic default email text
* Cleanup
* Remove "email invoices" button and refine logic
* Rebase migration
* Fix edge case
---------
Co-authored-by: luelista <weller@rami.io>
This commit is contained in:
246
src/pretix/base/invoicing/transmission.py
Normal file
246
src/pretix/base/invoicing/transmission.py
Normal file
@@ -0,0 +1,246 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from typing import Optional
|
||||
|
||||
from django_countries.fields import Country
|
||||
|
||||
from pretix.base.models import Invoice, InvoiceAddress
|
||||
from pretix.base.signals import EventPluginRegistry, Registry
|
||||
|
||||
|
||||
class TransmissionType:
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
"""
|
||||
A short and unique identifier for this transmission type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def verbose_name(self) -> str:
|
||||
"""
|
||||
A human-readable name for this transmission type to be shown internally in the backend.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def public_name(self) -> str:
|
||||
"""
|
||||
A human-readable name for this transmission type to be shown to the public.
|
||||
By default, this is the same as ``verbose_name``
|
||||
"""
|
||||
return self.verbose_name
|
||||
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
"""
|
||||
Returns a priority that is used for sorting transmission type. Higher priority means higher up in the list.
|
||||
Default to 100. Providers with same priority are sorted alphabetically.
|
||||
"""
|
||||
return 100
|
||||
|
||||
@property
|
||||
def exclusive(self) -> bool:
|
||||
"""
|
||||
If a transmission type is exclusive, no other type can be chosen if this type is
|
||||
available. Use e.g. if a certain transmission type is legally required in a certain
|
||||
jurisdiction.
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def enforce_transmission(self) -> bool:
|
||||
"""
|
||||
If a transmission type enforces transmission, every invoice created with this type will be transferred.
|
||||
If not, the backend user is in some cases trusted to decide whether or not to transmit it.
|
||||
"""
|
||||
return False
|
||||
|
||||
def is_available(self, event, country: Country, is_business: bool) -> bool:
|
||||
providers = transmission_providers.filter(type=self.identifier, active_in=event)
|
||||
return any(
|
||||
provider.is_available(event, country, is_business)
|
||||
for provider, _ in providers
|
||||
)
|
||||
|
||||
def invoice_address_form_fields_required(self, country: Country, is_business: bool):
|
||||
return set()
|
||||
|
||||
def invoice_address_form_fields_visible(self, country: Country, is_business: bool) -> set:
|
||||
return set(self.invoice_address_form_fields.keys())
|
||||
|
||||
def validate_address(self, ia: InvoiceAddress):
|
||||
pass
|
||||
|
||||
@property
|
||||
def invoice_address_form_fields(self) -> dict:
|
||||
"""
|
||||
Return a set of form fields that **must** be prefixed with ``transmission_<identifier>_``.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def form_data_to_transmission_info(self, form_data: dict) -> dict:
|
||||
return form_data
|
||||
|
||||
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
|
||||
return transmission_info
|
||||
|
||||
|
||||
class TransmissionProvider:
|
||||
"""
|
||||
Base class for a transmission provider. Should NOT hold internal state as the class is only
|
||||
instantiated once and then shared between events and organizers.
|
||||
"""
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""
|
||||
A short and unique identifier for this transmission provider.
|
||||
This should only contain lowercase letters and underscores.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
Identifier of the transmission type this provider provides.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
"""
|
||||
A human-readable name for this transmission provider (can be localized).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def testmode_supported(self) -> bool:
|
||||
"""
|
||||
Whether testmode invoices may be passed to this provider.
|
||||
"""
|
||||
return False
|
||||
|
||||
def is_ready(self, event) -> bool:
|
||||
"""
|
||||
Return whether this provider has all required configuration to be used in this event.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_available(self, event, country: Country, is_business: bool) -> bool:
|
||||
"""
|
||||
Return whether this provider may be used for an invoice for the given recipient country and address type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def transmit(self, invoice: Invoice):
|
||||
"""
|
||||
Transmit the invoice. The invoice passed as a parameter will be in status ``TRANSMISSION_STATUS_INFLIGHT``.
|
||||
Invoices that stay in this state for more than 24h will be retried automatically. Implementations are expected to:
|
||||
|
||||
- Send the invoice.
|
||||
|
||||
- Update the ``transmission_status`` to `TRANSMISSION_STATUS_COMPLETED` or `TRANSMISSION_STATUS_FAILED`
|
||||
after sending, as well as ``transmission_info`` with provider-specific data, and ``transmission_date`` to
|
||||
the date and time of completion.
|
||||
|
||||
- Create a log entry of action type ``pretix.event.order.invoice.sent`` or
|
||||
``pretix.event.order.invoice.sending_failed`` with the fields ``full_invoice_no``, ``transmission_provider``,
|
||||
``transmission_type`` and a provider-specific ``data`` field.
|
||||
|
||||
Make sure to either handle ``invoice.order.testmode`` properly or set ``testmode_supported`` to ``False``.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
"""
|
||||
Returns a priority that is used for sorting transmission providers. Higher priority will be chosen over
|
||||
lower priority for transmission. Default to 100.
|
||||
"""
|
||||
return 100
|
||||
|
||||
def settings_url(self, event) -> Optional[str]:
|
||||
"""
|
||||
Return a URL to the settings page of this provider (if any).
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class TransmissionProviderRegistry(EventPluginRegistry):
|
||||
def __init__(self):
|
||||
super().__init__({
|
||||
'identifier': lambda o: getattr(o, 'identifier'),
|
||||
'type': lambda o: getattr(o, 'type'),
|
||||
})
|
||||
|
||||
def register(self, *objs):
|
||||
for obj in objs:
|
||||
if not isinstance(obj, TransmissionProvider):
|
||||
raise TypeError('Entries must be derived from TransmissionProvider')
|
||||
|
||||
if obj.type == "email" and not obj.__module__.startswith('pretix.base.'):
|
||||
raise TypeError('No custom providers for email allowed')
|
||||
|
||||
return super().register(*objs)
|
||||
|
||||
|
||||
class TransmissionTypeRegistry(Registry):
|
||||
def __init__(self):
|
||||
super().__init__({
|
||||
'identifier': lambda o: getattr(o, 'identifier'),
|
||||
})
|
||||
|
||||
def register(self, *objs):
|
||||
for obj in objs:
|
||||
if not isinstance(obj, TransmissionType):
|
||||
raise TypeError('Entries must be derived from TransmissionType')
|
||||
|
||||
if not obj.__module__.startswith('pretix.base.'):
|
||||
raise TypeError('Plugins are currently not allowed to add transmission types')
|
||||
|
||||
return super().register(*objs)
|
||||
|
||||
|
||||
"""
|
||||
Registry for transmission providers.
|
||||
|
||||
Each entry in this registry should be an instance of a subclass of ``TransmissionProvider``.
|
||||
They are annotated with their ``identifier``, ``type``, and the defining ``plugin``.
|
||||
"""
|
||||
transmission_providers = TransmissionProviderRegistry()
|
||||
|
||||
|
||||
"""
|
||||
Registry for transmission types.
|
||||
|
||||
Each entry in this registry should be an instance of a subclass of ``TransmissionType``.
|
||||
They are annotated with their ``identifier``.
|
||||
"""
|
||||
transmission_types = TransmissionTypeRegistry()
|
||||
|
||||
|
||||
def get_transmission_types():
|
||||
return sorted(
|
||||
transmission_types.registered_entries.keys(),
|
||||
key=lambda t: (-t.priority, str(t.public_name)),
|
||||
)
|
||||
Reference in New Issue
Block a user