refactor: unit-centric lease view — Einheiten & Mietverträge

Replace building-level Miet- & Mieterinformationen section with
unit-level lease display. Each unit row now shows tenant name and
lease end date directly; expand reveals full contract details
(Mietbeginn, Mietende, Laufzeit, Breakout, source system, doc link).

Floor grouping groups consecutive same-floor units under one label.
Domain model extended with Lease and Tenant types; deprecated
property-level tenant fields preserved via @deprecated for compat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-06-10 22:52:18 +02:00
parent d771029633
commit ee229f8f4f
9 changed files with 518 additions and 343 deletions
+32 -5
View File
@@ -4,6 +4,7 @@ import type {
} from './enums'
import { AvailabilityStatus as AS } from './enums'
import type { PropertyUnit } from './unit'
import type { Lease } from './lease'
// Re-export unit types so all existing imports from 'property' continue to work
export type { PropertyUnit, UnitBundle, UnitNeedMatch } from './unit'
@@ -137,18 +138,26 @@ export interface Property {
description?: string
images?: string[]
floorPlanUrl?: string
leaseContractUrl?: string // link to the signed lease document
leaseContractName?: string // display label, e.g. "Mietvertrag 20212026"
/** @deprecated Use unit.leases[].contractDocumentUrl instead */
leaseContractUrl?: string
/** @deprecated Use unit.leases[].contractDocumentName instead */
leaseContractName?: string
propertyNumber?: string
units?: PropertyUnit[]
mapImageUrl?: string
/** @deprecated Use unit.leases instead */
leaseTerm?: string
/** @deprecated Use unit.leases[].startDate instead */
leaseStartDate?: string
/** @deprecated Use unit.leases[].endDate instead */
leaseEndDate?: string
/** @deprecated Use unit.leases[].breakoutOption instead */
breakoutOption?: boolean
/** @deprecated Use unit.leases[].breakoutOptionDate instead */
breakoutOptionDate?: string
/** @deprecated Use unit.leases[].tenant.companyName instead */
currentTenant?: string
importedFrom?: string
importedAt?: string
@@ -169,9 +178,29 @@ export type UpdatePropertyInput = Partial<CreatePropertyInput>
* Returns the rentable units of a property.
* For properties without explicit units, synthesises one unit from property-level data
* so that all matching logic can operate uniformly at unit level.
* Property-level lease fields are synthesised into a proper Lease object for backward compat.
*/
export function getEffectiveUnits(p: Property): PropertyUnit[] {
if (p.units && p.units.length > 0) return p.units
const syntheticLease: Lease | undefined = p.currentTenant
? {
id: `${p.id}-lease`,
unitId: p.id,
tenant: { id: `${p.id}-tenant`, companyName: p.currentTenant },
rentPerSqm: p.rentPricePerSqm,
startDate: p.leaseStartDate ?? '',
endDate: p.leaseEndDate ?? '',
contractDurationYears: p.contractDurationMonths ? Math.round(p.contractDurationMonths / 12) : undefined,
breakoutOption: p.breakoutOption,
breakoutOptionDate: p.breakoutOptionDate,
status: p.leaseEndDate && new Date(p.leaseEndDate) > new Date() ? 'ACTIVE' : 'EXPIRED',
sourceSystem: p.importedFrom ?? 'MANUAL',
contractDocumentUrl: p.leaseContractUrl,
contractDocumentName: p.leaseContractName,
}
: undefined
return [{
id: p.id,
propertyId: p.id,
@@ -180,9 +209,7 @@ export function getEffectiveUnits(p: Property): PropertyUnit[] {
areaSqm: p.areaSqm,
available: p.availabilityStatus === AS.AVAILABLE_NOW || p.availabilityStatus === AS.AVAILABLE_SOON,
rentPricePerSqm: p.rentPricePerSqm,
currentTenant: p.currentTenant,
leaseTerm: p.leaseTerm,
leaseEndDate: p.leaseEndDate,
leases: syntheticLease ? [syntheticLease] : [],
schattenmarktRelease: p.schattenmarktRelease?.enabled
? { enabled: true, availableFrom: p.leaseEndDate }
: undefined,