Skip to content

Commit

Permalink
Improve docs on validating and transactions
Browse files Browse the repository at this point in the history
See: #131
  • Loading branch information
Hugo Osvaldo Barrera committed May 24, 2022
1 parent 10132c8 commit 75044c3
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 15 deletions.
56 changes: 41 additions & 15 deletions django_afip/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,23 +783,44 @@ def validate(self, ticket: AuthTicket = None) -> list[str]:
"""Validate all receipts matching this queryset.
Note that, due to how AFIP implements its numbering, this method is not
thread-safe, or even multiprocess-safe.
thread-safe, or even multiprocess-safe. You MAY however, call this method
concurrently for receipts from different :class:`~.PointOfSales`.
Because of this, it is possible that not all instances matching this
queryset are validated properly. Obviously, only successfully validated
receipts will be updated.
It is possible that not all instances matching this queryset are validated
properly. This method is written in a way that the database will always remain
in a consistent state.
Returns a list of errors as returned from AFIP's webservices. An
exception is not raised because partial failures are possible.
Only successfully validated receipts will marked as such. This method takes care
of saving all changes to database.
Receipts that succesfully validate will have a
:class:`~.ReceiptValidation` object attatched to them with a validation
date and CAE information.
Returns a list of errors as returned from AFIP's webservices. When AFIP returns
a failure response, an exception is not raised because partial failures are
possible. Network issues (e.g.: DNS failure) _will_ raise an exception.
Receipts that successfully validate will have a :class:`~.ReceiptValidation`
object attached to them with a validation date and CAE information.
Already-validated receipts are ignored.
Attempting to validate an empty queryset will simply return an empty
list.
This method takes the following steps:
- Assigns numbers to all receipts.
- Saves the assigned numbers to the database.
- Sends the receipts to AFIP.
- Saves the results into the local DB.
Should execution be interrupted (e.g.: a power failure), receipts will have been
saved with their number. In this case, the ``revalidate`` method should be used,
to determine if they have been registered by AFIP, or if the interruption
happened before sending them.
This method MUST NOT be called inside a database transaction; doing so risks
leaving the database in an inconsistent state should there be any fatal
interruptions. In particular, the numbers will not have been saved, so it would
be impossible to recover from the incomplete operation.
"""
# Skip any already-validated ones:
qs = self.filter(validation__isnull=True).check_groupable()
Expand Down Expand Up @@ -938,6 +959,12 @@ class Receipt(models.Model):
If the taxpayer has taxes or pays VAT, you need to attach :class:`~.Tax`
and/or :class:`~.Vat` instances to the Receipt.
Application code SHOULD NOT set the `receipt_number` code. It will be set by
:meth:`~.Receipt.validate()` internally. When writing code outside `django-afip`,
this should be considered read-only. The sole exception is importing
previously-validated receipts from another database.
`
"""

point_of_sales = models.ForeignKey(
Expand Down Expand Up @@ -1116,13 +1143,12 @@ def is_validated(self) -> bool:
def validate(self, ticket: AuthTicket = None, raise_=False) -> list[str]:
"""Validates this receipt.
This is a shortcut to :class:`~.ReceiptQuerySet`'s method of the same
name. Calling this validates only this instance.
This is a shortcut to :meth:`~.ReceiptQuerySet.validate`. See the documentation
for that method for details. Calling this validates only this instance.
:param AuthTicket ticket: Use this ticket. If None, one will be loaded
or created automatically.
:param bool raise_: If True, an exception will be raised when
validation fails.
:param ticket: Use this ticket. If None, one will be loaded or created
automatically.
:param raise_: If True, an exception will be raised when validation fails.
"""
# XXX: Maybe actually have this sortcut raise an exception?
rv = Receipt.objects.filter(pk=self.pk).validate(ticket)
Expand Down
8 changes: 8 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,11 @@ use a combination missing from the CI run matrix, feel free to reach out.
sqlite should work, and is only supported with the latest Python and latest
Django. sqlite should only by used for prototypes, demos, or example projects,
as it has not been tested for production-grade reliability.

Transactions
............

Generally, avoid calling network-related methods within a transaction. The
implementation has assumptions that they **are not** called during a request
cycle. Assumptions are made about this, and `django-afip` handles transactions
internally to keep data consistent at all times.

0 comments on commit 75044c3

Please sign in to comment.