# From 0.48 to 0.49

> What to check when upgrading from better-supabase 0.48 to 0.49. The renames left open by the cleanup finish, notifications gain digests and the statuses held and digested, new blocks declare tenant default seeds, maintenance runs without pg_cron, billing keeps native subscriptions and accounting recognizes revenue over time.

Source: https://bettersupabase.com/docs/migration/0.48-to-0.49

0.49.0 finishes the renames that ADR 0130 left open, adds the `commissions` and
`referrals` blocks, and gives several blocks new tables, columns and functions.
Run `better-supabase upgrade` to rename the table and to copy stored action
items into tasks, then generate a migration with `db diff` for the rest.

| Section                                                     | Needs code                                            |
| ----------------------------------------------------------- | ----------------------------------------------------- |
| [Permission actions](#permission-actions)                   | Rename keys in `permissions` and in tokens            |
| [Activity note actions](#activity-note-actions)             | Run `upgrade` with the `tasks` block installed        |
| [E-invoice deliveries](#e-invoice-deliveries)               | Run `upgrade`; rename `EINVOICE_*` hint checks        |
| [Seeds and tenant defaults](#seeds-and-tenant-defaults)     | Run `db diff`, then call `seed_tenant_defaults`       |
| [Maintenance without pg_cron](#maintenance-without-pg-cron) | Install `housekeeping`, schedule `maintenanceTimer`   |
| [Notification digests](#notification-digests)               | Run `db diff`; widen an adopted delivery status check |
| [Native billing](#native-billing)                           | Run `db diff`                                         |
| [Revenue recognition](#revenue-recognition)                 | Run `db diff`                                         |
| [Drive transfers](#drive-transfers)                         | Pass the service role for offboarding                 |

## Permission actions [#permission-actions]

The actions of the operations model use the verbs of the rest of the
convention. The config types accept only the new names; delegated tokens and
stored configs that carry an old name still resolve, so nothing breaks until you
write the config again.

| Old action      | New action      |
| --------------- | --------------- |
| `view`          | `read`          |
| `viewAll`       | `readAll`       |
| `edit`          | `write`         |
| `admin`         | `manage`        |
| `viewSensitive` | `readSensitive` |

## Activity note actions [#activity-note-actions]

`EngagementNotes.actionItems` is now `actionTaskIds`: each action item is a task
of the tasks block, linked through the table `activity_note_actions`. The
`action_items` column goes away. The upgrade step creates one task per stored
item and drops the column. It stops with instructions when the tasks block is
not installed; install it first.

## E-invoice deliveries [#e-invoice-deliveries]

The table `invoice_e_deliveries` is now `e_invoice_deliveries`, with its
indexes and policies, and the hint codes `EINVOICE_*` are `E_INVOICE_*`. The
upgrade step renames the table in place, so no rows move. The table
`invoice_e_endpoints` keeps its name: it is the one documented exception in
[ADR 0206](https://github.com/better-supabase/better-supabase/blob/main/docs/decisions/0206-e-invoice-naming.md).

## Seeds and tenant defaults [#seeds-and-tenant-defaults]

The `organizations` block installs a `seeds` module with
`seed_tenant_defaults(tenant, variants, locale)`. It calls the
`seed_<block>_defaults` function of every installed block that declares rows, in
install order, and `create_organization` calls it for a new organization unless
`database.seed.defaults` is `false` or its `onCreate` is `false`.

Blocks that declare defaults:

- `pipelines` declares the default pipeline and stage template of every record
  type without a `scope`, with the `b2b-sales` and `recruiting` variants.
- `accounting` declares its dimensions, the `generic` chart and the `nl-rgs`
  Dutch chart.
- `permissions` adds the catalog roles `owner`, `admin` and `member` when the
  roles table lacks them.
- `inbox` creates a Help inbox on the in-app channel.
- `attachments` creates the document types `contract`, `invoice`, `receipt`,
  `identity`, `report` and `other`.
- `calendar` creates a Public holidays calendar for the locales `nl`, `de`,
  `fr`, `en-gb` and `en-us`, with an all-day event per holiday for the years
  2026 to 2030.

Apply the new functions with `db diff`, then call `seed_tenant_defaults` for the
organizations that should get the rows. Named presets now seed variants: `crm`
seeds `b2b-sales`, `ats` seeds `recruiting` and `erp` seeds the `generic` chart,
written under `database.seed.defaults.variants`. Design format 9 adds `seeds`
with `sample: { size, seed }`, `variants` and `locale`, and `init --preset` and
`add --preset` write all of it to the config.

Doctor reports BS354 when a declared default never reaches a new organization,
BS355 when a configured variant is not declared, and BS356 when a generated
sample file sits outside `database.seed.sample.output`.

## Maintenance without pg_cron [#maintenance-without-pg_cron]

`maintenanceTimer` and `maintenanceTick` in `better-supabase/blocks/queue` run
`better_supabase.maintenance()` from the `block_ticks` queue, and skip the run
when pg_cron already has it. `support`, `usage` and `webhooks-out` now register
their cleanups with the `housekeeping` module, which becomes a requirement of
those three modules. Doctor reports BS510 when the housekeeping module is
installed and nothing schedules `maintenance()`.

## Notification digests [#notification-digests]

`notification_preferences` gains a `cadence`: `immediate`, `daily`, `weekly` or
`off`. A daily or weekly delivery is held, and the `digest_run` housekeeping job
folds held deliveries into `notification_digests`. `createNotificationDigests`
sends each digest as one email through the `mail-sync` mailer, and
`set_notification_cadence` sets the choice.

Run `db diff` to pick up the column, the table and the functions. The
`notifications` block also writes two new delivery statuses: `held` for a
delivery that waits for a digest, and `digested` for one folded into a digest.
The block's own table accepts them. When the deliveries live in your own table,
its `status` check must accept `pending`, `sent`, `failed`, `skipped`, `held`
and `digested`; a check that predates digests rejects a held delivery and the
send fails. Doctor reports such a check as
[BS361](/docs/cli/doctor#bs361). Widen the check, or add the values to the
enum, and the block keeps working. The
[notifications guide](/docs/blocks/notifications#digests) has the flow.

The `consents` block adds `requires_consent(subject_type, subject_id,
purpose_key)` and a `consents` contribution key for system purposes. `sequences`
email steps take `consentPurpose`, and `send` in `notifications` takes
`consentPurpose`; both skip a send while the subject has no consent.

## Native billing [#native-billing]

`billing` keeps subscriptions in Postgres: `billing_native_subscriptions` holds
a plan, a price in minor units, a quantity and the period, independent of
Stripe. `create_native_subscription`, `prorate_change` and
`cancel_native_subscription` are service-role functions, and
`createNativeSubscriptions` in `better-supabase/blocks/billing` calls them.
`prorate_change` returns the credit, charge and net of a mid-period change and,
with the `ledger` block installed, posts the net as one `billing.proration`
transaction. Run `db diff` to pick up the table and the functions.

## Revenue recognition [#revenue-recognition]

With `options.recognition: true`, `accounting` splits an amount over the months
of a service period through `schedule_revenue_recognition`, books the deferral
against the `deferred_revenue` account (`2200` in the generic chart) and reads
the rows with `list_revenue_recognition`. The `revenue_recognition` period kind
posts each month's row when that month closes. The two `accounting_recognition_*`
tables always exist, so run `db diff` to pick them up.

## Drive transfers [#drive-transfers]

`drive_transfer_ownership` (`drive.transferOwnership`) no longer lets a member
with `drive.manage` take another member's items. A member hands over only their
own items. Moving someone else's items, as `driveOffboardingSink` does when a
member leaves, needs the service role. Before, a member with `drive.manage`
could transfer another member's private files to themselves and read them.

## New blocks [#new-blocks]

`commissions` keeps plans per seller, accrues commissions from sold records or,
with the `invoices` option, from paid invoices, builds statements per period and
pays them out. `referrals` keeps codes per member, attributes signups and posts
a reward once to the ledger when a referral qualifies. Both are opt in: install
them with `better-supabase add`, or through a preset that includes them.