Skip to content

[PM-37512] feat: Add Teams Starter to current pricing migration path#7827

Open
amorask-bitwarden wants to merge 3 commits into
mainfrom
billing/PM-37512/support-migration-of-teams-starter
Open

[PM-37512] feat: Add Teams Starter to current pricing migration path#7827
amorask-bitwarden wants to merge 3 commits into
mainfrom
billing/PM-37512/support-migration-of-teams-starter

Conversation

@amorask-bitwarden

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-37512

📔 Objective

Adds the Teams Starter → Teams Current pricing-migration path (epic PM-37496), covering both TeamsStarter and TeamsStarter2023 source plans migrating to TeamsMonthly. Teams Starter is a flat, packaged 10-seat bundle, so this is the migration engine's first Packaged → Scalable conversion.

  • Paths & ids: registers TeamsStarterToCurrent / TeamsStarter2023ToCurrent with append-only MigrationPathIds (5, 6), guarded by the snapshot test.
  • Price mapping: a guarded Packaged → Scalable case maps the flat base price (StripePlanId) to the target per-seat price (StripeSeatPlanId); the !IsNullOrEmpty guard prevents a null == null misfire for Scalable sources.
  • Seat quantity: the packaged bundle bills at quantity ~1, so the Phase-2 seat quantity is overridden to the org's occupied seat count (GetOccupiedSeatCountByOrganizationIdAsync(...).Total) — applied unconditionally for packaged sources, floored at 1.
  • Routing: ProductTierType.TeamsStarter added to the business-migration tier gates in both PriceIncreaseScheduler and UpcomingInvoiceHandler.
  • Renewal email: reuses BusinessPlanRenewal2020MigrationMail; ResolveSeatCount now quotes the occupied count for packaged sources instead of the bundle cap.
  • Secrets Manager: no production changes — the existing migration-grace machinery grandfathers TeamsStarter2023's larger SM service-account baseline automatically (grace 30); plain TeamsStarter matches the target (grace 0). Covered by tests.

No DB migration (enum-only, append-only); no new feature flag (rides PM35215_BusinessPlanPriceMigration).

Notes for reviewers

  • PM-37514 (Teams 2019): the new mapper case is generic and will also serve Teams 2019's packaged base price when those paths land. Teams Starter's override is intentionally unconditional; the <5 threshold is owned by PM-37514, so any future shared SeatCountPolicy must preserve Teams Starter's unconditional behavior.
  • Renewal-email copy: whether Teams Starter needs distinct copy is an open product question (tracked separately); it would be a small conditional, not a new template.

@amorask-bitwarden amorask-bitwarden added the ai-review Request a Claude code review label Jun 17, 2026
Teams Starter is a flat 10-seat bundle; migrating to per-seat Teams Current bills the org's actual occupied seats. Secrets Manager service accounts are preserved by the existing migration-grace machinery.
@amorask-bitwarden amorask-bitwarden force-pushed the billing/PM-37512/support-migration-of-teams-starter branch from 95ab190 to cc58c11 Compare June 17, 2026 13:58
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the Teams Starter → Teams Current pricing-migration path (PM-37512), covering the new Packaged → Scalable conversion. The change registers two append-only migration paths, adds a guarded base-price mapper case, overrides Phase-2 seat quantity to the org's occupied seat count for packaged sources, and extends the business-migration tier gates in PriceIncreaseScheduler and UpcomingInvoiceHandler. The logic is well-guarded, internally consistent across the scheduling and migration flows, and backed by comprehensive unit tests.

Code Review Details

No findings. Verified the following:

  • The new Packaged → Scalable mapper case is guarded by !IsNullOrEmpty(source.PasswordManager.StripePlanId), preventing a null == null misfire for Scalable sources; the existing seat/storage/SM slot cases remain reachable and correctly mapped.
  • The Phase-2 seat-quantity override applies only to lines resolving to the target seat price, is floored at 1, and is skipped for Scalable sources — confirmed by the non-starter boundary test.
  • ResolveSeatCountAsync correctly quotes the occupied seat count (not the bundle cap) for packaged sources in the renewal email.
  • The shared MapOrPassThrough usage in UpdateOrganizationSubscriptionCommand is unaffected: same-instance short-circuit plus already-translated post-migration phase items prevent the new case from misfiring.
  • Migration path enum values are append-only and snapshot-guarded; tier gates updated consistently in both routing locations.

@amorask-bitwarden amorask-bitwarden marked this pull request as ready for review June 17, 2026 14:15
@amorask-bitwarden amorask-bitwarden requested a review from a team as a code owner June 17, 2026 14:15
@amorask-bitwarden amorask-bitwarden marked this pull request as draft June 17, 2026 14:15
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.21%. Comparing base (51192df) to head (7a5afd0).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7827      +/-   ##
==========================================
+ Coverage   61.20%   61.21%   +0.01%     
==========================================
  Files        2209     2209              
  Lines       97732    97765      +33     
  Branches     8813     8817       +4     
==========================================
+ Hits        59812    59847      +35     
+ Misses      35796    35795       -1     
+ Partials     2124     2123       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@amorask-bitwarden amorask-bitwarden marked this pull request as ready for review June 22, 2026 16:03
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant