django-plans changelog
======================

2.1.9
-----

* **Bug Fixes**: Fixed ``Order.return_order`` leaving ``UserPlan.expire`` later than the user's pre-order state when refunding an order completed against an expired (or ``expire=None``) plan. From an expired state ``extend_account`` advances ``expire`` to ``today + pricing.period``, but ``reduce_account`` rewound by exactly ``pricing.period``, so refunds kept the days the extension rolled over the expired gap. ``complete_order`` now snapshots ``UserPlan.expire``/``active`` on the ``Order`` (migration 0018) and ``return_order`` rewinds by the actual extension delta. Legacy orders without a snapshot keep the previous behaviour.
* **Tests**: Added tests for the expired-plan refund regression and for stacked refunds across multiple orders.

2.1.8
-----

* **Bug Fixes**: Fixed ``select_for_update().get()`` in ``complete_order()`` and ``return_order()`` crashing with ``UserPlan.DoesNotExist`` when the Order's user has no ``UserPlan`` row in the database (e.g. in test setups where baker creates an Order with ``user__userplan=baker.make("UserPlan")``). The lock now uses ``filter().first()`` and skips ``refresh_from_db()`` when no row is found, preserving concurrency protection while not introducing crash paths that didn't exist before the locking was added in 2.1.7.
* **Tests**: Added test for ``complete_order()`` when the ``UserPlan`` row is linked to a different user than the Order's user (simulating baker's double-underscore reverse relation behavior).

2.1.7
-----

* **Bug Fixes**: **CRITICAL**: Fixed race condition in ``complete_order()`` and ``return_order()`` when multiple payments for the same user are processed concurrently (e.g. duplicate payment webhooks). Without row-level locking on ``UserPlan``, concurrent transactions read stale ``expire``/``plan`` values (PostgreSQL READ COMMITTED isolation), causing plan extensions not to stack and refunds to over-subtract — potentially cancelling a valid subscription. Both methods now acquire ``select_for_update()`` on the ``UserPlan`` row before modifying it, serializing concurrent access per-user. Also wraps ``return_order()`` in ``@transaction.atomic()`` to properly scope the lock.
* **Tests**: Added concurrent ``complete_order`` and ``return_order`` tests using threads and ``TransactionTestCase`` to verify that extensions stack correctly and refund reductions are accurate under concurrent execution.

2.1.6
-----

* **Bug Fixes**: Fixed timezone inconsistency in date calculations when ``USE_TZ=True`` with a non-UTC system timezone. Methods ``is_expired()``, ``days_left()``, ``get_plan_extended_from()``, ``extend_account()``, invoice creation, and the ``expire_account`` task all used ``date.today()`` (system-local date) instead of ``timezone.now().date()`` (UTC date). Near midnight UTC, this caused off-by-one-day errors in plan expiry checks, proration calculations, and billing period boundaries. All date references now consistently use ``django.utils.timezone.now().date()``.
* **Tests**: Added regression tests verifying all date-dependent UserPlan methods use ``timezone.now()`` instead of ``date.today()``.

2.1.4
-----

* **Bug Fixes**: Fixed ``cancel_invoice()`` and ``create_partial_credit_note()`` crashing with ``TypeError`` when subclasses set ``unit_price_net``, ``item_description``, or ``rebate`` to ``None``. These fields are now guarded with ``is not None`` checks, allowing subclasses that use line items (instead of a single unit price) to create credit notes via the parent class methods without overriding them.
* **Tests**: Added tests for credit note creation with nullable fields to ensure subclass compatibility, plus a regression test verifying standard behavior is unchanged.

2.1.3
-----

* **Bug Fixes**: **CRITICAL**: Fixed VIES VAT validation crashing when EU service returns ``TransportError`` (e.g., CloudFront 403) or ``XMLSyntaxError`` (e.g., HTML error page instead of WSDL). These exceptions were imported but not caught in the VIES exception handler, causing user checkout failures. The system now gracefully falls back to country-based VAT rates and displays a user-friendly warning message when VIES validation fails.
* **Bug Fixes**: Fixed nested anchor tags in ``OrderedModelAdmin`` changelist for ``QuotaAdmin`` and ``PlanAdmin``. Changed ``list_display_links`` to only include the first 2 columns instead of all columns, ensuring HTML5 compliance and preventing invalid nested ``<a>`` tags.
* **Tests**: Added unit tests for ``TransportError`` and ``XMLSyntaxError`` handling in VIES validation to ensure proper fallback behavior.

2.1.2
-----

* **Bug Fixes**: **CRITICAL**: Fixed TEDB client crashing when EU service returns HTML error page instead of WSDL XML (``XMLSyntaxError``). The client now gracefully falls back to static VAT rates when the TEDB service is temporarily unavailable, preventing user request failures.
* **Tests**: Added tests for ``XMLSyntaxError`` handling and fallback mechanism when TEDB service returns HTML error pages.

2.1.1
-----

* **Bug Fixes**: Fixed TEDB client to correctly filter regional VAT rates (e.g., Spain was returning 7% for Canary Islands instead of 21% for mainland). The client now prioritizes rates without territorial comments to return general/mainland rates. Removed debug print statement from TEDB client.
* **Tests**: Added comprehensive unit tests for regional VAT rate filtering logic, documenting Spain, France, and decimal normalization behaviors.

2.1.0
-----

* **Autorenew Schedule Enhancement**: Introduced flexible scheduling mechanism via ``PLANS_AUTORENEW_SCHEDULE`` setting for automatic account renewals. Deprecated legacy ``PLANS_AUTORENEW_BEFORE_DAYS`` and ``PLANS_AUTORENEW_BEFORE_HOURS`` settings with warnings. Enhanced autorenewal logic and expanded test coverage for robust behavior. Added ``--dry-run`` option and ``PLANS_AUTORENEW_MAX_DAYS_AFTER_EXPIRY`` setting to the ``autorenew_accounts`` management command.
* **VAT Rates Integration**: Added TEDB (Taxes in Europe Database) integration for real-time EU VAT rates with automatic 24-hour caching and fallback to updated static rates table. Updated VAT rates for Estonia (24%), Finland (25.5%), Slovakia (23%), and Romania (21%) to reflect 2024-2025 changes.
* **Invoice Credit Notes and Cancellation**: Added credit note invoice type with support for full and partial credit notes. Added admin actions to cancel invoices (creating credit notes) and create partial credit notes with custom amounts. Credit notes automatically link to original invoices and preserve payment dates. Orders are automatically returned when invoices are cancelled.
* **Plan Slug Field**: Added unique slug field to Plan model for URL-friendly identifiers. Includes automatic slug generation from plan names with duplicate handling and comprehensive test coverage. **WARNING**: Migration creates unique index synchronously and can lock PostgreSQL tables with large datasets - see migration comments for safe production deployment steps.
* **InvoiceDetailView Enhancement**: Added custom 404 page for invoice access with helpful messaging for multi-account users, explaining they need to log in with the correct account to access their invoices.
* **Exception Handling**: Add an option to catch and log exceptions during automatic account renewal, with an option to notify admins by email. Configurable via ``--catch-exceptions`` in the ``autorenew_accounts`` management command.
* **Dependencies**: Use zeep instead of suds. Updated swapper from 1.3.0 to 1.4.0. Updated django-extensions requirement from <3.2.0 to <4.2.0.
* **Bug Fixes**: Fixed TEDB API integration and updated Estonia VAT rate to 24%. Fixed backward compatible Decimal serialization for VAT rates. Fixed payment_date handling when generating credit notes and invoices. Fixed local imports to reduce circular dependencies. Fixed migration issue #192: 'no such column: plans_plan.updated_at'.
* **Documentation Fixes**: Fixed misleading installation instruction comment to correctly state installation is from PyPI (fixes #185). Added translatable 'Plans' header to plan table template for proper internationalization (fixes #191).

2.0.0
-----

* Add support for Django 4.2 - 5.2
* Add ``--throttle`` parameter to the ``autorenew_accounts`` management command
* Refactor tax calculation logic in AbstractOrder.recalculate

1.2.0
-----

* make possible to reuse get_change_price()

1.1.0
-----

* Add `AbstractRecurringUserPlan.renewal_triggered_by`
* Use `AbstractRecurringUserPlan.renewal_triggered_by` instead of `has_automatic_renewal`; `has_automatic_renewal` is not used anywhere anymore; `has_automatic_renewal=True` is automatically migrated to `renewal_triggered_by=TASK` and `has_automatic_renewal=False` to `renewal_triggered_by=USER`
* Rename `AbstractRecurringUserPlan.has_automatic_renewal` to `_has_automatic_renewal_backup_deprecated` so it can be used make your own data migration from the former `has_automatic_renewal` to `renewal_triggered_by` if the default one does not work for you
* Add `AbstractRecurringUserPlan.has_automatic_renewal` property that issues a deprecation warning and uses `renewal_triggered_by` under the hood
* Add `renewal_triggered_by` parameter to `AbstractUserPlan.set_plan_renewal`
* Deprecate `AbstractRecurringUserPlan.has_automatic_renewal`; use `AbstractRecurringUserPlan.renewal_triggered_by` instead
* Deprecate `AbstractRecurringUserPlan._has_automatic_renewal_backup_deprecated`; use `AbstractRecurringUserPlan.renewal_triggered_by` instead
* Deprecate `has_automatic_renewal` parameter of `AbstractUserPlan.set_plan_renewal`; use `renewal_triggered_by` instead
* Deprecate `None` value of `renewal_triggered_by` parameter of `AbstractUserPlan.set_plan_renewal`; use an `AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY` instead

1.0.7
-----
* Add `AbstractOrder.return_order()`

1.0.6
-----

* Simplify the query generated by get_initial_number

1.0.5
-----

* Re-use old RecurringUserPlan in set_plan_renewal() to retain ID and history
* `autorenew_accounts` management command: better output, --providers option
* Blackify whole source

1.0.3
-----

* Prevent duplicated invoice creation when concurrent Order.complete_order() are callled
* Test in Python 3.11

1.0.2
-----

* Accidentaly same as 1.0.1

1.0.1
-----

* Handle SAXParseException when fetching VAT ID

1.0.0
-----

* Transform BillingInfo create and update view into one BillingInfoCreateOrUpdateView. This will require implementors to change these views if they were overriden. Also if one of the `billing_info_create.html` or `billing_info_update.html` templates has been overriden, he has to transform the code into the new `billing_info_create_or_update.html` template.
* Celery dependency was removed. Now the implementor has to connect the `plans.tasks.autorenew_account` `plans.tasks.expire_account` tasks by himself or use management command.
* Disable particular e-mails from system through configuration: `PLANS_SEND_EMAILS_NO_SEND_TYPES`, `PLANS_SEND_EMAILS_PLAN_CHANGED`, `PLANS_SEND_EMAILS_PLAN_EXTENDED`
* If e-mail template with `.html` appendix, send HTML e-mail.
* Add basic recurring payments support
* Added `expire_accounts` management command as an alternative to Celery task
* Added free plan support, fix expired quotas.
* It is now ensured, that we have only one default migration.
* Use parameters to compose error messages in validators.
* Fix problem with changed `AUTH_USER_MODEL` and user link in `PlanAdmin`.
* Determine country by IP address in billing info, if geolite2 is installed
* Added possibility to define `PLANS_VALIDATORS` as lazy imported string.
* Supporting Django 2.1
* The range when the plan was extended is now stored in `Order.plan_extended_from` and `Order.plan_extended_until`.
* The fron/until extension range is shown to the user during order confirmation
* UserPlans are now automatically created with initial migration or with `create_userplans` adminaction.
* Added possibility to define `PLANS_APP_VERBOSE_NAME` to personalize plans' `verbose_name`
* Return back to order after setting up billing info

0.8.13
------

* Supporting Django 2.0.6.
1.2.0 (unreleased)
* New VAT standard rates 2017
* Remove dependency on `django.contrib.sites`.
* Feature #53: Cleaner and more explicit exception handling on `Plan.get_default_plan `.
* Feature #59: Adding code coverage to code base.
* Support customised user models by using `django.contrib.auth.get_user_model()`.
* Add Database migrations.


0.7
---

* Changes in plans.taxation.eu.EUTaxationPolicy to implement new EU VAT regulations (MOSS)
* Clean up settings variables naming conventions prepending PLANS_ prefix:
  * Renamed settings variable name TAXATION_POLICY to PLANS_TAXATION_POLICY
  * Renamed settings variable name ISSUER_DATA to PLANS_INVOICE_ISSUER
  * Renamed settings variable name PLAN_EXPIRATION_REMIND to PLANS_EXPIRATION_REMIND
  * Renamed settings variable name PLAN_CHANGE_POLICY to PLANS_CHANGE_POLICY
  * Renamed settings variable name PLAN_VALIDATORS to PLANS_VALIDATORS
  * Renamed settings variable name CURRENCY to PLANS_CURRENCY
  * Renamed settings variable name TAX to PLANS_TAX
  * Renamed settings variable name TAX_COUNTRY to PLANS_TAX_COUNTRY
  * Renamed settings variable name INVOICE_LOGO_URL to PLANS_INVOICE_LOGO_URL
  * Renamed settings variable name INVOICE_NUMBER_FORMAT to PLANS_INVOICE_NUMBER_FORMAT
  * Renamed settings variable name INVOICE_TEMPLATE to PLANS_INVOICE_TEMPLATE
  * Renamed settings variable name INVOICE_COUNTER_RESET to PLANS_INVOICE_COUNTER_RESET
  * Renamed settings variable name ORDER_EXPIRATION to PLANS_ORDER_EXPIRATION
  * Renamed settings variable name PLAN_DEFAULT_GRACE_PERIOD to PLANS_DEFAULT_GRACE_PERIOD



0.6+
----

* Changing `QuotaValidator` API. `ModelCountValidator` requires now to give `add` argument only as a kwarg.
* Adding support for defining URL for plan, quota and pricing period that will act as a clickable pricing
  table header (requires schema migration)
* Rename settings variable PLAN_ACTIVATION_VALIDATORS to PLAN_VALIDATORS
* Major refactoring of Validators API providing new feature - required_to_activate False/True for validators
* updating dependency to django-countries>=2.0 (fixes #29)
* support for django1.6 (fixes #28)
* complete demo application using Boostrap v3 for cool look
* adding missing default templates
* refactored login_required decorator usage (issue #20)
* taxation policies are moved from locale directory (issue #13) warning: backward incompatible!
* dependencies (apart from suds) are reviewed and refactored in setup.py (issue #9)

v.0.5 - v0.6
------------

[...] (FIXME: write history changelog from git commits

v.0.4
-----

* Migrating to django-countries. Requires schema migration.

v.0.3
-----

* Change plan policy - custom action how to billing change plan (downgrade/upgrade) can be implemented
* Taxation policy - custom action how to calculate tax can be implemented
* Dropping south migrations, it should be managed now as a project dependent migrations via SOUTH_MIGRATION_MODULES

v. 0.1.1
--------

* Added field default to Plans model - it means that this plan is supposed to be added to every new user. Via south migration 0002
* Added South migrations
