Update bun.lock and package.json to add new dependencies and scripts; refactor unitsController to use projectSlug for filtering

This commit is contained in:
2026-01-26 17:13:29 +05:00
parent cfbc5214e3
commit cb20a4e5f7
5 changed files with 305 additions and 8 deletions
+20
View File
@@ -1,5 +1,6 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "irth-backend", "name": "irth-backend",
@@ -10,6 +11,7 @@
"elysia": "^1.3.1", "elysia": "^1.3.1",
"got": "^14.4.7", "got": "^14.4.7",
"node-cron": "^3.0.3", "node-cron": "^3.0.3",
"xlsx": "^0.18.5",
}, },
"devDependencies": { "devDependencies": {
"bun-types": "latest", "bun-types": "latest",
@@ -90,6 +92,8 @@
"@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="],
"adler-32": ["adler-32@1.3.1", "", {}, "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="], "bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
@@ -98,8 +102,14 @@
"cacheable-request": ["cacheable-request@12.0.1", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.4", "get-stream": "^9.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.4", "mimic-response": "^4.0.0", "normalize-url": "^8.0.1", "responselike": "^3.0.0" } }, "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg=="], "cacheable-request": ["cacheable-request@12.0.1", "", { "dependencies": { "@types/http-cache-semantics": "^4.0.4", "get-stream": "^9.0.1", "http-cache-semantics": "^4.1.1", "keyv": "^4.5.4", "mimic-response": "^4.0.0", "normalize-url": "^8.0.1", "responselike": "^3.0.0" } }, "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg=="],
"cfb": ["cfb@1.2.2", "", { "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" } }, "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA=="],
"codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="],
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
@@ -130,6 +140,8 @@
"form-data-encoder": ["form-data-encoder@4.0.2", "", {}, "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw=="], "form-data-encoder": ["form-data-encoder@4.0.2", "", {}, "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw=="],
"frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="],
"gel": ["gel@2.0.2", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-XTKpfNR9HZOw+k0Bl04nETZjuP5pypVAXsZADSdwr3EtyygTTe1RqvftU2FjGu7Tp9e576a9b/iIOxWrRBxMiQ=="], "gel": ["gel@2.0.2", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-XTKpfNR9HZOw+k0Bl04nETZjuP5pypVAXsZADSdwr3EtyygTTe1RqvftU2FjGu7Tp9e576a9b/iIOxWrRBxMiQ=="],
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
@@ -186,6 +198,8 @@
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"ssf": ["ssf@0.11.2", "", { "dependencies": { "frac": "~1.1.2" } }, "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g=="],
"strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="], "strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="],
"token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
@@ -202,6 +216,12 @@
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
"wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="],
"word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="],
"xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"decompress-response/mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "decompress-response/mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
+6 -2
View File
@@ -4,7 +4,10 @@
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --watch src/index.ts", "dev": "bun run --watch src/index.ts",
"build": "bun run ./bun.build.ts" "build": "bun run ./bun.build.ts",
"import:units": "bun run scripts/import-units.ts",
"rename:project": "bun run scripts/rename-project.ts",
"update:slug": "bun run scripts/update-project-slug.ts"
}, },
"dependencies": { "dependencies": {
"@elysiajs/cors": "^1.3.3", "@elysiajs/cors": "^1.3.3",
@@ -12,7 +15,8 @@
"drizzle-typebox": "^0.3.3", "drizzle-typebox": "^0.3.3",
"elysia": "^1.3.1", "elysia": "^1.3.1",
"got": "^14.4.7", "got": "^14.4.7",
"node-cron": "^3.0.3" "node-cron": "^3.0.3",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"bun-types": "latest", "bun-types": "latest",
+77
View File
@@ -0,0 +1,77 @@
# Import Scripts
## Units Import from XLSX
This script imports unit data from an Excel (XLSX) file into the database.
### Usage
```bash
bun run import:units <path-to-xlsx-file>
```
Or directly:
```bash
bun run scripts/import-units.ts <path-to-xlsx-file>
```
### Example
```bash
bun run import:units ./data/units.xlsx
```
### Required XLSX Format
The Excel file must have the following columns (headers in first row):
| Column Name | Type | Description |
|-------------|------|-------------|
| Project Name | String | Name of the project (required) |
| Floor | Number | Floor number (required) |
| Unit No. | String | Unit number/identifier (required) |
| Unit Series | String | Unit series identifier |
| No of Parking Space | Number | Number of parking spaces |
| Suits Area (sqft) | Number | Suite area in square feet |
| Balcony Area (sqft) | Number | Balcony area in square feet |
| Total Square Ft. | Number | Total area in square feet |
| Unit Type Variant | String | Unit type variant (e.g., "1BR", "2BR+Study") |
| Unit View | String | View description (e.g., "Marina View", "Sea View") |
### Features
- **Duplicate Detection**: Checks if a unit already exists (by Project Name + Unit No.)
- **Update Existing**: Updates existing units instead of creating duplicates
- **Auto-generation**: Automatically generates:
- Project slug (URL-friendly version of project name)
- Unit type variant slug
- Unit type (e.g., "1 Bedroom" from "1BR")
- Number of bathrooms (inferred from bedroom count)
- Unit number (extracted from Unit No.)
- Wing (East/West, if mentioned in Unit View)
- Side (left/right, based on unit number)
### Default Values
The following fields are set to default values and may need to be updated separately:
- `salesPrice`: 0 (not in XLSX format)
- `state`: "available"
- `isLoft`: false
### Import Summary
After completion, the script displays:
- Total rows processed
- Number of new units imported
- Number of existing units updated
- Number of rows skipped (due to missing data)
- Number of errors encountered
### Notes
1. Make sure your database connection is properly configured in `.env`
2. The script uses the unique constraint on (project, unitNo) to prevent duplicates
3. You may need to adjust the helper functions (`getWing`, `getSide`, etc.) based on your specific project requirements
4. Review the imported data and update `salesPrice` and other fields as needed
+196
View File
@@ -0,0 +1,196 @@
import * as XLSX from 'xlsx';
import { db } from '../src/db';
import { unitsTable } from '../src/db/schema';
import { eq, and } from 'drizzle-orm';
interface XLSXRow {
'Project Name': string;
'Floor': number;
'Unit No.': string;
'Unit Series': string;
'No of Parking Space': number;
'Suits Area (sqft)': number;
'Balcony Area (sqft)': number;
'Total Square Ft.': number;
'Unit Type Variant': string;
'Unit View': string;
}
// Constants for project
const PROJECT_NAME = 'Rove Home HQ';
const PROJECT_SLUG = 'hq';
// Helper function to generate slug from text
function generateSlug(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
// Helper function to extract unit number
function extractUnitNumber(unitNo: string): number | null {
const match = unitNo.match(/\d+/);
return match ? parseInt(match[0]) : null;
}
// Helper function to determine unit type based on variant
function getUnitType(variant: string): string {
// Extract bedroom count from variant (e.g., "1BR" -> "1 Bedroom")
const match = variant.match(/(\d+)BR/i);
if (match) {
return `${match[1]} Bedroom`;
}
return variant;
}
// Helper function to determine number of bathrooms based on variant
function getNoOfBathrooms(variant: string): number {
// Common logic: 1BR = 1 bath, 2BR = 2 baths, 3BR = 3 baths, etc.
const match = variant.match(/(\d+)BR/i);
if (match) {
const bedrooms = parseInt(match[1]);
return bedrooms; // Simple 1:1 mapping, adjust if needed
}
return 1; // Default
}
// Helper function to determine wing (East/West) if applicable
function getWing(unitView: string): 'East' | 'West' | null {
const view = unitView.toLowerCase();
if (view.includes('east')) return 'East';
if (view.includes('west')) return 'West';
return null;
}
// Helper function to determine side (left/right) if applicable
function getSide(unitNo: string): 'left' | 'right' | null {
// This logic depends on your project's numbering system
// You may need to adjust this based on your specific requirements
const num = extractUnitNumber(unitNo);
if (num === null) return null;
// Example logic: even numbers on right, odd on left
// Adjust based on your actual unit numbering system
return num % 2 === 0 ? 'right' : 'left';
}
async function importUnitsFromXLSX(filePath: string) {
try {
console.log(`Reading file: ${filePath}`);
// Read the XLSX file
const workbook = XLSX.readFile(filePath);
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
// Convert to JSON
const data: XLSXRow[] = XLSX.utils.sheet_to_json(worksheet);
console.log(`Found ${data.length} rows to import`);
let imported = 0;
let updated = 0;
let skipped = 0;
let errors = 0;
for (const row of data) {
try {
// Validate required fields
if (!row['Unit No.'] || row['Floor'] === undefined) {
console.warn(`Skipping row due to missing required fields:`, row);
skipped++;
continue;
}
const unitTypeVariant = row['Unit Type Variant'] || row['Unit Series'] || '';
const unitTypeVariantSlug = generateSlug(unitTypeVariant);
// Prepare unit data
const unitData = {
unitNo: row['Unit No.'].toString().trim(),
number: extractUnitNumber(row['Unit No.'].toString()),
project: PROJECT_NAME,
projectSlug: PROJECT_SLUG,
floor: Math.floor(row['Floor']),
unitType: getUnitType(unitTypeVariant),
noOfBathrooms: getNoOfBathrooms(unitTypeVariant),
unitView: row['Unit View']?.trim() || null,
unitTypeVariant: unitTypeVariant || null,
unitTypeVariantSlug: unitTypeVariantSlug || null,
suitsArea: row['Suits Area (sqft)'] || 0,
squareFt: row['Total Square Ft.'] || 0,
noOfParkingSpace: row['No of Parking Space'] || 0,
salesPrice: 0, // Not in XLSX, needs to be set separately
state: 'available' as const, // Default state
balconyArea: row['Balcony Area (sqft)'] || 0,
wing: getWing(row['Unit View'] || ''),
side: getSide(row['Unit No.'].toString()),
isLoft: false, // Default, can be updated based on unit type
};
// Check if unit already exists
const existingUnit = await db
.select()
.from(unitsTable)
.where(
and(
eq(unitsTable.project, PROJECT_NAME),
eq(unitsTable.unitNo, unitData.unitNo)
)
)
.limit(1);
if (existingUnit.length > 0) {
// Update existing unit
await db
.update(unitsTable)
.set(unitData)
.where(eq(unitsTable.id, existingUnit[0].id));
console.log(`Updated: ${PROJECT_NAME} - ${unitData.unitNo}`);
updated++;
} else {
// Insert new unit
await db.insert(unitsTable).values(unitData);
console.log(`Imported: ${PROJECT_NAME} - ${unitData.unitNo}`);
imported++;
}
} catch (error) {
console.error(`Error processing row:`, row, error);
errors++;
}
}
console.log('\n=== Import Summary ===');
console.log(`Total rows: ${data.length}`);
console.log(`Imported: ${imported}`);
console.log(`Updated: ${updated}`);
console.log(`Skipped: ${skipped}`);
console.log(`Errors: ${errors}`);
} catch (error) {
console.error('Failed to import units:', error);
throw error;
}
}
// Main execution
const args = process.argv.slice(2);
const filePath = args[0];
if (!filePath) {
console.error('Usage: bun run scripts/import-units.ts <path-to-xlsx-file>');
process.exit(1);
}
importUnitsFromXLSX(filePath)
.then(() => {
console.log('Import completed successfully');
process.exit(0);
})
.catch((error) => {
console.error('Import failed:', error);
process.exit(1);
});
+6 -6
View File
@@ -37,7 +37,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
try { try {
return await db.query.unitsTable.findMany({ return await db.query.unitsTable.findMany({
where: and( where: and(
project ? eq(unitsTable.project, project) : undefined, project ? eq(unitsTable.projectSlug, project) : undefined,
unitTypes ? inArray(unitsTable.unitType, unitTypes) : undefined, unitTypes ? inArray(unitsTable.unitType, unitTypes) : undefined,
cost ? between(unitsTable.salesPrice, cost[0], cost[1]) : undefined, cost ? between(unitsTable.salesPrice, cost[0], cost[1]) : undefined,
floor ? between(unitsTable.floor, floor[0], floor[1]) : undefined, floor ? between(unitsTable.floor, floor[0], floor[1]) : undefined,
@@ -97,7 +97,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
.from(unitsTable) .from(unitsTable)
.where( .where(
and( and(
project ? eq(unitsTable.project, project) : undefined, project ? eq(unitsTable.projectSlug, project) : undefined,
unitTypes ? inArray(unitsTable.unitType, unitTypes) : undefined, unitTypes ? inArray(unitsTable.unitType, unitTypes) : undefined,
cost cost
? between(unitsTable.salesPrice, cost[0], cost[1]) ? between(unitsTable.salesPrice, cost[0], cost[1])
@@ -139,7 +139,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
}) => { }) => {
try { try {
const filters = and( const filters = and(
project ? eq(unitsTable.project, project) : undefined, project ? eq(unitsTable.projectSlug, project) : undefined,
unitTypes && filterName !== "unitTypes" unitTypes && filterName !== "unitTypes"
? inArray(unitsTable.unitType, unitTypes) ? inArray(unitsTable.unitType, unitTypes)
: undefined, : undefined,
@@ -247,7 +247,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
const unitsData = await db const unitsData = await db
.select() .select()
.from(unitsTable) .from(unitsTable)
.where(eq(unitsTable.project, decodeURIComponent(project))); .where(eq(unitsTable.projectSlug, project));
// Создаем структуру для хранения данных по этажам // Создаем структуру для хранения данных по этажам
const floorMap = new Map< const floorMap = new Map<
@@ -331,7 +331,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
return await db.query.unitsTable.findFirst({ return await db.query.unitsTable.findFirst({
where: and( where: and(
eq(unitsTable.unitNo, unitNumber), eq(unitsTable.unitNo, unitNumber),
eq(unitsTable.project, decodeURIComponent(project)) eq(unitsTable.projectSlug, project)
), ),
}); });
} catch (error) { } catch (error) {
@@ -354,7 +354,7 @@ export const unitsController = new Elysia({ prefix: "/units" })
try { try {
return await db.query.unitsTable.findMany({ return await db.query.unitsTable.findMany({
where: and( where: and(
eq(unitsTable.project, decodeURIComponent(project)), eq(unitsTable.projectSlug, project),
eq(unitsTable.floor, floor), eq(unitsTable.floor, floor),
wing ? eq(unitsTable.wing, wing) : undefined wing ? eq(unitsTable.wing, wing) : undefined
), ),