[PM-37514] Support Teams 2019 Migration#7864
Conversation
…on logic comments
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7864 +/- ##
==========================================
+ Coverage 61.21% 61.23% +0.01%
==========================================
Files 2219 2220 +1
Lines 98052 98102 +50
Branches 8846 8852 +6
==========================================
+ Hits 60020 60068 +48
- Misses 35917 35918 +1
- Partials 2115 2116 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the addition of the Teams 2019 → Teams Current pricing-migration paths (Monthly and Annual) to the deferred price-increase engine. The change factors the Packaged-source detection and billed-seat formula into shared Code Review DetailsNo blocking findings. Verified during review:
|
e8b968f to
4868430
Compare
…r.cs Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
|



🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-37514
📔 Objective
Adds the Teams 2019 → Teams Current pricing-migration paths (Monthly and Annual) to the deferred price-increase migration engine (epic PM-37496).
"Teams 2019 Packaged" is a flat base covering 5 included seats, plus a per-seat overage for seats beyond 5. Current Teams is pure per-seat.
Unlike Teams Starter (a flat bundle), Teams 2019 carries a per-seat overage line, so
HasNonSeatBasedPasswordManagerPlan()is false for it — that distinction drives most of the changes below.Code changes
Teams2019AnnualToCurrent/Teams2019MonthlyToCurrent(append-onlyMigrationPathIds 9, 10; 7–8 reserved for the in-flight Teams Starter paths). AddsSeatCountPolicy { Preserve, ActualUsage }toMigrationPath; Teams 2019 usesActualUsage, all other paths default toPreserve.PlanMigrationExtensions) — the packaged-source rule and the seat formula are factored into two extensions so the scheduler, the renewal quote, and renewal activation can't drift:IsPackagedMigrationSource(plan, policy)— a source is Packaged when it's a flat bundle (HasNonSeatBasedPasswordManagerPlan()) or its path isActualUsage. Replaces the same expression previously duplicated acrossPriceIncreaseScheduler,UpcomingInvoiceHandler, andSubscriptionUpdatedHandler.ResolveMigratedSeatCount(plan, occupied, purchased)— the single billed-seat formula:Max(1, occupied)for a flat bundle, otherwiseoccupied < base ? occupied : purchased. Guards its inputs and throwsArgumentExceptionfor a Scalable source (BaseSeats == 0), which must preserve its line-item quantity rather than resolve from usage.OrganizationPlanMigrationPriceMapper) — a guarded Packaged → Scalable case maps the flat base price (StripePlanId) to the target per-seat price; the not-null guard keeps anull == nullmisfire off Scalable sources.PriceIncreaseScheduler) — for a Packaged source, the base line and the seat-overage line (both map to the same target seat price) collapse into a single seat line billed atResolveMigratedSeatCount. Storage and other items pass through unchanged.UpcomingInvoiceHandler) — reusesBusinessPlanRenewal2020MigrationMail; the quoted seat count comes from the sameResolveMigratedSeatCount, so it matches what Phase 2 bills (occupied for < 5, purchased for ≥ 5).SubscriptionUpdatedHandler) — source detection usesIsPackagedMigrationSourceand selects the base price for packaged sources (so a sub-5 Teams 2019 org, which has no overage line, is still recognized when the schedule advances). Theorganization.Seatsreconciliation (PM-39562, [PM-39562] fix: Reconcile org seats to billed quantity on packaged-plan migration #7869) also fires for Teams 2019 — reconcilingSeatsto the billed quantity.Notes for reviewers
MaxAutoscaleSeatsis preserved by construction —ChangePlandoesn't touch it and the reconciliation writes onlySeats.ResolveMigratedSeatCountguard — Scalable sources (Preservepolicy) never reach the extension via the call-site gate today; theArgumentExceptionis a backstop so a future caller or a misconfiguredActualUsagepath fails loudly instead of silently billing the wrong quantity.📸 Screenshots
Incoming!