feat(industrial): update TypeScript types for Float→Decimal USD field migration (GOO-27)
Migration SQL (20260422120000_industrial_usd_to_decimal) and Prisma schema already reflected Decimal(18,4). This commit completes the TypeScript / frontend layer. API changes: - Domain repo interfaces (IndustrialListingListItem, IndustrialListingDetailData, IndustrialParkListItem, IndustrialParkDetailData, IndustrialMarketData): USD money fields changed from number|null → string|null (PostgreSQL numeric serialises as string in raw query results) - Raw DB interface types in Prisma repositories updated to string|null for Decimal columns - toDomain() mappers: parseFloat() added where entity props require number|null for business-logic arithmetic - estimate-industrial-rent handler: Number() cast on Prisma ORM Decimal objects before arithmetic and comparisons Web changes: - khu-cong-nghiep-api.ts: IndustrialParkListItem, IndustrialParkDetail, IndustrialListingItem, IndustrialMarketData USD fields → string|null with JSDoc - listing-card.tsx: parseFloat() wrapping for priceUsdM2/totalLeasePrice display - park-compare-client.tsx: parseFloat() for landRentUsdM2Year in radar score Note: pre-existing test failures in filter-bar/login/search specs are unrelated to this migration (confirmed present on branch before this change). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -48,7 +48,10 @@ export class EstimateIndustrialRentHandler
|
|||||||
// Calculate base rent based on property type
|
// Calculate base rent based on property type
|
||||||
const rentField = this.getRentField(propertyType);
|
const rentField = this.getRentField(propertyType);
|
||||||
const rents = provinceParks
|
const rents = provinceParks
|
||||||
.map((p) => p[rentField] as number | null)
|
.map((p) => {
|
||||||
|
const val = p[rentField];
|
||||||
|
return val != null ? Number(val) : null;
|
||||||
|
})
|
||||||
.filter((r): r is number => r != null);
|
.filter((r): r is number => r != null);
|
||||||
|
|
||||||
const provinceLow = rents.length > 0 ? Math.min(...rents) : null;
|
const provinceLow = rents.length > 0 ? Math.min(...rents) : null;
|
||||||
@@ -58,7 +61,7 @@ export class EstimateIndustrialRentHandler
|
|||||||
// Determine base rent
|
// Determine base rent
|
||||||
let baseRentUsdM2: number;
|
let baseRentUsdM2: number;
|
||||||
if (specificPark && specificPark[rentField] != null) {
|
if (specificPark && specificPark[rentField] != null) {
|
||||||
baseRentUsdM2 = specificPark[rentField] as number;
|
baseRentUsdM2 = Number(specificPark[rentField]);
|
||||||
} else if (provinceAvg != null) {
|
} else if (provinceAvg != null) {
|
||||||
baseRentUsdM2 = provinceAvg;
|
baseRentUsdM2 = provinceAvg;
|
||||||
} else {
|
} else {
|
||||||
@@ -126,9 +129,11 @@ export class EstimateIndustrialRentHandler
|
|||||||
const totalLeaseUsd = Math.round(totalMonthlyUsd * 12 * leaseDurationYears * 100) / 100;
|
const totalLeaseUsd = Math.round(totalMonthlyUsd * 12 * leaseDurationYears * 100) / 100;
|
||||||
|
|
||||||
// Management fee
|
// Management fee
|
||||||
const managementFeeUsdM2 = specificPark?.managementFeeUsd ?? (provinceParks.length > 0
|
const managementFeeUsdM2 = specificPark?.managementFeeUsd != null
|
||||||
? provinceParks.reduce((sum, p) => sum + (p.managementFeeUsd ?? 0), 0) / provinceParks.length || null
|
? Number(specificPark.managementFeeUsd)
|
||||||
: null);
|
: (provinceParks.length > 0
|
||||||
|
? provinceParks.reduce((sum, p) => sum + (p.managementFeeUsd != null ? Number(p.managementFeeUsd) : 0), 0) / provinceParks.length || null
|
||||||
|
: null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
estimated_rent_usd_m2: adjustedRent,
|
estimated_rent_usd_m2: adjustedRent,
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ export interface IndustrialListingListItem {
|
|||||||
status: IndustrialListingStatus;
|
status: IndustrialListingStatus;
|
||||||
title: string;
|
title: string;
|
||||||
areaM2: number;
|
areaM2: number;
|
||||||
priceUsdM2: number | null;
|
/** Decimal(18,4) serialised as string by PostgreSQL numeric — use parseFloat() for arithmetic. */
|
||||||
|
priceUsdM2: string | null;
|
||||||
pricingUnit: string | null;
|
pricingUnit: string | null;
|
||||||
ceilingHeightM: number | null;
|
ceilingHeightM: number | null;
|
||||||
hasMezzanine: boolean;
|
hasMezzanine: boolean;
|
||||||
@@ -64,10 +65,13 @@ export interface IndustrialListingDetailData {
|
|||||||
hasMezzanine: boolean;
|
hasMezzanine: boolean;
|
||||||
hasOfficeArea: boolean;
|
hasOfficeArea: boolean;
|
||||||
officeAreaM2: number | null;
|
officeAreaM2: number | null;
|
||||||
priceUsdM2: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
priceUsdM2: string | null;
|
||||||
pricingUnit: string | null;
|
pricingUnit: string | null;
|
||||||
totalLeasePrice: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
managementFee: number | null;
|
totalLeasePrice: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
managementFee: string | null;
|
||||||
depositMonths: number | null;
|
depositMonths: number | null;
|
||||||
minLeaseYears: number | null;
|
minLeaseYears: number | null;
|
||||||
maxLeaseYears: number | null;
|
maxLeaseYears: number | null;
|
||||||
|
|||||||
@@ -37,9 +37,12 @@ export interface IndustrialParkListItem {
|
|||||||
occupancyRate: number;
|
occupancyRate: number;
|
||||||
remainingAreaHa: number;
|
remainingAreaHa: number;
|
||||||
tenantCount: number;
|
tenantCount: number;
|
||||||
landRentUsdM2Year: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
rbfRentUsdM2Month: number | null;
|
landRentUsdM2Year: string | null;
|
||||||
rbwRentUsdM2Month: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
rbfRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
rbwRentUsdM2Month: string | null;
|
||||||
targetIndustries: string[];
|
targetIndustries: string[];
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
@@ -66,10 +69,14 @@ export interface IndustrialParkDetailData {
|
|||||||
remainingAreaHa: number;
|
remainingAreaHa: number;
|
||||||
tenantCount: number;
|
tenantCount: number;
|
||||||
establishedYear: number | null;
|
establishedYear: number | null;
|
||||||
landRentUsdM2Year: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
rbfRentUsdM2Month: number | null;
|
landRentUsdM2Year: string | null;
|
||||||
rbwRentUsdM2Month: number | null;
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
managementFeeUsd: number | null;
|
rbfRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
rbwRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string — use parseFloat() for arithmetic. */
|
||||||
|
managementFeeUsd: string | null;
|
||||||
infrastructure: Record<string, unknown> | null;
|
infrastructure: Record<string, unknown> | null;
|
||||||
connectivity: Record<string, unknown> | null;
|
connectivity: Record<string, unknown> | null;
|
||||||
incentives: Record<string, unknown> | null;
|
incentives: Record<string, unknown> | null;
|
||||||
@@ -100,10 +107,12 @@ export interface IndustrialParkStatsData {
|
|||||||
export interface IndustrialMarketData {
|
export interface IndustrialMarketData {
|
||||||
totalParks: number;
|
totalParks: number;
|
||||||
avgOccupancyRate: number;
|
avgOccupancyRate: number;
|
||||||
avgLandRentUsdM2: number | null;
|
/** AVG(numeric) serialised as string by PostgreSQL. */
|
||||||
avgRbfRentUsdM2: number | null;
|
avgLandRentUsdM2: string | null;
|
||||||
rentByRegion: { region: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: number }[];
|
/** AVG(numeric) serialised as string by PostgreSQL. */
|
||||||
rentByProvince: { province: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: number }[];
|
avgRbfRentUsdM2: string | null;
|
||||||
|
rentByRegion: { region: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: number }[];
|
||||||
|
rentByProvince: { province: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIndustrialParkRepository {
|
export interface IIndustrialParkRepository {
|
||||||
|
|||||||
@@ -196,10 +196,10 @@ export class PrismaIndustrialListingRepository implements IIndustrialListingRepo
|
|||||||
hasMezzanine: row.hasMezzanine,
|
hasMezzanine: row.hasMezzanine,
|
||||||
hasOfficeArea: row.hasOfficeArea,
|
hasOfficeArea: row.hasOfficeArea,
|
||||||
officeAreaM2: row.officeAreaM2,
|
officeAreaM2: row.officeAreaM2,
|
||||||
priceUsdM2: row.priceUsdM2,
|
priceUsdM2: row.priceUsdM2 != null ? parseFloat(row.priceUsdM2 as unknown as string) : null,
|
||||||
pricingUnit: row.pricingUnit,
|
pricingUnit: row.pricingUnit,
|
||||||
totalLeasePrice: row.totalLeasePrice,
|
totalLeasePrice: row.totalLeasePrice != null ? parseFloat(row.totalLeasePrice as unknown as string) : null,
|
||||||
managementFee: row.managementFee,
|
managementFee: row.managementFee != null ? parseFloat(row.managementFee as unknown as string) : null,
|
||||||
depositMonths: row.depositMonths,
|
depositMonths: row.depositMonths,
|
||||||
minLeaseYears: row.minLeaseYears,
|
minLeaseYears: row.minLeaseYears,
|
||||||
maxLeaseYears: row.maxLeaseYears,
|
maxLeaseYears: row.maxLeaseYears,
|
||||||
@@ -299,10 +299,10 @@ interface RawListing {
|
|||||||
hasMezzanine: boolean;
|
hasMezzanine: boolean;
|
||||||
hasOfficeArea: boolean;
|
hasOfficeArea: boolean;
|
||||||
officeAreaM2: number | null;
|
officeAreaM2: number | null;
|
||||||
priceUsdM2: number | null;
|
priceUsdM2: string | null;
|
||||||
pricingUnit: string | null;
|
pricingUnit: string | null;
|
||||||
totalLeasePrice: number | null;
|
totalLeasePrice: string | null;
|
||||||
managementFee: number | null;
|
managementFee: string | null;
|
||||||
depositMonths: number | null;
|
depositMonths: number | null;
|
||||||
minLeaseYears: number | null;
|
minLeaseYears: number | null;
|
||||||
maxLeaseYears: number | null;
|
maxLeaseYears: number | null;
|
||||||
@@ -327,7 +327,7 @@ interface RawListingListItem {
|
|||||||
status: string;
|
status: string;
|
||||||
title: string;
|
title: string;
|
||||||
areaM2: number;
|
areaM2: number;
|
||||||
priceUsdM2: number | null;
|
priceUsdM2: string | null;
|
||||||
pricingUnit: string | null;
|
pricingUnit: string | null;
|
||||||
ceilingHeightM: number | null;
|
ceilingHeightM: number | null;
|
||||||
hasMezzanine: boolean;
|
hasMezzanine: boolean;
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ export class PrismaIndustrialParkRepository implements IIndustrialParkRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getMarketData(): Promise<IndustrialMarketData> {
|
async getMarketData(): Promise<IndustrialMarketData> {
|
||||||
const [overall] = await this.prisma.$queryRaw<[{ totalParks: bigint; avgOccupancy: number; avgLandRent: number | null; avgRbfRent: number | null }]>`
|
const [overall] = await this.prisma.$queryRaw<[{ totalParks: bigint; avgOccupancy: number; avgLandRent: string | null; avgRbfRent: string | null }]>`
|
||||||
SELECT COUNT(*)::bigint as "totalParks",
|
SELECT COUNT(*)::bigint as "totalParks",
|
||||||
AVG("occupancyRate") as "avgOccupancy",
|
AVG("occupancyRate") as "avgOccupancy",
|
||||||
AVG("landRentUsdM2Year") as "avgLandRent",
|
AVG("landRentUsdM2Year") as "avgLandRent",
|
||||||
@@ -250,14 +250,14 @@ export class PrismaIndustrialParkRepository implements IIndustrialParkRepository
|
|||||||
FROM "IndustrialPark" WHERE status = 'OPERATIONAL' OR status = 'FULL'
|
FROM "IndustrialPark" WHERE status = 'OPERATIONAL' OR status = 'FULL'
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const rentByRegion = await this.prisma.$queryRaw<{ region: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: bigint }[]>`
|
const rentByRegion = await this.prisma.$queryRaw<{ region: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: bigint }[]>`
|
||||||
SELECT region::text, AVG("landRentUsdM2Year") as "avgLandRent",
|
SELECT region::text, AVG("landRentUsdM2Year") as "avgLandRent",
|
||||||
AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
|
AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
|
||||||
FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
|
FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
|
||||||
GROUP BY region ORDER BY "avgLandRent" DESC NULLS LAST
|
GROUP BY region ORDER BY "avgLandRent" DESC NULLS LAST
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const rentByProvince = await this.prisma.$queryRaw<{ province: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: bigint }[]>`
|
const rentByProvince = await this.prisma.$queryRaw<{ province: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: bigint }[]>`
|
||||||
SELECT province, AVG("landRentUsdM2Year") as "avgLandRent",
|
SELECT province, AVG("landRentUsdM2Year") as "avgLandRent",
|
||||||
AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
|
AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
|
||||||
FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
|
FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
|
||||||
@@ -296,10 +296,10 @@ export class PrismaIndustrialParkRepository implements IIndustrialParkRepository
|
|||||||
remainingAreaHa: row.remainingAreaHa,
|
remainingAreaHa: row.remainingAreaHa,
|
||||||
tenantCount: row.tenantCount,
|
tenantCount: row.tenantCount,
|
||||||
establishedYear: row.establishedYear,
|
establishedYear: row.establishedYear,
|
||||||
landRentUsdM2Year: row.landRentUsdM2Year,
|
landRentUsdM2Year: row.landRentUsdM2Year != null ? parseFloat(row.landRentUsdM2Year) : null,
|
||||||
rbfRentUsdM2Month: row.rbfRentUsdM2Month,
|
rbfRentUsdM2Month: row.rbfRentUsdM2Month != null ? parseFloat(row.rbfRentUsdM2Month) : null,
|
||||||
rbwRentUsdM2Month: row.rbwRentUsdM2Month,
|
rbwRentUsdM2Month: row.rbwRentUsdM2Month != null ? parseFloat(row.rbwRentUsdM2Month) : null,
|
||||||
managementFeeUsd: row.managementFeeUsd,
|
managementFeeUsd: row.managementFeeUsd != null ? parseFloat(row.managementFeeUsd) : null,
|
||||||
infrastructure: row.infrastructure as Record<string, unknown> | null,
|
infrastructure: row.infrastructure as Record<string, unknown> | null,
|
||||||
connectivity: row.connectivity as Record<string, unknown> | null,
|
connectivity: row.connectivity as Record<string, unknown> | null,
|
||||||
incentives: row.incentives as Record<string, unknown> | null,
|
incentives: row.incentives as Record<string, unknown> | null,
|
||||||
@@ -407,10 +407,10 @@ interface RawPark {
|
|||||||
remainingAreaHa: number;
|
remainingAreaHa: number;
|
||||||
tenantCount: number;
|
tenantCount: number;
|
||||||
establishedYear: number | null;
|
establishedYear: number | null;
|
||||||
landRentUsdM2Year: number | null;
|
landRentUsdM2Year: string | null;
|
||||||
rbfRentUsdM2Month: number | null;
|
rbfRentUsdM2Month: string | null;
|
||||||
rbwRentUsdM2Month: number | null;
|
rbwRentUsdM2Month: string | null;
|
||||||
managementFeeUsd: number | null;
|
managementFeeUsd: string | null;
|
||||||
infrastructure: Prisma.JsonValue;
|
infrastructure: Prisma.JsonValue;
|
||||||
connectivity: Prisma.JsonValue;
|
connectivity: Prisma.JsonValue;
|
||||||
incentives: Prisma.JsonValue;
|
incentives: Prisma.JsonValue;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ interface ListingCardProps {
|
|||||||
|
|
||||||
export function IndustrialListingCard({ listing }: ListingCardProps) {
|
export function IndustrialListingCard({ listing }: ListingCardProps) {
|
||||||
const priceText = listing.priceUsdM2
|
const priceText = listing.priceUsdM2
|
||||||
? `$${listing.priceUsdM2}/${listing.pricingUnit ?? 'm²/tháng'}`
|
? `$${parseFloat(listing.priceUsdM2)}/${listing.pricingUnit ?? 'm²/tháng'}`
|
||||||
: listing.totalLeasePrice
|
: listing.totalLeasePrice
|
||||||
? `$${listing.totalLeasePrice.toLocaleString()}`
|
? `$${parseFloat(listing.totalLeasePrice).toLocaleString()}`
|
||||||
: 'Liên hệ';
|
: 'Liên hệ';
|
||||||
|
|
||||||
const leaseTermText =
|
const leaseTermText =
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function normalizeScore(park: IndustrialParkDetail, metric: string): number {
|
|||||||
case 'area':
|
case 'area':
|
||||||
return Math.min((park.totalAreaHa / 1000) * 100, 100);
|
return Math.min((park.totalAreaHa / 1000) * 100, 100);
|
||||||
case 'rent': {
|
case 'rent': {
|
||||||
const rent = park.landRentUsdM2Year ?? 0;
|
const rent = park.landRentUsdM2Year != null ? parseFloat(park.landRentUsdM2Year) : 0;
|
||||||
return rent > 0 ? Math.min((rent / 150) * 100, 100) : 0;
|
return rent > 0 ? Math.min((rent / 150) * 100, 100) : 0;
|
||||||
}
|
}
|
||||||
case 'infrastructure': {
|
case 'infrastructure': {
|
||||||
|
|||||||
@@ -23,9 +23,12 @@ export interface IndustrialParkListItem {
|
|||||||
occupancyRate: number;
|
occupancyRate: number;
|
||||||
remainingAreaHa: number;
|
remainingAreaHa: number;
|
||||||
tenantCount: number;
|
tenantCount: number;
|
||||||
landRentUsdM2Year: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
rbfRentUsdM2Month: number | null;
|
landRentUsdM2Year: string | null;
|
||||||
rbwRentUsdM2Month: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
rbfRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
rbwRentUsdM2Month: string | null;
|
||||||
targetIndustries: string[];
|
targetIndustries: string[];
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
@@ -51,10 +54,14 @@ export interface IndustrialParkDetail {
|
|||||||
remainingAreaHa: number;
|
remainingAreaHa: number;
|
||||||
tenantCount: number;
|
tenantCount: number;
|
||||||
establishedYear: number | null;
|
establishedYear: number | null;
|
||||||
landRentUsdM2Year: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
rbfRentUsdM2Month: number | null;
|
landRentUsdM2Year: string | null;
|
||||||
rbwRentUsdM2Month: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
managementFeeUsd: number | null;
|
rbfRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
rbwRentUsdM2Month: string | null;
|
||||||
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
managementFeeUsd: string | null;
|
||||||
infrastructure: Record<string, string> | null;
|
infrastructure: Record<string, string> | null;
|
||||||
connectivity: Record<string, { name: string; distanceKm: number }> | null;
|
connectivity: Record<string, { name: string; distanceKm: number }> | null;
|
||||||
incentives: Record<string, unknown> | null;
|
incentives: Record<string, unknown> | null;
|
||||||
@@ -84,10 +91,12 @@ export interface IndustrialParkStats {
|
|||||||
export interface IndustrialMarketData {
|
export interface IndustrialMarketData {
|
||||||
totalParks: number;
|
totalParks: number;
|
||||||
avgOccupancyRate: number;
|
avgOccupancyRate: number;
|
||||||
avgLandRentUsdM2: number | null;
|
/** AVG(numeric) serialised as string by PostgreSQL. */
|
||||||
avgRbfRentUsdM2: number | null;
|
avgLandRentUsdM2: string | null;
|
||||||
rentByRegion: { region: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: number }[];
|
/** AVG(numeric) serialised as string by PostgreSQL. */
|
||||||
rentByProvince: { province: string; avgLandRent: number | null; avgRbfRent: number | null; parkCount: number }[];
|
avgRbfRentUsdM2: string | null;
|
||||||
|
rentByRegion: { region: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: number }[];
|
||||||
|
rentByProvince: { province: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: number }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Industrial Listing Types ───────────────────────────
|
// ─── Industrial Listing Types ───────────────────────────
|
||||||
@@ -125,9 +134,11 @@ export interface IndustrialListingItem {
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
areaM2: number;
|
areaM2: number;
|
||||||
ceilingHeightM: number | null;
|
ceilingHeightM: number | null;
|
||||||
priceUsdM2: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
priceUsdM2: string | null;
|
||||||
pricingUnit: string | null;
|
pricingUnit: string | null;
|
||||||
totalLeasePrice: number | null;
|
/** Decimal(18,4) serialised as string. Use parseFloat() for arithmetic. */
|
||||||
|
totalLeasePrice: string | null;
|
||||||
minLeaseYears: number | null;
|
minLeaseYears: number | null;
|
||||||
maxLeaseYears: number | null;
|
maxLeaseYears: number | null;
|
||||||
availableFrom: string | null;
|
availableFrom: string | null;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- Migrate IndustrialPark and IndustrialListing USD money fields from
|
||||||
|
-- double precision (Float) to numeric(18, 4) (Decimal) to preserve exact
|
||||||
|
-- precision for money. USING casts keep existing data intact.
|
||||||
|
|
||||||
|
-- IndustrialPark
|
||||||
|
ALTER TABLE "IndustrialPark"
|
||||||
|
ALTER COLUMN "landRentUsdM2Year" TYPE numeric(18, 4) USING "landRentUsdM2Year"::numeric(18, 4),
|
||||||
|
ALTER COLUMN "rbfRentUsdM2Month" TYPE numeric(18, 4) USING "rbfRentUsdM2Month"::numeric(18, 4),
|
||||||
|
ALTER COLUMN "rbwRentUsdM2Month" TYPE numeric(18, 4) USING "rbwRentUsdM2Month"::numeric(18, 4),
|
||||||
|
ALTER COLUMN "managementFeeUsd" TYPE numeric(18, 4) USING "managementFeeUsd"::numeric(18, 4);
|
||||||
|
|
||||||
|
-- IndustrialListing
|
||||||
|
ALTER TABLE "IndustrialListing"
|
||||||
|
ALTER COLUMN "priceUsdM2" TYPE numeric(18, 4) USING "priceUsdM2"::numeric(18, 4),
|
||||||
|
ALTER COLUMN "totalLeasePrice" TYPE numeric(18, 4) USING "totalLeasePrice"::numeric(18, 4),
|
||||||
|
ALTER COLUMN "managementFee" TYPE numeric(18, 4) USING "managementFee"::numeric(18, 4);
|
||||||
Reference in New Issue
Block a user