@@ -556,8 +556,7 @@ await stripeAdapter.UpdateSubscriptionScheduleAsync(schedule.Id,
556556 // Teams Starter and Teams 2019 are Packaged sources whose base line (and, for Teams 2019, the
557557 // seat-overage line) collapse onto the Scalable target's single seat price at a usage-resolved
558558 // quantity. Scalable sources preserve their line-item quantities.
559- var isPackagedSourcePlan = sourcePlan . HasNonSeatBasedPasswordManagerPlan ( ) ||
560- migrationPath . SeatCountPolicy == SeatCountPolicy . ActualUsage ;
559+ var isPackagedSourcePlan = sourcePlan . IsPackagedMigrationSource ( migrationPath . SeatCountPolicy ) ;
561560
562561 var items = new List < SubscriptionSchedulePhaseItemOptions > ( ) ;
563562 foreach ( var item in subscription . Items . Data )
@@ -619,27 +618,16 @@ await stripeAdapter.UpdateSubscriptionScheduleAsync(schedule.Id,
619618 }
620619
621620 /// <summary>
622- /// Resolves the billed Phase 2 seat count for a Packaged source. Teams Starter (flat bundle)
623- /// bills occupied seats, floored at 1; Teams 2019 (seat overage) bills occupied seats below the
624- /// base and the purchased count at or above it.
621+ /// Resolves the billed Phase 2 seat count for a Packaged source from the organization's actual usage.
625622 /// </summary>
626623 private async Task < int > CalculateTargetPlanSeatCountAsync ( OrganizationPlan sourcePlan , Guid organizationId )
627624 {
625+ // Packaged line items don't reflect the true total, so bill by actual usage. organization.Seats
626+ // supplies the purchased count ResolveMigratedSeatCount uses for the seat-overage case.
628627 var occupiedSeatCount = ( await organizationRepository
629628 . GetOccupiedSeatCountByOrganizationIdAsync ( organizationId ) ) . Total ;
630-
631- // Teams Starter: flat bundle, no overage — bill occupied seats (no base/purchased comparison),
632- // floored at 1.
633- if ( sourcePlan . HasNonSeatBasedPasswordManagerPlan ( ) )
634- {
635- return Math . Max ( 1 , occupiedSeatCount ) ;
636- }
637-
638- // Teams 2019: occupied below the packaged base, otherwise the purchased seat count.
639629 var organization = await organizationRepository . GetByIdAsync ( organizationId ) ;
640- return occupiedSeatCount < sourcePlan . PasswordManager . BaseSeats
641- ? occupiedSeatCount
642- : organization ? . Seats ?? occupiedSeatCount ;
630+ return sourcePlan . ResolveMigratedSeatCount ( occupiedSeatCount , organization ? . Seats ) ;
643631 }
644632
645633 /// <summary>
0 commit comments