Skip to content

Release: license transition usage, unused-seat entitlements, invoice refund allocation - #3258

Merged
johnyeocx merged 5 commits into
mainfrom
dev
Sep 3, 2026
Merged

Release: license transition usage, unused-seat entitlements, invoice refund allocation#3258
johnyeocx merged 5 commits into
mainfrom
dev

Conversation

@johnyeocx

@johnyeocx johnyeocx commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • License transitions (#3255): batch license replace resets entitlement usage to granted unless carryOverUsages is enabled (or allocated / carry_from_previous). Migration replace pages pin carry-over so a 100→200 grant with 60 consumed increments by +100 instead of SET 200.
  • Unused assignment seats (#3255): entitlement batch replace/delete no longer filters out assignment customer products with no internal_entity_id; unused seats participate in add/replace/delete transitions.
  • Invoice refund allocation (#3256): when reconstructing prorated credits from stored line items, each historical refund is attributed once to the latest matching earlier charge (never a replacement charge on the same invoice). Fixes sequential quantity updates (e.g. 5→7→9) double-counting refunds against multiple charges.

No new database migrations in this release. main currently has the ai submodule bump (#9491faab) not yet on dev; this merge brings both sides together.

Test plan

  • License transition: immediate switch resets usage; carry-over paths retain/increment as expected
  • Migration replace with carry pin: consumed balance increments, not full SET to new grant
  • Unused assignment CPs receive entitlement rows on batch replace/delete
  • Sequential license quantity update (5→7→9): second preview bills only the delta (2 seats), not a full re-refund
  • Stored-credit unit tests and license billing integration tests green
  • Monorepo typecheck and CI green on the merge commit

Made with Cursor


Summary by cubic

Fixes three license billing bugs: entitlement usage now resets on plan transitions unless carry-over is opted in, unused assignment seats participate in batch operations, and prorated refunds are no longer double-counted across sequential quantity updates.

License transitions

  • Usage carries for allocated features, carry_from_previous, and carry_over_usages; everything else resets to the incoming grant.
  • Migration replace pages always carry usage, so a 100→200 grant with 60 consumed increments by +100 instead of resetting to 200.
  • Same-definition entitlements now emit a reset replace, and unused assignment seats (no internal_entity_id) are included in add/replace/delete operations.
  • Transitions for customers under 1,000 entities run synchronously so upgrades converge before the API responds.

Invoice refunds

  • Each historical refund is attributed once to the latest matching earlier charge, fixing 5→7→9 quantity updates double-counting refunds against replacement charges.

No new database migrations in this release. Merges main into dev, bringing the ai submodule bump.

Written for commit b561e78. Summary will update on new commits.

Review in cubic

Greptile Summary

This release changes license usage transitions, brings unused seats into entitlement updates, and assigns stored invoice refunds to one historical charge.

  • Bug fixes: Reset license-seat usage on plan transitions unless carry-over is enabled, while preserving usage during catalog migrations.
  • Bug fixes: Include unused license seats when adding, replacing, or deleting entitlement rows.
  • Bug fixes: Attribute each historical refund once when calculating later prorated credits.
  • Improvements: Run license transitions synchronously for customers classified as small, although the current classifier does not account for large unused-seat pools.

Confidence Score: 4/5

The synchronous license-transition threshold should be corrected before merging because customers with few entities but many unused seats can execute a very large transition inside the billing request.

The new threshold measures entity records, while the changed batch queries process assigned and unused seat customer products; this mismatch can route large entitlement mutations through the synchronous request path and cause the plan change to time out after state updates have started.

Files Needing Attention: server/src/internal/billing/v2/execute/executeAutumnActions/executeCustomerLicenseTransitions.ts

Important Files Changed

Filename Overview
server/src/internal/billing/v2/execute/executeAutumnActions/executeCustomerLicenseTransitions.ts Adds synchronous execution for customers below an entity threshold, but the threshold can significantly undercount the seat rows actually processed.
server/src/internal/billing/v2/actions/batchTransition/execute/sql/replaceCustomerEntitlementsBatch.ts Extends replacements to unused seats and adds pooled contribution reset support.
server/src/internal/billing/v2/actions/batchTransition/compute/operations/entitlementPriceOperations/computeEntitlementPriceOperations.ts Adds retained-entitlement resets and configurable usage carry-over behavior.
server/src/internal/billing/v2/utils/lineItems/storedLineItemUtils.ts Assigns each refund to the latest matching earlier charge while avoiding same-invoice replacement charges.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[License plan transition] --> B[Count live entities]
  B -->|Below 1,000| C[Run transition in billing request]
  B -->|1,000 or more| D[Queue transition]
  C --> E[Update assigned and unused seats]
  D --> E
  E --> F[Replace, add, or delete entitlements]
Loading
Prompt To Fix All With AI
### Issue 1
server/src/internal/billing/v2/execute/executeAutumnActions/executeCustomerLicenseTransitions.ts:24-34
**Entity count underestimates transition size**

If a customer has fewer than 1,000 live entities but many unused license seats, this count runs the transition synchronously even though the batch queries now process all license-linked seat products. For example, 10 entities and 50,000 unused seats execute the large entitlement mutation inside the billing request, where a timed-out batch can fail the plan change after license state updates have started.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "Merge branch 'main' into dev" | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

johnyeocx and others added 4 commits September 3, 2026 15:39
Run smaller license transitions synchronously and preserve explicit carry-over semantics across retained and pooled entitlements.

Co-authored-by: Cursor <cursoragent@cursor.com>
Unused assignment CPs should receive add/replace/delete, and migration
item replaces must keep carrying usage after the default-reset change.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prevent historical refunds from reducing multiple replacement charges when reconstructing prorated invoice credits.

Co-authored-by: Cursor <cursoragent@cursor.com>
fix(billing): allocate stored refunds to one charge
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
checkout Ignored Ignored Sep 3, 2026 3:55pm UTC
landing-page Ignored Ignored Sep 3, 2026 3:55pm UTC

Request Review

Comment on lines +24 to +34
const customerEntityCount = hasTransitions
? await countEntitiesByInternalCustomerId({
db: ctx.db,
internalCustomerId:
customerLicenseTransitions![0].incomingCustomerLicense
.internal_customer_id,
cap: SYNC_BATCH_TRANSITION_MAX_ENTITIES,
})
: 0;
const runSynchronously =
customerEntityCount < SYNC_BATCH_TRANSITION_MAX_ENTITIES;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Entity count underestimates transition size

If a customer has fewer than 1,000 live entities but many unused license seats, this count runs the transition synchronously even though the batch queries now process all license-linked seat products. For example, 10 entities and 50,000 unused seats execute the large entitlement mutation inside the billing request, where a timed-out batch can fail the plan change after license state updates have started.

Knowledge Base Used: Billing lifecycle and payment flows

Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/internal/billing/v2/execute/executeAutumnActions/executeCustomerLicenseTransitions.ts
Line: 24-34

Comment:
**Entity count underestimates transition size**

If a customer has fewer than 1,000 live entities but many unused license seats, this count runs the transition synchronously even though the batch queries now process all license-linked seat products. For example, 10 entities and 50,000 unused seats execute the large entitlement mutation inside the billing request, where a timed-out batch can fail the plan change after license state updates have started.

**Knowledge Base Used:** [Billing lifecycle and payment flows](https://app.greptile.com/autumn-org-2/-/custom-context/knowledge-base/useautumn/autumn/-/docs/billing-lifecycle.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@johnyeocx
johnyeocx merged commit e60fc94 into main Sep 3, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant