Skip to content

[18.0][FIX] subscription_oca: prevent cron crash and enforce data consistency#1456

Open
rrebollo wants to merge 8 commits into
OCA:18.0from
BinhexTeam:18.0-fix-subscription_oca-open-subscriptions-with-non-recurring_next_date
Open

[18.0][FIX] subscription_oca: prevent cron crash and enforce data consistency#1456
rrebollo wants to merge 8 commits into
OCA:18.0from
BinhexTeam:18.0-fix-subscription_oca-open-subscriptions-with-non-recurring_next_date

Conversation

@rrebollo

@rrebollo rrebollo commented Jun 10, 2026

Copy link
Copy Markdown

Problem

During a database upgrade, a subscription was found with in_progress=True
but no valid recurring_next_date. When the cron job cron_subscription_management()
processed this record, the comparison False <= date.today() raised TypeError,
crashing the entire cron and rolling back all invoices generated in that run.

Root Cause (Potentially)

close_subscription() wrote recurring_next_date = False as a standalone
assignment before the write({'stage_id': ...}) call, creating a split-write
window that left the record in an inconsistent state.

What This Does

  • Consolidates close_subscription() into a single atomic write() call
  • Adds @api.constrains enforcing in_progress=True requires a consistent
    stage type and a valid recurring_next_date
  • Hardens the cron with per-record savepoint isolation so a single failure
    never kills the entire job
  • Adds a domain filter to skip closed subscriptions, tighter to
    pre/in_progress stages only
  • Adds defensive guards on date comparisons to prevent TypeError on
    legacy bad data
  • Aligns test fixtures (sub7/sub8/sub9) with correct in_progress
    semantics (stage type "in_progress")
  • Fixes write() override timing: inject in_progress=False before
    super().write() when target stage is not "in_progress", so the
    constraint sees consistent state during flush

Commits

  1. [FIX] subscription_oca: fix test fixtures and cron error test
    Add stage_in_progress with correct type, update error test for new
    cron behavior.

  2. [FIX] subscription_oca: consolidate close_subscription atomic write
    Merge all three fields into one write() to prevent split-write
    inconsistencies.

  3. [FIX] subscription_oca: add constraint for in_progress consistency
    Enforce in_progress=True requires stage_id.type="in_progress"
    and a valid recurring_next_date.

  4. [FIX] subscription_oca: harden cron with savepoint and domain filter
    Per-record savepoint isolation, domain filter for pre/in_progress
    stages, defensive guards on date comparisons.

  5. [FIX] subscription_oca: set in_progress=False in close_subscription
    Ensure constraint sees consistent state during write().

  6. [FIX] subscription_oca: prevent ValidationError when changing stage via write()
    Inject in_progress=False before super().write() when the target
    stage type is not "in_progress", preventing constraint errors on any
    stage change for in-progress subscriptions.

Testing

All existing tests pass. Updated test_subscription_oca_sub_cron_error
to verify the cron catches and logs exceptions instead of propagating them.

@BinhexTeam T18387

rrebollo and others added 7 commits April 27, 2026 18:55
…tion create

When confirming a sale.order from a CRM opportunity, sale_crm injects
default_tag_ids (containing crm.tag IDs) into the context. Without this fix,
those IDs are applied by default_get() to sale.subscription.tag_ids, causing
a FK constraint violation on sale_subscription_sale_subscription_tag_rel.

Fix: use clean_context() from odoo.tools to strip all default_* keys from
context before calling sale.subscription.create(), following the idiomatic
Odoo core pattern used in res_bank, account_move, hr_expense_sheet, etc.

Adds a regression test that reproduces the production scenario end-to-end.
Add stage_in_progress fixture with type in_progress and assign
it to sub7, sub8, sub9 so their in_progress=True matches a proper
stage type. Update test_subscription_oca_sub_cron_error to verify
the cron catches and logs exceptions instead of propagating them.
Merge recurring_next_date, close_reason_id, and stage_id into a
single write() call to eliminate the split-write window that could
leave recurring_next_date=False with in_progress=True.
Enforce in_progress=True requires stage_id.type==in_progress
and a valid recurring_next_date via @api.constrains.
Add per-record savepoint isolation so a single subscription failure
does not kill the entire cron. Filter to pre/in_progress stages only.
Add defensive guards on recurring_next_date and date comparisons.
Ensure the @api.constrains sees consistent state during write by
including in_progress=False in the write dict, so the constraint
fires before the write() override sets it.
@rrebollo

Copy link
Copy Markdown
Author

Maybe this is related to #1091.

…ia write()

Before super().write(), inject in_progress=False into values when the target stage type is not 'in_progress'. This ensures the @api.constrains validator sees consistent state during flush, preventing ValidationError for any write() that changes stage_id on an in-progress subscription.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants