# Landlord Management (Main · User Portal)

**Portal:** Main · **Route:** `main.portal.landlord-management.index` (`GET /property/landlord-management`; `GET /property/renovation-management` redirects) · **Nav:** sidebar → **Landlord Management** · **Gated by:** `['auth','main','contact.verified','lock.analyze']` + `feature:landlord-management`

## What it does
The **after-you-own-it** line: everything a member does with a property once it is theirs. Since
2026-09-25 it is **one page laid out like the app's Landlord tab** (owner: *"follow app and add my
properties"*), top to bottom:

1. **My properties** — the member's units, each a card opening `/property/my-properties/{uuid}`.
   Drawn only when there are any.
2. **The post-VP partner, Antserv** — see below.
3. **Coming soon** — one honest "Landlord tools are on the way" card plus the app's three planned
   lines: **Tenancies · Rent · Property care**. No fake dashboard of a feature that does not exist.
4. **Property Concierge** — the one live part, the done-for-you version of the same jobs, with the
   member's own requests listed here.

It is also the **Control** stage of the member's DMAIC road; the Learning Hub's road links here.

> **Why it stopped being five tabs.** Until 2026-09-25 the page was Renovation · Rent · Tenant
> Management · Tax Planning · Concierge, each in-build tab with a Notify-me waitlist and "use today"
> links. The app's Landlord tab had meanwhile become a different list (Tenancies · Rent · Property
> care, with My properties first), so the website and the app described two different modules. The
> owner chose the app's. Nothing links to a `?tab=` of this page, so no link broke.

> **Renamed from "Renovation & Management" on 2026-08-27.** The URL moved with it; the old one
> redirects because it was the sidebar entry until that day.

## How it works
- **The controller is the content.** `LandlordManagementController@index` sends `myProperties`,
  `planned` (`key`, `title`, `body` — keep in step with the app's `landlordPlan*` strings in
  `lib/l10n/app_en.arb`) and `concierge` (`requests[]`, `services`, `statuses`).
- **`myProperties` is the SAME query as the dashboard and the app** — `OwnsPostVpUnits::ownedUnits()`,
  `UnitJourneyPresenter`'s member view. `Components/MyProperties/MyPropertyCard.vue` is the website
  twin of the app's `MyPropertyCard` and reuses the dashboard card's phrases (zh_CN.json), so the three
  say the same thing in both languages.
- **The waitlist keys stay.** `Src\Portal\FeatureWaitlistSignup`'s `renovation` / `rent` /
  `tenant-management` / `tax-planning` rows are real demand already collected; the page no longer asks
  for new signups, and nothing deletes them (append-never-rename still holds).
- **The Concierge list reads the same rows as `/property-concierge`** via the shared
  `PresentsConciergeRequest` trait.

## The post-VP partner — Antserv (2026-09-25)
PropertyLab's post-VP team works inside **Antserv**'s partner ecosystem (a post-purchase property
service platform: operator matching, a customer-service layer, operator standards). The owner asked for
Antserv's logo and the partnership on this page and on the dashboard, *"so that landlord feel secure."*

**The rule it is built on: PropertyLab stays the front door.** Antserv appears as the network and the
standards behind us, never as a second landlord — and **Antserv's yellow lives only inside its logo**.

- **One source, on the SERVER** — [`Src\PostVp\Support\PostVpPartner`](/src/PostVp/Support/PostVpPartner.php)
  `::payload()`: badge, logo (absolute URLs), headline, body, the unit-card line, the four standards,
  the disclaimer, who-does-what — every string with a `*_zh` twin. Its header records what may be
  said. It was a JS module (`utils/partners/antserv.js`, deleted) until the app needed the same words
  (owner, 2026-09-25: *"make sure mobile app is updated too with Antserv"*).
- **Where it is served** (all additive): Inertia `partner` on Landlord Management, `postVpPartner` on
  `/dashboard`; `partner` beside `member-api/v1/my-properties`, `…/my-properties/{uuid}` and
  `member/dashboard`.
- **Where the line applies is the server's word too:** each unit carries `shows_partner`
  (`PostVpPartner::appliesTo()` — a `vp_at` and a `phase` other than `construction`), added in
  `OwnsPostVpUnits::withOwnerFacts()`, so the website and the app never apply two rules.
- **This page:** `Components/Portal/AntservPartnerSection.vue` (`id="antserv"`) — "Partner of Choice",
  the logo, **four standards as numbers** (renovation ≤ 45 business days · rent within ±20 % of
  projection a year · unit returned within 60 business days on switching operator, if nothing is
  outstanding · open reporting), then *who does what* in two lines.
- **The dashboard:** `Components/Dashboard/AntservPartnerLine.vue` on the My Portfolio card, just
  above the renovation decision, for every unit whose `shows_partner` is true; it links to `#antserv`
  here. The decision's **A partner contractor** option carries
  `hint` "An operator from the Antserv partner network" (`BuildAttentionBand`, additive — the app
  ignores unknown keys).

⚠️ **Wording decisions taken WITHOUT the owner's final word** (he was asleep; defaults chosen as the
least-claiming option — revisit when the agreement is confirmed):
- ~~Badge "Post-VP Partner"~~ → **"Partner of Choice"**, the owner's call (2026-09-25), with the copy
  saying outright that **PropertyLab and Antserv work together** (section headline + the dashboard
  line's "We work together —").
- The four standards are Antserv's **own operator criteria**, quoted from its deck (slide 4, "FAIR
  TERMS TO PROTECT OWNER"), and shown under *"Antserv's requirements of every operator on its platform
  — not a PropertyLab guarantee."* Antserv should confirm we may quote them.
- **No "Details →" link** — there is no public Antserv terms page to point at yet.

Deliberately NOT built (from the design review): a wall of the owner's five fears (the standards answer
them one by one, so the numbers are the answer), a fake "78 % renovated" dashboard preview, and a
three-tier "PropertyLab / Antserv / operator" strip — "Risen" in the brief is the **Maxim Risen**
project, not an operator. **The app shows it too** (app branch `claude/antserv-partner`,
2026-09-25): `lib/features/landlord/domain/post_vp_partner.dart` parses the payload;
`presentation/post_vp_partner_view.dart` draws `PostVpPartnerCard` under My properties on the
Landlord tab and `PostVpPartnerLine` on Home's unit card above the renovation decision (tapping it
opens the card in a sheet); the renovation sheet shows each choice's `hint`. The logo file is
`antserv-logo-2x.png`, not `@2x` — the app's fixture guard reads `x@y.z` as an e-mail address.

## Five-day trainees (2026-09-02)
`index()` renders `Main/Portal/Training/Locked` (`title / reason / day / cardHref / backHref`) for a
trainee whose C card is not complete yet (`TrainingAccess::verdict($user, 'landlord')`, Day 4);
everyone else gets the page as before.

## Related files

**Backend**
- [app/Http/Controllers/Main/Portal/LandlordManagementController.php](/app/Http/Controllers/Main/Portal/LandlordManagementController.php) — my properties, the three planned lines, the concierge list.
- [app/Http/Controllers/Concerns/OwnsPostVpUnits.php](/app/Http/Controllers/Concerns/OwnsPostVpUnits.php) — the member's units (shared with the dashboard, the unit page and the app).
- [app/Http/Controllers/Concerns/PresentsConciergeRequest.php](/app/Http/Controllers/Concerns/PresentsConciergeRequest.php) — `conciergeCard()`, shared with `ConciergeController`.
- [app/Actions/Dashboard/BuildAttentionBand.php](/app/Actions/Dashboard/BuildAttentionBand.php) — the renovation decision's choices (+ the Antserv `hint`).
- [src/Portal/FeatureWaitlistSignup.php](/src/Portal/FeatureWaitlistSignup.php) — the waitlist keys (kept; no longer asked for here).

**Frontend (Vue)**
- [resources/js/Pages/Main/Portal/LandlordManagement/Index.vue](/resources/js/Pages/Main/Portal/LandlordManagement/Index.vue) — the page.
- [resources/js/Components/MyProperties/MyPropertyCard.vue](/resources/js/Components/MyProperties/MyPropertyCard.vue) — one unit, compact (the app's `MyPropertyCard`).
- [resources/js/Components/Portal/AntservPartnerSection.vue](/resources/js/Components/Portal/AntservPartnerSection.vue) · [Components/Dashboard/AntservPartnerLine.vue](/resources/js/Components/Dashboard/AntservPartnerLine.vue) — the partnership, drawn from the server payload.
- [resources/js/Components/Dashboard/RenovationChoice.vue](/resources/js/Components/Dashboard/RenovationChoice.vue) — shows a choice's `hint` under its label.
- [resources/js/Pages/Main/Portal/Concierge/Partials/RequestCard.vue](/resources/js/Pages/Main/Portal/Concierge/Partials/RequestCard.vue) — the concierge cards.
- Logo: `public/main/images/partners/antserv-logo(.png|-2x.png)`, trimmed from Antserv's deck.
- [src/PostVp/Support/PostVpPartner.php](/src/PostVp/Support/PostVpPartner.php) — the ONE payload + `appliesTo()`; tested in `tests/Unit/PostVp/PostVpPartnerTest.php` and `tests/Feature/Api/v1/MobileMyPropertiesTest.php`.

**Routes**
- [routes/main.php](/routes/main.php) — `GET property/landlord-management` + the `property/renovation-management` redirect.

**Tests**
- [tests/Feature/Main/Portal/LandlordManagementTest.php](/tests/Feature/Main/Portal/LandlordManagementTest.php) — the page, the three planned keys, the empty my-properties list, the concierge list, the redirect.

**See also:** [Property Concierge](/docs/modules_handbook/main/property-concierge/readMe.md) · [Post-VP tracker](/docs/modules_handbook/manage/post-vp/readMe.md) — where the units' stages come from · [Dashboard](/docs/modules_handbook/main/dashboard/readMe.md) — the My Portfolio card · [DMAIC 之路](/docs/modules_handbook/main/dmaic-road/readMe.md) — this page is the **Control** stage's tool.
