Files
property-match/src/pages/supply/newListingMapper.ts
T
Benjamin Sutter 647493bf3c feat(supply): per-unit price/availability editor + mandatory fit-out cost-bearer for shell/edelrohbau
- unit.availableFrom: explicit per-unit availability date
- UnitStructurePanel: inline expandable editor (pencil) for per-unit price + availability via useUpdateUnit
- fitOutByLandlord now boolean|undefined ("not chosen"); toggle has no default for SHELL/BASIC
- NewListing: validate() blocks save until cost-bearer chosen for Rohbau/Edelrohbau
- PropertyDetailView.saveEdit: same guard for existing listings; required asterisk + hint in both forms
- newListingMapper persists explicit tenant(false) choice (was collapsed to undefined)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:26:45 +02:00

99 lines
3.5 KiB
TypeScript

import { AssetType, AvailabilityStatus, ResultType } from '../../domain/enums'
import type { CreatePropertyInput } from '../../domain/property'
import { ASSET_TYPE_LABELS, LEVEL_TO_SCORE } from './newListingConstants'
export function buildCreatePropertyInput(fields: {
assetType: string
street: string
houseNumber: string
postalCode: string
city: string
areaSqm: number
rentPerSqm: number
availableFrom: string
description: string
softLevels: Record<string, string>
floor: string
fitOut: string
fitOutByLandlord?: boolean
parking: string
ceilingHeight: string
images: string[]
floorPlanUrl: string
mieterausbaubeitrag?: string
isFlexible?: boolean
minLettableSqm?: string
}): CreatePropertyInput {
const {
assetType, street, houseNumber, postalCode, city,
areaSqm, rentPerSqm, availableFrom, description,
softLevels, floor, fitOut, fitOutByLandlord, parking, ceilingHeight, images, floorPlanUrl,
mieterausbaubeitrag, isFlexible, minLettableSqm,
} = fields
const sf = {
prestigeScore: LEVEL_TO_SCORE[softLevels.prestige ?? ''],
commuterAccessScore: LEVEL_TO_SCORE[softLevels.accessibility ?? ''],
visibilityScore: LEVEL_TO_SCORE[softLevels.visibility ?? ''],
footfallScore: LEVEL_TO_SCORE[softLevels.footfall ?? ''],
talentAccessScore: LEVEL_TO_SCORE[softLevels.talentAccess ?? ''],
esgScore: LEVEL_TO_SCORE[softLevels.esg ?? ''],
flexibilityScore: LEVEL_TO_SCORE[softLevels.flexibility ?? ''],
expansionPotentialScore: LEVEL_TO_SCORE[softLevels.expansionPotential ?? ''],
taxEnvironmentScore: LEVEL_TO_SCORE[softLevels.taxEnvironment ?? ''],
}
const hf = {
floor: floor ? parseInt(floor) : undefined,
fitOut: (fitOut || undefined) as 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM' | undefined,
fitOutByLandlord,
// MAB nur relevant, wenn der Mieter ausbaut — sonst nicht speichern
mieterausbaubeitragPerSqm: !fitOutByLandlord && mieterausbaubeitrag ? parseInt(mieterausbaubeitrag) : undefined,
parking: parking ? parseInt(parking) : undefined,
ceilingHeightM: ceilingHeight ? parseFloat(ceilingHeight) : undefined,
}
return {
title: `${ASSET_TYPE_LABELS[assetType] ?? assetType} · ${city}`,
assetType: assetType as typeof AssetType[keyof typeof AssetType],
resultType: ResultType.VERIFIED_PORTFOLIO,
sourceType: 'DIRECT',
location: { city, country: 'CH' },
address: {
street: street.trim(),
houseNumber: houseNumber.trim(),
postalCode: postalCode.trim(),
city: city.trim(),
country: 'CH',
},
areaSqm,
rentPricePerSqm: rentPerSqm,
availabilityDate: availableFrom || new Date().toISOString().slice(0, 10),
availabilityStatus: availableFrom ? AvailabilityStatus.AVAILABLE_SOON : AvailabilityStatus.AVAILABLE_NOW,
confidenceScore: 1.0,
description: description.trim() || undefined,
softFactors: sf,
hardFacts: hf,
images: images.length > 0 ? images : undefined,
floorPlanUrl: floorPlanUrl.trim() || undefined,
dataQuality: {
score: 1.0,
missingCriticalFields: [],
missingOptionalFields: [],
freshness: 'FRESH',
warnings: [],
},
status: 'ACTIVE',
units: isFlexible ? [{
id: `unit-listing-${Date.now()}`,
propertyId: '',
floorLevel: hf.floor ?? 0,
areaSqm,
available: true,
rentPricePerSqm: rentPerSqm,
isFlexible: true,
minLettableSqm: minLettableSqm ? parseInt(minLettableSqm) : undefined,
}] : undefined,
}
}