# Move a customer between plans

> Move-plan preflight, then apply, one customer or in bulk.

Source: https://metrocli.com/docs/guides/move-a-customer/ · Docs index: https://metrocli.com/llms.txt

## Goal

Move Acme Corp from its current package to `growth` at its next renewal boundary, without an unplanned invoice.

A move is two writes: `updateEndDate` on the current contract at the cutover, then a successor contract on the target package with a `RENEWAL` transition from the old one. Balances carry over by each grant's `rollover_fraction`. See [Contracts](/docs/concepts/contracts).

## Preview

```bash
metro contract move-plan preflight --to=growth --ingest-alias=acme-corp \
  --cutover-mode=renewal-boundary --sandbox --out=move.json
```

The cutover is one of:

| Flag | Cutover |
|---|---|
| `--cutover-mode=renewal-boundary` (default) | The next monthly boundary after the last paid plan invoice, or the contract start |
| `--cutover-mode=next-hour` | The next UTC hour |
| `--cutover-mode=current-hour` | The current UTC hour. May finalize the plan fee immediately. |
| `--cutover=<ISO>` | An exact UTC hour |

Pass `--from=<alias>` to assert which package the customer is on now. Preflight then stops if the current package doesn't carry that alias.

```text
metro contract move-plan preflight (sandbox, key sha256:80f0714120b8) - read-only, nothing written to Metronome.
  contract 00000000-0000-4000-8000-000000000002.ending_before: null -> "2027-01-01T00:00:00.000Z"
  successor contract: null -> {"package_id":"00000000-0000-4000-8000-000000000104","alias":"growth","starting_at":"2027-01-01T00:00:00.000Z","transition":"RENEWAL"}
Steps: POST /v1/contracts/updateEndDate ; POST /v1/contracts/create
Payload hash: sha256:...
```

Preflight needs one active contract to move from: the single open-ended one, or the only active one. An active trial qualifies with warning `MTR-MOVE-TRIAL-001`, because apply truncates it at the cutover. An expired customer doesn't; create a fresh contract instead. Preflight pins the target package live at the cutover, warns about balances that won't roll over, and warns `MTR-TIME-002` when the cutover isn't in the future.

> **Credits on the new plan.** move-plan no longer takes a target credit amount. Grant credits on the successor afterwards with `metro ledger-adjustment preflight --grant=credit`, through the same ladder. See [Credits and commits](/docs/guides/credits-and-commits).

## Confirm

Show the human the cutover hour, the old contract's new end, the target package, any rollover warning and the hash. Then dry-run:

```bash
metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:...
```

## Apply

```bash
metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:... \
  --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX
```

### In bulk

Write a roster: one customer ID per line. Lines starting with `#`, and a `customer_id` header, are skipped.

```bash
metro contract move-plan bulk-preflight --to=growth --roster-file=roster.csv \
  --cutover-mode=renewal-boundary --sandbox --out-dir=plans/bulk-move
```

```text
Planned 40/40 moves (0 failed). Manifest: plans/bulk-move/manifest.json
Manifest hash: sha256:...
Canary: metro contract move-plan bulk-apply --file plans/bulk-move/manifest.json --sandbox --confirm-hash sha256:... --limit=1 --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX
```

Run the canary line first: `--limit=1` moves one customer. Read it back, then run the same command without `--limit`. bulk-apply stops at the first failure, waits `--delay-ms` (default 300) between moves, and refuses a manifest edited after preflight.

## Readback

```text
Applied (sandbox).
Read-back (expected vs actual):
  ok   predecessor.ending_before: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
  ok   successor exists: expected true actual true
  ok   successor.starting_at: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
  ok   successor.package_id: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"
  ok   alias growth resolves to successor package at cutover: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"
```

Illustrative IDs; the check names are exact. Any `FAIL` exits `3`. Confirm from the customer's side with `metro ops who "Acme Corp" --sandbox`.
