Update bun.lock and package.json to add new dependencies and scripts; refactor unitsController to use projectSlug for filtering
This commit is contained in:
@@ -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
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user