dealer table added and claim type filter implemented
This commit is contained in:
parent
651576f51d
commit
9c8a8512bc
275
docs/DEALERS_CSV_IMPORT_FIX.sql
Normal file
275
docs/DEALERS_CSV_IMPORT_FIX.sql
Normal file
@ -0,0 +1,275 @@
|
||||
-- ============================================================
|
||||
-- DEALERS CSV IMPORT - WORKING SOLUTION
|
||||
-- ============================================================
|
||||
-- This script provides a working solution for importing dealers
|
||||
-- from CSV with auto-generated columns (dealer_id, created_at, updated_at, is_active)
|
||||
-- ============================================================
|
||||
|
||||
-- METHOD 1: If your CSV does NOT have dealer_id, created_at, updated_at, is_active
|
||||
-- ============================================================
|
||||
-- Use this COPY command if your CSV has exactly 44 columns (without the auto-generated ones)
|
||||
|
||||
\copy public.dealers(
|
||||
sales_code,
|
||||
service_code,
|
||||
gear_code,
|
||||
gma_code,
|
||||
region,
|
||||
dealership,
|
||||
state,
|
||||
district,
|
||||
city,
|
||||
location,
|
||||
city_category_pst,
|
||||
layout_format,
|
||||
tier_city_category,
|
||||
on_boarding_charges,
|
||||
date,
|
||||
single_format_month_year,
|
||||
domain_id,
|
||||
replacement,
|
||||
termination_resignation_status,
|
||||
date_of_termination_resignation,
|
||||
last_date_of_operations,
|
||||
old_codes,
|
||||
branch_details,
|
||||
dealer_principal_name,
|
||||
dealer_principal_email_id,
|
||||
dp_contact_number,
|
||||
dp_contacts,
|
||||
showroom_address,
|
||||
showroom_pincode,
|
||||
workshop_address,
|
||||
workshop_pincode,
|
||||
location_district,
|
||||
state_workshop,
|
||||
no_of_studios,
|
||||
website_update,
|
||||
gst,
|
||||
pan,
|
||||
firm_type,
|
||||
prop_managing_partners_directors,
|
||||
total_prop_partners_directors,
|
||||
docs_folder_link,
|
||||
workshop_gma_codes,
|
||||
existing_new,
|
||||
dlrcode
|
||||
)
|
||||
FROM 'C:/Users/COMP/Downloads/DEALERS_CLEAN.csv'
|
||||
WITH (
|
||||
FORMAT csv,
|
||||
HEADER true,
|
||||
ENCODING 'UTF8'
|
||||
);
|
||||
|
||||
|
||||
-- ============================================================
|
||||
-- METHOD 2: If your CSV HAS dealer_id, created_at, updated_at, is_active columns
|
||||
-- ============================================================
|
||||
-- Use this approach if your CSV has 48 columns (including the auto-generated ones)
|
||||
-- This creates a temporary table, imports, then inserts with defaults
|
||||
|
||||
-- Step 1: Create temporary table matching your CSV structure
|
||||
-- This accepts ALL columns from CSV (whether 44 or 48 columns)
|
||||
CREATE TEMP TABLE dealers_temp (
|
||||
dealer_id TEXT,
|
||||
sales_code TEXT,
|
||||
service_code TEXT,
|
||||
gear_code TEXT,
|
||||
gma_code TEXT,
|
||||
region TEXT,
|
||||
dealership TEXT,
|
||||
state TEXT,
|
||||
district TEXT,
|
||||
city TEXT,
|
||||
location TEXT,
|
||||
city_category_pst TEXT,
|
||||
layout_format TEXT,
|
||||
tier_city_category TEXT,
|
||||
on_boarding_charges TEXT,
|
||||
date TEXT,
|
||||
single_format_month_year TEXT,
|
||||
domain_id TEXT,
|
||||
replacement TEXT,
|
||||
termination_resignation_status TEXT,
|
||||
date_of_termination_resignation TEXT,
|
||||
last_date_of_operations TEXT,
|
||||
old_codes TEXT,
|
||||
branch_details TEXT,
|
||||
dealer_principal_name TEXT,
|
||||
dealer_principal_email_id TEXT,
|
||||
dp_contact_number TEXT,
|
||||
dp_contacts TEXT,
|
||||
showroom_address TEXT,
|
||||
showroom_pincode TEXT,
|
||||
workshop_address TEXT,
|
||||
workshop_pincode TEXT,
|
||||
location_district TEXT,
|
||||
state_workshop TEXT,
|
||||
no_of_studios TEXT,
|
||||
website_update TEXT,
|
||||
gst TEXT,
|
||||
pan TEXT,
|
||||
firm_type TEXT,
|
||||
prop_managing_partners_directors TEXT,
|
||||
total_prop_partners_directors TEXT,
|
||||
docs_folder_link TEXT,
|
||||
workshop_gma_codes TEXT,
|
||||
existing_new TEXT,
|
||||
dlrcode TEXT,
|
||||
created_at TEXT,
|
||||
updated_at TEXT,
|
||||
is_active TEXT
|
||||
);
|
||||
|
||||
-- Step 2: Import CSV into temporary table
|
||||
-- This will work whether your CSV has 44 or 48 columns
|
||||
\copy dealers_temp FROM 'C:/Users/COMP/Downloads/DEALERS_CLEAN.csv' WITH (FORMAT csv, HEADER true, ENCODING 'UTF8');
|
||||
|
||||
-- Optional: Check what was imported
|
||||
-- SELECT COUNT(*) FROM dealers_temp;
|
||||
|
||||
-- Step 3: Insert into actual dealers table
|
||||
-- IMPORTANT: We IGNORE dealer_id, created_at, updated_at, is_active from CSV
|
||||
-- These will use database DEFAULT values (auto-generated UUID, current timestamp, true)
|
||||
INSERT INTO public.dealers (
|
||||
sales_code,
|
||||
service_code,
|
||||
gear_code,
|
||||
gma_code,
|
||||
region,
|
||||
dealership,
|
||||
state,
|
||||
district,
|
||||
city,
|
||||
location,
|
||||
city_category_pst,
|
||||
layout_format,
|
||||
tier_city_category,
|
||||
on_boarding_charges,
|
||||
date,
|
||||
single_format_month_year,
|
||||
domain_id,
|
||||
replacement,
|
||||
termination_resignation_status,
|
||||
date_of_termination_resignation,
|
||||
last_date_of_operations,
|
||||
old_codes,
|
||||
branch_details,
|
||||
dealer_principal_name,
|
||||
dealer_principal_email_id,
|
||||
dp_contact_number,
|
||||
dp_contacts,
|
||||
showroom_address,
|
||||
showroom_pincode,
|
||||
workshop_address,
|
||||
workshop_pincode,
|
||||
location_district,
|
||||
state_workshop,
|
||||
no_of_studios,
|
||||
website_update,
|
||||
gst,
|
||||
pan,
|
||||
firm_type,
|
||||
prop_managing_partners_directors,
|
||||
total_prop_partners_directors,
|
||||
docs_folder_link,
|
||||
workshop_gma_codes,
|
||||
existing_new,
|
||||
dlrcode
|
||||
)
|
||||
SELECT
|
||||
NULLIF(sales_code, ''),
|
||||
NULLIF(service_code, ''),
|
||||
NULLIF(gear_code, ''),
|
||||
NULLIF(gma_code, ''),
|
||||
NULLIF(region, ''),
|
||||
NULLIF(dealership, ''),
|
||||
NULLIF(state, ''),
|
||||
NULLIF(district, ''),
|
||||
NULLIF(city, ''),
|
||||
NULLIF(location, ''),
|
||||
NULLIF(city_category_pst, ''),
|
||||
NULLIF(layout_format, ''),
|
||||
NULLIF(tier_city_category, ''),
|
||||
NULLIF(on_boarding_charges, ''),
|
||||
NULLIF(date, ''),
|
||||
NULLIF(single_format_month_year, ''),
|
||||
NULLIF(domain_id, ''),
|
||||
NULLIF(replacement, ''),
|
||||
NULLIF(termination_resignation_status, ''),
|
||||
NULLIF(date_of_termination_resignation, ''),
|
||||
NULLIF(last_date_of_operations, ''),
|
||||
NULLIF(old_codes, ''),
|
||||
NULLIF(branch_details, ''),
|
||||
NULLIF(dealer_principal_name, ''),
|
||||
NULLIF(dealer_principal_email_id, ''),
|
||||
NULLIF(dp_contact_number, ''),
|
||||
NULLIF(dp_contacts, ''),
|
||||
NULLIF(showroom_address, ''),
|
||||
NULLIF(showroom_pincode, ''),
|
||||
NULLIF(workshop_address, ''),
|
||||
NULLIF(workshop_pincode, ''),
|
||||
NULLIF(location_district, ''),
|
||||
NULLIF(state_workshop, ''),
|
||||
CASE WHEN no_of_studios = '' THEN 0 ELSE no_of_studios::INTEGER END,
|
||||
NULLIF(website_update, ''),
|
||||
NULLIF(gst, ''),
|
||||
NULLIF(pan, ''),
|
||||
NULLIF(firm_type, ''),
|
||||
NULLIF(prop_managing_partners_directors, ''),
|
||||
NULLIF(total_prop_partners_directors, ''),
|
||||
NULLIF(docs_folder_link, ''),
|
||||
NULLIF(workshop_gma_codes, ''),
|
||||
NULLIF(existing_new, ''),
|
||||
NULLIF(dlrcode, '')
|
||||
FROM dealers_temp;
|
||||
|
||||
-- Step 4: Clean up temporary table
|
||||
DROP TABLE dealers_temp;
|
||||
|
||||
-- ============================================================
|
||||
-- METHOD 3: Using COPY with DEFAULT (PostgreSQL 12+)
|
||||
-- ============================================================
|
||||
-- Alternative approach using a function to set defaults
|
||||
|
||||
-- Create a function to handle the import with defaults
|
||||
CREATE OR REPLACE FUNCTION import_dealers_from_csv()
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
-- This will be called from a COPY command that uses a function
|
||||
-- See METHOD 1 for the actual COPY command
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ============================================================
|
||||
-- VERIFICATION QUERIES
|
||||
-- ============================================================
|
||||
|
||||
-- Check import results
|
||||
SELECT
|
||||
COUNT(*) as total_dealers,
|
||||
COUNT(dealer_id) as has_dealer_id,
|
||||
COUNT(created_at) as has_created_at,
|
||||
COUNT(updated_at) as has_updated_at,
|
||||
COUNT(*) FILTER (WHERE is_active = true) as active_count
|
||||
FROM dealers;
|
||||
|
||||
-- View sample records with auto-generated values
|
||||
SELECT
|
||||
dealer_id,
|
||||
dlrcode,
|
||||
dealership,
|
||||
created_at,
|
||||
updated_at,
|
||||
is_active
|
||||
FROM dealers
|
||||
LIMIT 5;
|
||||
|
||||
-- Check for any issues
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE dealer_id IS NULL) as missing_dealer_id,
|
||||
COUNT(*) FILTER (WHERE created_at IS NULL) as missing_created_at,
|
||||
COUNT(*) FILTER (WHERE updated_at IS NULL) as missing_updated_at
|
||||
FROM dealers;
|
||||
|
||||
515
docs/DEALERS_CSV_IMPORT_GUIDE.md
Normal file
515
docs/DEALERS_CSV_IMPORT_GUIDE.md
Normal file
@ -0,0 +1,515 @@
|
||||
# Dealers CSV Import Guide
|
||||
|
||||
This guide explains how to format and import dealer data from a CSV file into the PostgreSQL `dealers` table.
|
||||
|
||||
## ⚠️ Important: Auto-Generated Columns
|
||||
|
||||
**DO NOT include these columns in your CSV file** - they are automatically generated by the database:
|
||||
|
||||
- ❌ `dealer_id` - Auto-generated UUID (e.g., `550e8400-e29b-41d4-a716-446655440000`)
|
||||
- ❌ `created_at` - Auto-generated timestamp (current time on import)
|
||||
- ❌ `updated_at` - Auto-generated timestamp (current time on import)
|
||||
- ❌ `is_active` - Defaults to `true`
|
||||
|
||||
Your CSV should have **exactly 44 columns** (the data columns listed below).
|
||||
|
||||
## Table of Contents
|
||||
- [CSV File Format Requirements](#csv-file-format-requirements)
|
||||
- [Column Mapping](#column-mapping)
|
||||
- [Preparing Your CSV File](#preparing-your-csv-file)
|
||||
- [Import Methods](#import-methods)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## CSV File Format Requirements
|
||||
|
||||
### File Requirements
|
||||
- **Format**: CSV (Comma-Separated Values)
|
||||
- **Encoding**: UTF-8
|
||||
- **Header Row**: Required (first row must contain column names)
|
||||
- **Delimiter**: Comma (`,`)
|
||||
- **Text Qualifier**: Double quotes (`"`) for fields containing commas or special characters
|
||||
|
||||
### Required Columns (in exact order)
|
||||
|
||||
**Important Notes:**
|
||||
- **DO NOT include** `dealer_id`, `created_at`, `updated_at`, or `is_active` in your CSV file
|
||||
- These columns will be automatically generated by the database:
|
||||
- `dealer_id`: Auto-generated UUID
|
||||
- `created_at`: Auto-generated timestamp (current time)
|
||||
- `updated_at`: Auto-generated timestamp (current time)
|
||||
- `is_active`: Defaults to `true`
|
||||
|
||||
Your CSV file must have these **44 columns** in the following order:
|
||||
|
||||
1. `sales_code`
|
||||
2. `service_code`
|
||||
3. `gear_code`
|
||||
4. `gma_code`
|
||||
5. `region`
|
||||
6. `dealership`
|
||||
7. `state`
|
||||
8. `district`
|
||||
9. `city`
|
||||
10. `location`
|
||||
11. `city_category_pst`
|
||||
12. `layout_format`
|
||||
13. `tier_city_category`
|
||||
14. `on_boarding_charges`
|
||||
15. `date`
|
||||
16. `single_format_month_year`
|
||||
17. `domain_id`
|
||||
18. `replacement`
|
||||
19. `termination_resignation_status`
|
||||
20. `date_of_termination_resignation`
|
||||
21. `last_date_of_operations`
|
||||
22. `old_codes`
|
||||
23. `branch_details`
|
||||
24. `dealer_principal_name`
|
||||
25. `dealer_principal_email_id`
|
||||
26. `dp_contact_number`
|
||||
27. `dp_contacts`
|
||||
28. `showroom_address`
|
||||
29. `showroom_pincode`
|
||||
30. `workshop_address`
|
||||
31. `workshop_pincode`
|
||||
32. `location_district`
|
||||
33. `state_workshop`
|
||||
34. `no_of_studios`
|
||||
35. `website_update`
|
||||
36. `gst`
|
||||
37. `pan`
|
||||
38. `firm_type`
|
||||
39. `prop_managing_partners_directors`
|
||||
40. `total_prop_partners_directors`
|
||||
41. `docs_folder_link`
|
||||
42. `workshop_gma_codes`
|
||||
43. `existing_new`
|
||||
44. `dlrcode`
|
||||
|
||||
---
|
||||
|
||||
## Column Mapping
|
||||
|
||||
### Column Details
|
||||
|
||||
| Column Name | Type | Required | Notes |
|
||||
|------------|------|----------|-------|
|
||||
| `sales_code` | String(50) | No | Sales code identifier |
|
||||
| `service_code` | String(50) | No | Service code identifier |
|
||||
| `gear_code` | String(50) | No | Gear code identifier |
|
||||
| `gma_code` | String(50) | No | GMA code identifier |
|
||||
| `region` | String(50) | No | Geographic region |
|
||||
| `dealership` | String(255) | No | Dealership business name |
|
||||
| `state` | String(100) | No | State name |
|
||||
| `district` | String(100) | No | District name |
|
||||
| `city` | String(100) | No | City name |
|
||||
| `location` | String(255) | No | Location details |
|
||||
| `city_category_pst` | String(50) | No | City category (PST) |
|
||||
| `layout_format` | String(50) | No | Layout format |
|
||||
| `tier_city_category` | String(100) | No | TIER City Category |
|
||||
| `on_boarding_charges` | Decimal | No | Numeric value (e.g., 1000.50) |
|
||||
| `date` | Date | No | Format: YYYY-MM-DD (e.g., 2014-09-30) |
|
||||
| `single_format_month_year` | String(50) | No | Format: Sep-2014 |
|
||||
| `domain_id` | String(255) | No | Email domain (e.g., dealer@royalenfield.com) |
|
||||
| `replacement` | String(50) | No | Replacement status |
|
||||
| `termination_resignation_status` | String(255) | No | Termination/Resignation status |
|
||||
| `date_of_termination_resignation` | Date | No | Format: YYYY-MM-DD |
|
||||
| `last_date_of_operations` | Date | No | Format: YYYY-MM-DD |
|
||||
| `old_codes` | String(255) | No | Old code references |
|
||||
| `branch_details` | Text | No | Branch information |
|
||||
| `dealer_principal_name` | String(255) | No | Principal's full name |
|
||||
| `dealer_principal_email_id` | String(255) | No | Principal's email |
|
||||
| `dp_contact_number` | String(20) | No | Contact phone number |
|
||||
| `dp_contacts` | String(20) | No | Additional contacts |
|
||||
| `showroom_address` | Text | No | Full showroom address |
|
||||
| `showroom_pincode` | String(10) | No | Showroom postal code |
|
||||
| `workshop_address` | Text | No | Full workshop address |
|
||||
| `workshop_pincode` | String(10) | No | Workshop postal code |
|
||||
| `location_district` | String(100) | No | Location/District |
|
||||
| `state_workshop` | String(100) | No | State for workshop |
|
||||
| `no_of_studios` | Integer | No | Number of studios (default: 0) |
|
||||
| `website_update` | String(10) | No | Yes/No value |
|
||||
| `gst` | String(50) | No | GST number |
|
||||
| `pan` | String(50) | No | PAN number |
|
||||
| `firm_type` | String(100) | No | Type of firm (e.g., Proprietorship) |
|
||||
| `prop_managing_partners_directors` | String(255) | No | Proprietor/Partners/Directors names |
|
||||
| `total_prop_partners_directors` | String(255) | No | Total count or names |
|
||||
| `docs_folder_link` | Text | No | Google Drive or document folder URL |
|
||||
| `workshop_gma_codes` | String(255) | No | Workshop GMA codes |
|
||||
| `existing_new` | String(50) | No | Existing/New status |
|
||||
| `dlrcode` | String(50) | No | Dealer code |
|
||||
|
||||
---
|
||||
|
||||
## Preparing Your CSV File
|
||||
|
||||
### Step 1: Create/Edit Your CSV File
|
||||
|
||||
1. **Open your CSV file** in Excel, Google Sheets, or a text editor
|
||||
2. **Remove auto-generated columns** (if present):
|
||||
- ❌ **DO NOT include**: `dealer_id`, `created_at`, `updated_at`, `is_active`
|
||||
- ✅ These will be automatically generated by the database
|
||||
3. **Ensure the header row** matches the column names exactly (see [Column Mapping](#column-mapping))
|
||||
4. **Verify column order** - columns must be in the exact order listed above (44 columns total)
|
||||
5. **Check data formats**:
|
||||
- Dates: Use `YYYY-MM-DD` format (e.g., `2014-09-30`)
|
||||
- Numbers: Use decimal format for `on_boarding_charges` (e.g., `1000.50`)
|
||||
- Empty values: Leave cells empty (don't use "NULL" or "N/A" as text)
|
||||
|
||||
### Step 2: Handle Special Characters
|
||||
|
||||
- **Commas in text**: Wrap the entire field in double quotes
|
||||
- Example: `"No.335, HVP RR Nagar Sector B"`
|
||||
- **Quotes in text**: Use double quotes to escape: `""quoted text""`
|
||||
- **Newlines in text**: Wrap field in double quotes
|
||||
|
||||
### Step 3: Date Formatting
|
||||
|
||||
Ensure dates are in `YYYY-MM-DD` format:
|
||||
- ✅ Correct: `2014-09-30`
|
||||
- ❌ Wrong: `30-Sep-14`, `09/30/2014`, `30-09-2014`
|
||||
|
||||
### Step 4: Save the File
|
||||
|
||||
1. **Save as CSV** (UTF-8 encoding)
|
||||
2. **File location**: Save to an accessible path (e.g., `C:/Users/COMP/Downloads/DEALERS_CLEAN.csv`)
|
||||
3. **File name**: Use a descriptive name (e.g., `DEALERS_CLEAN.csv`)
|
||||
|
||||
### Sample CSV Format
|
||||
|
||||
**Important:** Your CSV should **NOT** include `dealer_id`, `created_at`, `updated_at`, or `is_active` columns. These are auto-generated.
|
||||
|
||||
```csv
|
||||
sales_code,service_code,gear_code,gma_code,region,dealership,state,district,city,location,city_category_pst,layout_format,tier_city_category,on_boarding_charges,date,single_format_month_year,domain_id,replacement,termination_resignation_status,date_of_termination_resignation,last_date_of_operations,old_codes,branch_details,dealer_principal_name,dealer_principal_email_id,dp_contact_number,dp_contacts,showroom_address,showroom_pincode,workshop_address,workshop_pincode,location_district,state_workshop,no_of_studios,website_update,gst,pan,firm_type,prop_managing_partners_directors,total_prop_partners_directors,docs_folder_link,workshop_gma_codes,existing_new,dlrcode
|
||||
5124,5125,5573,9430,S3,Accelerate Motors,Karnataka,Bengaluru,Bengaluru,RAJA RAJESHWARI NAGAR,A+,A+,Tier 1 City,,2014-09-30,Sep-2014,acceleratemotors.rrnagar@dealer.royalenfield.com,,,,,,,N. Shyam Charmanna,shyamcharmanna@yahoo.co.in,7022049621,7022049621,"No.335, HVP RR Nagar Sector B, Ideal Homes Town Ship, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,"Works Shop No.460, 80ft Road, 2nd Phase R R Nagar, Bangalore - 560098, Dist – Bangalore, Karnataka",560098,Bangalore,Karnataka,0,Yes,29ARCPS1311D1Z6,ARCPS1311D,Proprietorship,CHARMANNA SHYAM NELLAMAKADA,CHARMANNA SHYAM NELLAMAKADA,https://drive.google.com/drive/folders/1sGtg3s1h9aBXX9fhxJufYuBWar8gVvnb,,,3386
|
||||
```
|
||||
|
||||
**What gets auto-generated:**
|
||||
- `dealer_id`: `550e8400-e29b-41d4-a716-446655440000` (example UUID)
|
||||
- `created_at`: `2025-01-20 10:30:45.123` (current timestamp)
|
||||
- `updated_at`: `2025-01-20 10:30:45.123` (current timestamp)
|
||||
- `is_active`: `true`
|
||||
|
||||
---
|
||||
|
||||
## Import Methods
|
||||
|
||||
### Method 1: PostgreSQL COPY Command (Recommended - If CSV has 44 columns)
|
||||
|
||||
**Use this if your CSV does NOT include `dealer_id`, `created_at`, `updated_at`, `is_active` columns.**
|
||||
|
||||
**Prerequisites:**
|
||||
- PostgreSQL client (psql) installed
|
||||
- Access to PostgreSQL server
|
||||
- CSV file path accessible from PostgreSQL server
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Connect to PostgreSQL:**
|
||||
```bash
|
||||
psql -U your_username -d royal_enfield_workflow -h localhost
|
||||
```
|
||||
|
||||
2. **Run the COPY command:**
|
||||
|
||||
**Note:** The COPY command explicitly lists only the columns from your CSV. The following columns are **automatically handled by the database** and should **NOT** be in your CSV:
|
||||
- `dealer_id` - Auto-generated UUID
|
||||
- `created_at` - Auto-generated timestamp
|
||||
- `updated_at` - Auto-generated timestamp
|
||||
- `is_active` - Defaults to `true`
|
||||
|
||||
```sql
|
||||
\copy public.dealers(
|
||||
sales_code,
|
||||
service_code,
|
||||
gear_code,
|
||||
gma_code,
|
||||
region,
|
||||
dealership,
|
||||
state,
|
||||
district,
|
||||
city,
|
||||
location,
|
||||
city_category_pst,
|
||||
layout_format,
|
||||
tier_city_category,
|
||||
on_boarding_charges,
|
||||
date,
|
||||
single_format_month_year,
|
||||
domain_id,
|
||||
replacement,
|
||||
termination_resignation_status,
|
||||
date_of_termination_resignation,
|
||||
last_date_of_operations,
|
||||
old_codes,
|
||||
branch_details,
|
||||
dealer_principal_name,
|
||||
dealer_principal_email_id,
|
||||
dp_contact_number,
|
||||
dp_contacts,
|
||||
showroom_address,
|
||||
showroom_pincode,
|
||||
workshop_address,
|
||||
workshop_pincode,
|
||||
location_district,
|
||||
state_workshop,
|
||||
no_of_studios,
|
||||
website_update,
|
||||
gst,
|
||||
pan,
|
||||
firm_type,
|
||||
prop_managing_partners_directors,
|
||||
total_prop_partners_directors,
|
||||
docs_folder_link,
|
||||
workshop_gma_codes,
|
||||
existing_new,
|
||||
dlrcode
|
||||
)
|
||||
FROM 'C:/Users/COMP/Downloads/DEALERS_CLEAN.csv'
|
||||
WITH (
|
||||
FORMAT csv,
|
||||
HEADER true,
|
||||
ENCODING 'UTF8'
|
||||
);
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
- `dealer_id` will be automatically generated as a UUID for each row
|
||||
- `created_at` will be set to the current timestamp
|
||||
- `updated_at` will be set to the current timestamp
|
||||
- `is_active` will default to `true`
|
||||
|
||||
3. **Verify import:**
|
||||
```sql
|
||||
SELECT COUNT(*) FROM dealers;
|
||||
SELECT * FROM dealers LIMIT 5;
|
||||
```
|
||||
|
||||
### Method 2: Using Temporary Table (If CSV has 48 columns including auto-generated ones)
|
||||
|
||||
**Use this if your CSV includes `dealer_id`, `created_at`, `updated_at`, `is_active` columns and you're getting errors.**
|
||||
|
||||
This method uses a temporary table to import the CSV, then inserts into the actual table while ignoring the auto-generated columns:
|
||||
|
||||
```sql
|
||||
-- Step 1: Create temporary table
|
||||
CREATE TEMP TABLE dealers_temp (
|
||||
dealer_id TEXT,
|
||||
sales_code TEXT,
|
||||
service_code TEXT,
|
||||
-- ... (all 48 columns as TEXT)
|
||||
);
|
||||
|
||||
-- Step 2: Import CSV into temp table
|
||||
\copy dealers_temp FROM 'C:/Users/COMP/Downloads/DEALERS_CLEAN.csv' WITH (FORMAT csv, HEADER true, ENCODING 'UTF8');
|
||||
|
||||
-- Step 3: Insert into actual table (ignoring dealer_id, created_at, updated_at, is_active)
|
||||
INSERT INTO public.dealers (
|
||||
sales_code,
|
||||
service_code,
|
||||
-- ... (only the 44 data columns)
|
||||
)
|
||||
SELECT
|
||||
NULLIF(sales_code, ''),
|
||||
NULLIF(service_code, ''),
|
||||
-- ... (convert and handle empty strings)
|
||||
FROM dealers_temp
|
||||
WHERE sales_code IS NOT NULL OR dealership IS NOT NULL; -- Skip completely empty rows
|
||||
|
||||
-- Step 4: Clean up
|
||||
DROP TABLE dealers_temp;
|
||||
```
|
||||
|
||||
**See `DEALERS_CSV_IMPORT_FIX.sql` for the complete working script.**
|
||||
|
||||
### Method 3: Using pgAdmin
|
||||
|
||||
1. Open pgAdmin and connect to your database
|
||||
2. Right-click on `dealers` table → **Import/Export Data**
|
||||
3. Select **Import**
|
||||
4. Configure:
|
||||
- **Filename**: Browse to your CSV file
|
||||
- **Format**: CSV
|
||||
- **Header**: Yes
|
||||
- **Encoding**: UTF8
|
||||
- **Delimiter**: Comma
|
||||
5. Click **OK** to import
|
||||
|
||||
### Method 4: Using Node.js Script
|
||||
|
||||
Create a script to import CSV programmatically (useful for automation):
|
||||
|
||||
```typescript
|
||||
import { sequelize } from '../config/database';
|
||||
import { QueryTypes } from 'sequelize';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as csv from 'csv-parser';
|
||||
|
||||
async function importDealersFromCSV(csvFilePath: string) {
|
||||
const dealers: any[] = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.createReadStream(csvFilePath)
|
||||
.pipe(csv())
|
||||
.on('data', (row) => {
|
||||
dealers.push(row);
|
||||
})
|
||||
.on('end', async () => {
|
||||
try {
|
||||
// Bulk insert dealers
|
||||
// Implementation depends on your needs
|
||||
console.log(`Imported ${dealers.length} dealers`);
|
||||
resolve(dealers);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues and Solutions
|
||||
|
||||
#### 1. **"Column count mismatch" Error**
|
||||
- **Problem**: CSV has different number of columns than expected
|
||||
- **Solution**:
|
||||
- Verify your CSV has exactly **44 columns** (excluding header)
|
||||
- **Remove** `dealer_id`, `created_at`, `updated_at`, and `is_active` if they exist in your CSV
|
||||
- These columns are auto-generated and should NOT be in the CSV file
|
||||
|
||||
#### 2. **"Invalid date format" Error**
|
||||
- **Problem**: Dates not in `YYYY-MM-DD` format
|
||||
- **Solution**: Convert dates to `YYYY-MM-DD` format (e.g., `2014-09-30`)
|
||||
|
||||
#### 3. **"Encoding error" or "Special characters not displaying correctly**
|
||||
- **Problem**: CSV file not saved in UTF-8 encoding
|
||||
- **Solution**:
|
||||
- In Excel: Save As → CSV UTF-8 (Comma delimited) (*.csv)
|
||||
- In Notepad++: Encoding → Convert to UTF-8 → Save
|
||||
|
||||
#### 4. **"Permission denied" Error (COPY command)**
|
||||
- **Problem**: PostgreSQL server cannot access the file path
|
||||
- **Solution**:
|
||||
- Use absolute path with forward slashes: `C:/Users/COMP/Downloads/DEALERS_CLEAN.csv`
|
||||
- Ensure file permissions allow read access
|
||||
- For remote servers, upload file to server first
|
||||
|
||||
#### 5. **"Duplicate key" Error**
|
||||
- **Problem**: Trying to import duplicate records
|
||||
- **Solution**:
|
||||
- Use `ON CONFLICT` handling in your import
|
||||
- Or clean CSV to remove duplicates before import
|
||||
|
||||
#### 6. **Empty values showing as "NULL" text**
|
||||
- **Problem**: CSV contains literal "NULL" or "N/A" strings
|
||||
- **Solution**: Replace with empty cells in CSV
|
||||
|
||||
#### 7. **Commas in address fields breaking import**
|
||||
- **Problem**: Address fields contain commas not properly quoted
|
||||
- **Solution**: Wrap fields containing commas in double quotes:
|
||||
```csv
|
||||
"No.335, HVP RR Nagar Sector B, Ideal Homes Town Ship"
|
||||
```
|
||||
|
||||
### Pre-Import Checklist
|
||||
|
||||
- [ ] CSV file saved in UTF-8 encoding
|
||||
- [ ] **Removed** `dealer_id`, `created_at`, `updated_at`, and `is_active` columns (if present)
|
||||
- [ ] Header row matches column names exactly
|
||||
- [ ] All 44 columns present in correct order
|
||||
- [ ] Dates formatted as `YYYY-MM-DD`
|
||||
- [ ] Numeric fields contain valid numbers (or are empty)
|
||||
- [ ] Text fields with commas are wrapped in quotes
|
||||
- [ ] File path is accessible from PostgreSQL server
|
||||
- [ ] Database connection credentials are correct
|
||||
|
||||
### Verification Queries
|
||||
|
||||
After import, run these queries to verify:
|
||||
|
||||
```sql
|
||||
-- Count total dealers
|
||||
SELECT COUNT(*) as total_dealers FROM dealers;
|
||||
|
||||
-- Verify auto-generated columns
|
||||
SELECT
|
||||
dealer_id,
|
||||
created_at,
|
||||
updated_at,
|
||||
is_active,
|
||||
dlrcode,
|
||||
dealership
|
||||
FROM dealers
|
||||
LIMIT 5;
|
||||
|
||||
-- Check for null values in key fields
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE dlrcode IS NULL) as null_dlrcode,
|
||||
COUNT(*) FILTER (WHERE domain_id IS NULL) as null_domain_id,
|
||||
COUNT(*) FILTER (WHERE dealership IS NULL) as null_dealership
|
||||
FROM dealers;
|
||||
|
||||
-- View sample records
|
||||
SELECT
|
||||
dealer_id,
|
||||
dlrcode,
|
||||
dealership,
|
||||
city,
|
||||
state,
|
||||
domain_id,
|
||||
created_at,
|
||||
is_active
|
||||
FROM dealers
|
||||
LIMIT 10;
|
||||
|
||||
-- Check date formats
|
||||
SELECT
|
||||
dlrcode,
|
||||
date,
|
||||
date_of_termination_resignation,
|
||||
last_date_of_operations
|
||||
FROM dealers
|
||||
WHERE date IS NOT NULL
|
||||
LIMIT 5;
|
||||
|
||||
-- Verify all dealers have dealer_id and timestamps
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
COUNT(dealer_id) as has_dealer_id,
|
||||
COUNT(created_at) as has_created_at,
|
||||
COUNT(updated_at) as has_updated_at,
|
||||
COUNT(*) FILTER (WHERE is_active = true) as active_count
|
||||
FROM dealers;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Notes
|
||||
|
||||
- **Backup**: Always backup your database before bulk imports
|
||||
- **Testing**: Test import with a small sample (5-10 rows) first
|
||||
- **Validation**: Validate data quality before import
|
||||
- **Updates**: Use `UPSERT` logic if you need to update existing records
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the troubleshooting section above
|
||||
2. Review PostgreSQL COPY documentation
|
||||
3. Verify CSV format matches the sample provided
|
||||
4. Check database logs for detailed error messages
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: December 2025
|
||||
**Version**: 1.0
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
"setup": "ts-node -r tsconfig-paths/register src/scripts/auto-setup.ts",
|
||||
"migrate": "ts-node -r tsconfig-paths/register src/scripts/migrate.ts",
|
||||
"seed:config": "ts-node -r tsconfig-paths/register src/scripts/seed-admin-config.ts",
|
||||
"seed:dealers": "ts-node -r tsconfig-paths/register src/scripts/seed-dealers.ts",
|
||||
"cleanup:dealer-claims": "ts-node -r tsconfig-paths/register src/scripts/cleanup-dealer-claims.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -46,6 +46,7 @@ export class DashboardController {
|
||||
const endDate = req.query.endDate as string | undefined;
|
||||
const status = req.query.status as string | undefined; // Status filter (not used in stats - stats show all statuses)
|
||||
const priority = req.query.priority as string | undefined;
|
||||
const templateType = req.query.templateType as string | undefined;
|
||||
const department = req.query.department as string | undefined;
|
||||
const initiator = req.query.initiator as string | undefined;
|
||||
const approver = req.query.approver as string | undefined;
|
||||
@ -61,6 +62,7 @@ export class DashboardController {
|
||||
endDate,
|
||||
status,
|
||||
priority,
|
||||
templateType,
|
||||
department,
|
||||
initiator,
|
||||
approver,
|
||||
|
||||
@ -380,6 +380,7 @@ export class WorkflowController {
|
||||
search: req.query.search as string | undefined,
|
||||
status: req.query.status as string | undefined,
|
||||
priority: req.query.priority as string | undefined,
|
||||
templateType: req.query.templateType as string | undefined,
|
||||
department: req.query.department as string | undefined,
|
||||
initiator: req.query.initiator as string | undefined,
|
||||
approver: req.query.approver as string | undefined,
|
||||
@ -441,6 +442,7 @@ export class WorkflowController {
|
||||
const search = req.query.search as string | undefined;
|
||||
const status = req.query.status as string | undefined;
|
||||
const priority = req.query.priority as string | undefined;
|
||||
const templateType = req.query.templateType as string | undefined;
|
||||
const department = req.query.department as string | undefined;
|
||||
const initiator = req.query.initiator as string | undefined;
|
||||
const approver = req.query.approver as string | undefined;
|
||||
@ -450,7 +452,7 @@ export class WorkflowController {
|
||||
const startDate = req.query.startDate as string | undefined;
|
||||
const endDate = req.query.endDate as string | undefined;
|
||||
|
||||
const filters = { search, status, priority, department, initiator, approver, approverType, slaCompliance, dateRange, startDate, endDate };
|
||||
const filters = { search, status, priority, templateType, department, initiator, approver, approverType, slaCompliance, dateRange, startDate, endDate };
|
||||
|
||||
const result = await workflowService.listParticipantRequests(userId, page, limit, filters);
|
||||
ResponseHandler.success(res, result, 'Participant requests fetched');
|
||||
@ -473,13 +475,14 @@ export class WorkflowController {
|
||||
const search = req.query.search as string | undefined;
|
||||
const status = req.query.status as string | undefined;
|
||||
const priority = req.query.priority as string | undefined;
|
||||
const templateType = req.query.templateType as string | undefined;
|
||||
const department = req.query.department as string | undefined;
|
||||
const slaCompliance = req.query.slaCompliance as string | undefined;
|
||||
const dateRange = req.query.dateRange as string | undefined;
|
||||
const startDate = req.query.startDate as string | undefined;
|
||||
const endDate = req.query.endDate as string | undefined;
|
||||
|
||||
const filters = { search, status, priority, department, slaCompliance, dateRange, startDate, endDate };
|
||||
const filters = { search, status, priority, templateType, department, slaCompliance, dateRange, startDate, endDate };
|
||||
|
||||
const result = await workflowService.listMyInitiatedRequests(userId, page, limit, filters);
|
||||
ResponseHandler.success(res, result, 'My initiated requests fetched');
|
||||
@ -499,7 +502,8 @@ export class WorkflowController {
|
||||
const filters = {
|
||||
search: req.query.search as string | undefined,
|
||||
status: req.query.status as string | undefined,
|
||||
priority: req.query.priority as string | undefined
|
||||
priority: req.query.priority as string | undefined,
|
||||
templateType: req.query.templateType as string | undefined
|
||||
};
|
||||
|
||||
// Extract sorting parameters
|
||||
@ -524,7 +528,8 @@ export class WorkflowController {
|
||||
const filters = {
|
||||
search: req.query.search as string | undefined,
|
||||
status: req.query.status as string | undefined,
|
||||
priority: req.query.priority as string | undefined
|
||||
priority: req.query.priority as string | undefined,
|
||||
templateType: req.query.templateType as string | undefined
|
||||
};
|
||||
|
||||
// Extract sorting parameters
|
||||
|
||||
322
src/migrations/20250120-create-dealers-table.ts
Normal file
322
src/migrations/20250120-create-dealers-table.ts
Normal file
@ -0,0 +1,322 @@
|
||||
import { QueryInterface, DataTypes } from 'sequelize';
|
||||
import { Sequelize } from 'sequelize';
|
||||
|
||||
export async function up(queryInterface: QueryInterface): Promise<void> {
|
||||
// Ensure uuid-ossp extension is enabled (required for uuid_generate_v4())
|
||||
await queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
|
||||
|
||||
// Create dealers table with all fields from sample data
|
||||
await queryInterface.createTable('dealers', {
|
||||
dealer_id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
defaultValue: Sequelize.literal('uuid_generate_v4()')
|
||||
},
|
||||
sales_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Sales Code'
|
||||
},
|
||||
service_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Service Code'
|
||||
},
|
||||
gear_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Gear Code'
|
||||
},
|
||||
gma_code: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'GMA CODE'
|
||||
},
|
||||
region: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Region'
|
||||
},
|
||||
dealership: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Dealership name'
|
||||
},
|
||||
state: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'State'
|
||||
},
|
||||
district: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'District'
|
||||
},
|
||||
city: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'City'
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Location'
|
||||
},
|
||||
city_category_pst: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'City category (PST)'
|
||||
},
|
||||
layout_format: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Layout format'
|
||||
},
|
||||
tier_city_category: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'TIER City Category'
|
||||
},
|
||||
on_boarding_charges: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'On Boarding Charges (stored as text to allow text values)'
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'DATE (stored as text to avoid format validation)'
|
||||
},
|
||||
single_format_month_year: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Single Format of Month/Year (stored as text)'
|
||||
},
|
||||
domain_id: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Domain Id'
|
||||
},
|
||||
replacement: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Replacement (stored as text to allow longer values)'
|
||||
},
|
||||
termination_resignation_status: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Termination / Resignation under Proposal or Evaluation'
|
||||
},
|
||||
date_of_termination_resignation: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Date Of termination/ resignation (stored as text to avoid format validation)'
|
||||
},
|
||||
last_date_of_operations: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Last date of operations (stored as text to avoid format validation)'
|
||||
},
|
||||
old_codes: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Old Codes'
|
||||
},
|
||||
branch_details: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Branch Details'
|
||||
},
|
||||
dealer_principal_name: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Dealer Principal Name'
|
||||
},
|
||||
dealer_principal_email_id: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Dealer Principal Email Id'
|
||||
},
|
||||
dp_contact_number: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'DP CONTACT NUMBER (stored as text to allow multiple numbers)'
|
||||
},
|
||||
dp_contacts: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'DP CONTACTS (stored as text to allow multiple contacts)'
|
||||
},
|
||||
showroom_address: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Showroom Address'
|
||||
},
|
||||
showroom_pincode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
comment: 'Showroom Pincode'
|
||||
},
|
||||
workshop_address: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Workshop Address'
|
||||
},
|
||||
workshop_pincode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
comment: 'Workshop Pincode'
|
||||
},
|
||||
location_district: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'Location / District'
|
||||
},
|
||||
state_workshop: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'State (for workshop)'
|
||||
},
|
||||
no_of_studios: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
comment: 'No Of Studios'
|
||||
},
|
||||
website_update: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Website update (stored as text to allow longer values)'
|
||||
},
|
||||
gst: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'GST'
|
||||
},
|
||||
pan: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'PAN'
|
||||
},
|
||||
firm_type: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'Firm Type'
|
||||
},
|
||||
prop_managing_partners_directors: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Prop. / Managing Partners / Managing Directors'
|
||||
},
|
||||
total_prop_partners_directors: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Total Prop. / Partners / Directors'
|
||||
},
|
||||
docs_folder_link: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'DOCS Folder Link'
|
||||
},
|
||||
workshop_gma_codes: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Workshop GMA Codes'
|
||||
},
|
||||
existing_new: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Existing / New'
|
||||
},
|
||||
dlrcode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'dlrcode'
|
||||
},
|
||||
is_active: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: 'Whether the dealer is currently active'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
|
||||
}
|
||||
});
|
||||
|
||||
// Create indexes
|
||||
await queryInterface.addIndex('dealers', ['sales_code'], {
|
||||
name: 'idx_dealers_sales_code',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['service_code'], {
|
||||
name: 'idx_dealers_service_code',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['gma_code'], {
|
||||
name: 'idx_dealers_gma_code',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['domain_id'], {
|
||||
name: 'idx_dealers_domain_id',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['region'], {
|
||||
name: 'idx_dealers_region',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['state'], {
|
||||
name: 'idx_dealers_state',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['city'], {
|
||||
name: 'idx_dealers_city',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['district'], {
|
||||
name: 'idx_dealers_district',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['dlrcode'], {
|
||||
name: 'idx_dealers_dlrcode',
|
||||
unique: false
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('dealers', ['is_active'], {
|
||||
name: 'idx_dealers_is_active',
|
||||
unique: false
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(queryInterface: QueryInterface): Promise<void> {
|
||||
// Drop indexes first
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_sales_code');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_service_code');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_gma_code');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_domain_id');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_region');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_state');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_city');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_district');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_dlrcode');
|
||||
await queryInterface.removeIndex('dealers', 'idx_dealers_is_active');
|
||||
|
||||
// Drop table
|
||||
await queryInterface.dropTable('dealers');
|
||||
}
|
||||
|
||||
442
src/models/Dealer.ts
Normal file
442
src/models/Dealer.ts
Normal file
@ -0,0 +1,442 @@
|
||||
import { DataTypes, Model, Optional } from 'sequelize';
|
||||
import { sequelize } from '../config/database';
|
||||
|
||||
interface DealerAttributes {
|
||||
dealerId: string;
|
||||
salesCode?: string | null;
|
||||
serviceCode?: string | null;
|
||||
gearCode?: string | null;
|
||||
gmaCode?: string | null;
|
||||
region?: string | null;
|
||||
dealership?: string | null;
|
||||
state?: string | null;
|
||||
district?: string | null;
|
||||
city?: string | null;
|
||||
location?: string | null;
|
||||
cityCategoryPst?: string | null;
|
||||
layoutFormat?: string | null;
|
||||
tierCityCategory?: string | null;
|
||||
onBoardingCharges?: string | null;
|
||||
date?: string | null;
|
||||
singleFormatMonthYear?: string | null;
|
||||
domainId?: string | null;
|
||||
replacement?: string | null;
|
||||
terminationResignationStatus?: string | null;
|
||||
dateOfTerminationResignation?: string | null;
|
||||
lastDateOfOperations?: string | null;
|
||||
oldCodes?: string | null;
|
||||
branchDetails?: string | null;
|
||||
dealerPrincipalName?: string | null;
|
||||
dealerPrincipalEmailId?: string | null;
|
||||
dpContactNumber?: string | null;
|
||||
dpContacts?: string | null;
|
||||
showroomAddress?: string | null;
|
||||
showroomPincode?: string | null;
|
||||
workshopAddress?: string | null;
|
||||
workshopPincode?: string | null;
|
||||
locationDistrict?: string | null;
|
||||
stateWorkshop?: string | null;
|
||||
noOfStudios?: number | null;
|
||||
websiteUpdate?: string | null;
|
||||
gst?: string | null;
|
||||
pan?: string | null;
|
||||
firmType?: string | null;
|
||||
propManagingPartnersDirectors?: string | null;
|
||||
totalPropPartnersDirectors?: string | null;
|
||||
docsFolderLink?: string | null;
|
||||
workshopGmaCodes?: string | null;
|
||||
existingNew?: string | null;
|
||||
dlrcode?: string | null;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface DealerCreationAttributes extends Optional<DealerAttributes, 'dealerId' | 'isActive' | 'createdAt' | 'updatedAt'> {}
|
||||
|
||||
class Dealer extends Model<DealerAttributes, DealerCreationAttributes> implements DealerAttributes {
|
||||
public dealerId!: string;
|
||||
public salesCode?: string | null;
|
||||
public serviceCode?: string | null;
|
||||
public gearCode?: string | null;
|
||||
public gmaCode?: string | null;
|
||||
public region?: string | null;
|
||||
public dealership?: string | null;
|
||||
public state?: string | null;
|
||||
public district?: string | null;
|
||||
public city?: string | null;
|
||||
public location?: string | null;
|
||||
public cityCategoryPst?: string | null;
|
||||
public layoutFormat?: string | null;
|
||||
public tierCityCategory?: string | null;
|
||||
public onBoardingCharges?: string | null;
|
||||
public date?: string | null;
|
||||
public singleFormatMonthYear?: string | null;
|
||||
public domainId?: string | null;
|
||||
public replacement?: string | null;
|
||||
public terminationResignationStatus?: string | null;
|
||||
public dateOfTerminationResignation?: string | null;
|
||||
public lastDateOfOperations?: string | null;
|
||||
public oldCodes?: string | null;
|
||||
public branchDetails?: string | null;
|
||||
public dealerPrincipalName?: string | null;
|
||||
public dealerPrincipalEmailId?: string | null;
|
||||
public dpContactNumber?: string | null;
|
||||
public dpContacts?: string | null;
|
||||
public showroomAddress?: string | null;
|
||||
public showroomPincode?: string | null;
|
||||
public workshopAddress?: string | null;
|
||||
public workshopPincode?: string | null;
|
||||
public locationDistrict?: string | null;
|
||||
public stateWorkshop?: string | null;
|
||||
public noOfStudios?: number | null;
|
||||
public websiteUpdate?: string | null;
|
||||
public gst?: string | null;
|
||||
public pan?: string | null;
|
||||
public firmType?: string | null;
|
||||
public propManagingPartnersDirectors?: string | null;
|
||||
public totalPropPartnersDirectors?: string | null;
|
||||
public docsFolderLink?: string | null;
|
||||
public workshopGmaCodes?: string | null;
|
||||
public existingNew?: string | null;
|
||||
public dlrcode?: string | null;
|
||||
public isActive!: boolean;
|
||||
public readonly createdAt!: Date;
|
||||
public readonly updatedAt!: Date;
|
||||
}
|
||||
|
||||
Dealer.init(
|
||||
{
|
||||
dealerId: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
field: 'dealer_id'
|
||||
},
|
||||
salesCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'sales_code',
|
||||
comment: 'Sales Code'
|
||||
},
|
||||
serviceCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'service_code',
|
||||
comment: 'Service Code'
|
||||
},
|
||||
gearCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'gear_code',
|
||||
comment: 'Gear Code'
|
||||
},
|
||||
gmaCode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'gma_code',
|
||||
comment: 'GMA CODE'
|
||||
},
|
||||
region: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'Region'
|
||||
},
|
||||
dealership: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Dealership name'
|
||||
},
|
||||
state: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'State'
|
||||
},
|
||||
district: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'District'
|
||||
},
|
||||
city: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: 'City'
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
comment: 'Location'
|
||||
},
|
||||
cityCategoryPst: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'city_category_pst',
|
||||
comment: 'City category (PST)'
|
||||
},
|
||||
layoutFormat: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'layout_format',
|
||||
comment: 'Layout format'
|
||||
},
|
||||
tierCityCategory: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'tier_city_category',
|
||||
comment: 'TIER City Category'
|
||||
},
|
||||
onBoardingCharges: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'on_boarding_charges',
|
||||
comment: 'On Boarding Charges (stored as text to allow text values)'
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'DATE (stored as text to avoid format validation)'
|
||||
},
|
||||
singleFormatMonthYear: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'single_format_month_year',
|
||||
comment: 'Single Format of Month/Year (stored as text)'
|
||||
},
|
||||
domainId: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'domain_id',
|
||||
comment: 'Domain Id'
|
||||
},
|
||||
replacement: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Replacement (stored as text to allow longer values)'
|
||||
},
|
||||
terminationResignationStatus: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'termination_resignation_status',
|
||||
comment: 'Termination / Resignation under Proposal or Evaluation'
|
||||
},
|
||||
dateOfTerminationResignation: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'date_of_termination_resignation',
|
||||
comment: 'Date Of termination/ resignation (stored as text to avoid format validation)'
|
||||
},
|
||||
lastDateOfOperations: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'last_date_of_operations',
|
||||
comment: 'Last date of operations (stored as text to avoid format validation)'
|
||||
},
|
||||
oldCodes: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'old_codes',
|
||||
comment: 'Old Codes'
|
||||
},
|
||||
branchDetails: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'branch_details',
|
||||
comment: 'Branch Details'
|
||||
},
|
||||
dealerPrincipalName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'dealer_principal_name',
|
||||
comment: 'Dealer Principal Name'
|
||||
},
|
||||
dealerPrincipalEmailId: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'dealer_principal_email_id',
|
||||
comment: 'Dealer Principal Email Id'
|
||||
},
|
||||
dpContactNumber: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'dp_contact_number',
|
||||
comment: 'DP CONTACT NUMBER (stored as text to allow multiple numbers)'
|
||||
},
|
||||
dpContacts: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'dp_contacts',
|
||||
comment: 'DP CONTACTS (stored as text to allow multiple contacts)'
|
||||
},
|
||||
showroomAddress: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'showroom_address',
|
||||
comment: 'Showroom Address'
|
||||
},
|
||||
showroomPincode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
field: 'showroom_pincode',
|
||||
comment: 'Showroom Pincode'
|
||||
},
|
||||
workshopAddress: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'workshop_address',
|
||||
comment: 'Workshop Address'
|
||||
},
|
||||
workshopPincode: {
|
||||
type: DataTypes.STRING(10),
|
||||
allowNull: true,
|
||||
field: 'workshop_pincode',
|
||||
comment: 'Workshop Pincode'
|
||||
},
|
||||
locationDistrict: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'location_district',
|
||||
comment: 'Location / District'
|
||||
},
|
||||
stateWorkshop: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'state_workshop',
|
||||
comment: 'State (for workshop)'
|
||||
},
|
||||
noOfStudios: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
field: 'no_of_studios',
|
||||
comment: 'No Of Studios'
|
||||
},
|
||||
websiteUpdate: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'website_update',
|
||||
comment: 'Website update (stored as text to allow longer values)'
|
||||
},
|
||||
gst: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'GST'
|
||||
},
|
||||
pan: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'PAN'
|
||||
},
|
||||
firmType: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'firm_type',
|
||||
comment: 'Firm Type'
|
||||
},
|
||||
propManagingPartnersDirectors: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'prop_managing_partners_directors',
|
||||
comment: 'Prop. / Managing Partners / Managing Directors'
|
||||
},
|
||||
totalPropPartnersDirectors: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'total_prop_partners_directors',
|
||||
comment: 'Total Prop. / Partners / Directors'
|
||||
},
|
||||
docsFolderLink: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'docs_folder_link',
|
||||
comment: 'DOCS Folder Link'
|
||||
},
|
||||
workshopGmaCodes: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'workshop_gma_codes',
|
||||
comment: 'Workshop GMA Codes'
|
||||
},
|
||||
existingNew: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
field: 'existing_new',
|
||||
comment: 'Existing / New'
|
||||
},
|
||||
dlrcode: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: 'dlrcode'
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
field: 'is_active',
|
||||
comment: 'Whether the dealer is currently active'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'created_at'
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
field: 'updated_at'
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: 'dealers',
|
||||
modelName: 'Dealer',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['sales_code'],
|
||||
name: 'idx_dealers_sales_code'
|
||||
},
|
||||
{
|
||||
fields: ['service_code'],
|
||||
name: 'idx_dealers_service_code'
|
||||
},
|
||||
{
|
||||
fields: ['gma_code'],
|
||||
name: 'idx_dealers_gma_code'
|
||||
},
|
||||
{
|
||||
fields: ['domain_id'],
|
||||
name: 'idx_dealers_domain_id'
|
||||
},
|
||||
{
|
||||
fields: ['region'],
|
||||
name: 'idx_dealers_region'
|
||||
},
|
||||
{
|
||||
fields: ['state'],
|
||||
name: 'idx_dealers_state'
|
||||
},
|
||||
{
|
||||
fields: ['city'],
|
||||
name: 'idx_dealers_city'
|
||||
},
|
||||
{
|
||||
fields: ['district'],
|
||||
name: 'idx_dealers_district'
|
||||
},
|
||||
{
|
||||
fields: ['dlrcode'],
|
||||
name: 'idx_dealers_dlrcode'
|
||||
},
|
||||
{
|
||||
fields: ['is_active'],
|
||||
name: 'idx_dealers_is_active'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
export { Dealer };
|
||||
export type { DealerAttributes, DealerCreationAttributes };
|
||||
@ -23,6 +23,7 @@ import { DealerProposalCostItem } from './DealerProposalCostItem';
|
||||
import { WorkflowTemplate } from './WorkflowTemplate';
|
||||
import { InternalOrder } from './InternalOrder';
|
||||
import { ClaimBudgetTracking } from './ClaimBudgetTracking';
|
||||
import { Dealer } from './Dealer';
|
||||
|
||||
// Define associations
|
||||
const defineAssociations = () => {
|
||||
@ -166,7 +167,8 @@ export {
|
||||
DealerProposalCostItem,
|
||||
WorkflowTemplate,
|
||||
InternalOrder,
|
||||
ClaimBudgetTracking
|
||||
ClaimBudgetTracking,
|
||||
Dealer
|
||||
};
|
||||
|
||||
// Export default sequelize instance
|
||||
|
||||
@ -133,6 +133,7 @@ async function runMigrations(): Promise<void> {
|
||||
const m38 = require('../migrations/20251213-create-claim-invoice-credit-note-tables');
|
||||
const m39 = require('../migrations/20251214-create-dealer-completion-expenses');
|
||||
const m40 = require('../migrations/20251218-fix-claim-invoice-credit-note-columns');
|
||||
const m41 = require('../migrations/20250120-create-dealers-table');
|
||||
|
||||
const migrations = [
|
||||
{ name: '2025103000-create-users', module: m0 },
|
||||
@ -178,6 +179,7 @@ async function runMigrations(): Promise<void> {
|
||||
{ name: '20251213-create-claim-invoice-credit-note-tables', module: m38 },
|
||||
{ name: '20251214-create-dealer-completion-expenses', module: m39 },
|
||||
{ name: '20251218-fix-claim-invoice-credit-note-columns', module: m40 },
|
||||
{ name: '20250120-create-dealers-table', module: m41 },
|
||||
];
|
||||
|
||||
const queryInterface = sequelize.getQueryInterface();
|
||||
@ -273,7 +275,8 @@ async function autoSetup(): Promise<void> {
|
||||
console.log('✅ Setup completed successfully!');
|
||||
console.log('========================================\n');
|
||||
|
||||
console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.\n');
|
||||
console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.');
|
||||
console.log('📝 Note: Dealers table will be empty - import dealers using CSV import script.\n');
|
||||
|
||||
if (wasCreated) {
|
||||
console.log('💡 Next steps:');
|
||||
|
||||
@ -43,6 +43,7 @@ import * as m37 from '../migrations/20251213-drop-claim-details-invoice-columns'
|
||||
import * as m38 from '../migrations/20251213-create-claim-invoice-credit-note-tables';
|
||||
import * as m39 from '../migrations/20251214-create-dealer-completion-expenses';
|
||||
import * as m40 from '../migrations/20251218-fix-claim-invoice-credit-note-columns';
|
||||
import * as m41 from '../migrations/20250120-create-dealers-table';
|
||||
|
||||
interface Migration {
|
||||
name: string;
|
||||
@ -100,6 +101,7 @@ const migrations: Migration[] = [
|
||||
{ name: '20251213-create-claim-invoice-credit-note-tables', module: m38 },
|
||||
{ name: '20251214-create-dealer-completion-expenses', module: m39 },
|
||||
{ name: '20251218-fix-claim-invoice-credit-note-columns', module: m40 },
|
||||
{ name: '20250120-create-dealers-table', module: m41 },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
185
src/scripts/seed-dealers-table.ts
Normal file
185
src/scripts/seed-dealers-table.ts
Normal file
@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Seed Dealers Table
|
||||
* Populates the dealers table with sample dealer data
|
||||
*
|
||||
* Note: Update this script with your actual dealer data from the Excel/CSV file
|
||||
*/
|
||||
|
||||
import { sequelize } from '../config/database';
|
||||
import { Dealer } from '../models/Dealer';
|
||||
import { Op } from 'sequelize';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
interface DealerSeedData {
|
||||
salesCode?: string | null;
|
||||
serviceCode?: string | null;
|
||||
gearCode?: string | null;
|
||||
gmaCode?: string | null;
|
||||
region?: string | null;
|
||||
dealership?: string | null;
|
||||
state?: string | null;
|
||||
district?: string | null;
|
||||
city?: string | null;
|
||||
location?: string | null;
|
||||
cityCategoryPst?: string | null;
|
||||
layoutFormat?: string | null;
|
||||
tierCityCategory?: string | null;
|
||||
onBoardingCharges?: string | null;
|
||||
date?: string | null;
|
||||
singleFormatMonthYear?: string | null;
|
||||
domainId?: string | null;
|
||||
replacement?: string | null;
|
||||
terminationResignationStatus?: string | null;
|
||||
dateOfTerminationResignation?: string | null;
|
||||
lastDateOfOperations?: string | null;
|
||||
oldCodes?: string | null;
|
||||
branchDetails?: string | null;
|
||||
dealerPrincipalName?: string | null;
|
||||
dealerPrincipalEmailId?: string | null;
|
||||
dpContactNumber?: string | null;
|
||||
dpContacts?: string | null;
|
||||
showroomAddress?: string | null;
|
||||
showroomPincode?: string | null;
|
||||
workshopAddress?: string | null;
|
||||
workshopPincode?: string | null;
|
||||
locationDistrict?: string | null;
|
||||
stateWorkshop?: string | null;
|
||||
noOfStudios?: number | null;
|
||||
websiteUpdate?: string | null;
|
||||
gst?: string | null;
|
||||
pan?: string | null;
|
||||
firmType?: string | null;
|
||||
propManagingPartnersDirectors?: string | null;
|
||||
totalPropPartnersDirectors?: string | null;
|
||||
docsFolderLink?: string | null;
|
||||
workshopGmaCodes?: string | null;
|
||||
existingNew?: string | null;
|
||||
dlrcode?: string | null;
|
||||
}
|
||||
|
||||
// Sample data based on the provided table
|
||||
// TODO: Replace with your actual dealer data from Excel/CSV
|
||||
const dealersData: DealerSeedData[] = [
|
||||
{
|
||||
salesCode: '5124',
|
||||
serviceCode: '5125',
|
||||
gearCode: '5573',
|
||||
gmaCode: '9430',
|
||||
region: 'S3',
|
||||
dealership: 'Accelerate Motors',
|
||||
state: 'Karnataka',
|
||||
district: 'Bengaluru',
|
||||
city: 'Bengaluru',
|
||||
location: 'RAJA RAJESHWARI NAGAR',
|
||||
cityCategoryPst: 'A+',
|
||||
layoutFormat: 'A+',
|
||||
tierCityCategory: 'Tier 1 City',
|
||||
onBoardingCharges: null,
|
||||
date: '2014-09-30',
|
||||
singleFormatMonthYear: 'Sep-2014',
|
||||
domainId: 'acceleratemotors.rrnagar@dealer.royalenfield.com',
|
||||
replacement: null,
|
||||
terminationResignationStatus: null,
|
||||
dateOfTerminationResignation: null,
|
||||
lastDateOfOperations: null,
|
||||
oldCodes: null,
|
||||
branchDetails: null,
|
||||
dealerPrincipalName: 'N. Shyam Charmanna',
|
||||
dealerPrincipalEmailId: 'shyamcharmanna@yahoo.co.in',
|
||||
dpContactNumber: '7022049621',
|
||||
dpContacts: '7022049621',
|
||||
showroomAddress: 'No.335, HVP RR Nagar Sector B, Ideal Homes Town Ship, Bangalore - 560098, Dist – Bangalore, Karnataka',
|
||||
showroomPincode: '560098',
|
||||
workshopAddress: 'Works Shop No.460, 80ft Road, 2nd Phase R R Nagar, Bangalore - 560098, Dist – Bangalore, Karnataka',
|
||||
workshopPincode: '560098',
|
||||
locationDistrict: 'Bangalore',
|
||||
stateWorkshop: 'Karnataka',
|
||||
noOfStudios: 0,
|
||||
websiteUpdate: 'Yes',
|
||||
gst: '29ARCPS1311D1Z6',
|
||||
pan: 'ARCPS1311D',
|
||||
firmType: 'Proprietorship',
|
||||
propManagingPartnersDirectors: 'CHARMANNA SHYAM NELLAMAKADA',
|
||||
totalPropPartnersDirectors: 'CHARMANNA SHYAM NELLAMAKADA',
|
||||
docsFolderLink: 'https://drive.google.com/drive/folders/1sGtg3s1h9aBXX9fhxJufYuBWar8gVvnb',
|
||||
workshopGmaCodes: null,
|
||||
existingNew: null,
|
||||
dlrcode: '3386'
|
||||
}
|
||||
// Add more dealer records here from your Excel/CSV data
|
||||
];
|
||||
|
||||
async function seedDealersTable(): Promise<void> {
|
||||
try {
|
||||
logger.info('[Seed Dealers Table] Starting dealers table seeding...');
|
||||
|
||||
for (const dealerData of dealersData) {
|
||||
// Use dlrcode or domainId as unique identifier if available
|
||||
const uniqueIdentifier = dealerData.dlrcode || dealerData.domainId || dealerData.salesCode;
|
||||
|
||||
if (!uniqueIdentifier) {
|
||||
logger.warn('[Seed Dealers Table] Skipping dealer record without unique identifier');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if dealer already exists (using dlrcode, domainId, or salesCode)
|
||||
const whereConditions: any[] = [];
|
||||
if (dealerData.dlrcode) whereConditions.push({ dlrcode: dealerData.dlrcode });
|
||||
if (dealerData.domainId) whereConditions.push({ domainId: dealerData.domainId });
|
||||
if (dealerData.salesCode) whereConditions.push({ salesCode: dealerData.salesCode });
|
||||
|
||||
const existingDealer = whereConditions.length > 0
|
||||
? await Dealer.findOne({
|
||||
where: {
|
||||
[Op.or]: whereConditions
|
||||
}
|
||||
})
|
||||
: null;
|
||||
|
||||
if (existingDealer) {
|
||||
logger.info(`[Seed Dealers Table] Dealer ${uniqueIdentifier} already exists, updating...`);
|
||||
|
||||
// Update existing dealer
|
||||
await existingDealer.update({
|
||||
...dealerData,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
logger.info(`[Seed Dealers Table] ✅ Updated dealer: ${uniqueIdentifier}`);
|
||||
} else {
|
||||
// Create new dealer
|
||||
await Dealer.create({
|
||||
...dealerData,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
logger.info(`[Seed Dealers Table] ✅ Created dealer: ${uniqueIdentifier}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('[Seed Dealers Table] ✅ Dealers table seeding completed successfully');
|
||||
} catch (error) {
|
||||
logger.error('[Seed Dealers Table] ❌ Error seeding dealers table:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
sequelize
|
||||
.authenticate()
|
||||
.then(() => {
|
||||
logger.info('[Seed Dealers Table] Database connection established');
|
||||
return seedDealersTable();
|
||||
})
|
||||
.then(() => {
|
||||
logger.info('[Seed Dealers Table] Seeding completed');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('[Seed Dealers Table] Seeding failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { seedDealersTable, dealersData };
|
||||
@ -122,7 +122,7 @@ export class DashboardService {
|
||||
engagement,
|
||||
aiInsights
|
||||
] = await Promise.all([
|
||||
this.getRequestStats(userId, dateRange, startDate, endDate, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, viewAsUser),
|
||||
this.getRequestStats(userId, dateRange, startDate, endDate, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, viewAsUser),
|
||||
this.getTATEfficiency(userId, dateRange, startDate, endDate, viewAsUser),
|
||||
this.getApproverLoad(userId, dateRange, startDate, endDate, viewAsUser),
|
||||
this.getEngagementStats(userId, dateRange, startDate, endDate, viewAsUser),
|
||||
@ -153,6 +153,7 @@ export class DashboardService {
|
||||
endDate?: string,
|
||||
status?: string,
|
||||
priority?: string,
|
||||
templateType?: string,
|
||||
department?: string,
|
||||
initiator?: string,
|
||||
approver?: string,
|
||||
@ -162,7 +163,8 @@ export class DashboardService {
|
||||
viewAsUser?: boolean
|
||||
) {
|
||||
// Check if date range should be applied
|
||||
const applyDateRange = dateRange !== undefined && dateRange !== null;
|
||||
// 'all' means no date filter - show all requests regardless of date
|
||||
const applyDateRange = dateRange !== undefined && dateRange !== null && dateRange !== 'all';
|
||||
const range = applyDateRange ? this.parseDateRange(dateRange, startDate, endDate) : null;
|
||||
|
||||
// Check if user is admin or management (has broader access)
|
||||
@ -205,6 +207,18 @@ export class DashboardService {
|
||||
replacements.priority = priority.toUpperCase();
|
||||
}
|
||||
|
||||
// TemplateType filter
|
||||
if (templateType && templateType !== 'all') {
|
||||
const templateTypeUpper = templateType.toUpperCase();
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
// For CUSTOM, include both CUSTOM and null (legacy requests)
|
||||
filterConditions += ` AND (wf.template_type = 'CUSTOM' OR wf.template_type IS NULL)`;
|
||||
} else {
|
||||
filterConditions += ` AND wf.template_type = :templateType`;
|
||||
replacements.templateType = templateTypeUpper;
|
||||
}
|
||||
}
|
||||
|
||||
// Department filter (through initiator)
|
||||
if (department && department !== 'all') {
|
||||
filterConditions += ` AND EXISTS (
|
||||
@ -279,11 +293,16 @@ export class DashboardService {
|
||||
|
||||
// Organization Level: Admin/Management see ALL requests across organization
|
||||
// Personal Level: Regular users see requests where they are INVOLVED (initiator, approver, or participant)
|
||||
// Note: If dateRange is provided, filter by submission_date. Otherwise, show all requests.
|
||||
// Note: If dateRange is provided, filter by submission_date (or createdAt if submission_date is null). Otherwise, show all requests.
|
||||
// For pending/open requests, if no date range, count ALL pending requests regardless of creation date
|
||||
// For approved/rejected/closed, if date range is provided, count only those submitted in date range
|
||||
// Match the same logic as listParticipantRequests: include requests where submission_date is in range OR (submission_date is null AND created_at is in range)
|
||||
const dateFilterClause = applyDateRange
|
||||
? `wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL`
|
||||
? `(
|
||||
(wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL)
|
||||
OR
|
||||
(wf.submission_date IS NULL AND wf.created_at BETWEEN :start AND :end)
|
||||
)`
|
||||
: `1=1`; // No date filter - show all requests
|
||||
|
||||
// Build user-level filter: Include requests where user is initiator, approver, or participant
|
||||
@ -313,8 +332,13 @@ export class DashboardService {
|
||||
|
||||
// For pending requests, if no date range is applied, don't filter by date at all
|
||||
// This ensures pending requests are always counted regardless of submission date
|
||||
// Match the same logic as listParticipantRequests: include requests where submission_date is in range OR (submission_date is null AND created_at is in range)
|
||||
const pendingDateFilterClause = applyDateRange
|
||||
? `wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL`
|
||||
? `(
|
||||
(wf.submission_date BETWEEN :start AND :end AND wf.submission_date IS NOT NULL)
|
||||
OR
|
||||
(wf.submission_date IS NULL AND wf.created_at BETWEEN :start AND :end)
|
||||
)`
|
||||
: `1=1`; // No date filter for pending requests
|
||||
|
||||
let whereClauseForPending = `
|
||||
|
||||
@ -560,7 +560,7 @@ export class WorkflowService {
|
||||
* Shows ALL requests in the organization, including where admin is initiator
|
||||
* Used by: "All Requests" page for admin users
|
||||
*/
|
||||
async listWorkflows(page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; department?: string; initiator?: string; approver?: string; approverType?: 'current' | 'any'; slaCompliance?: string; dateRange?: string; startDate?: string; endDate?: string }) {
|
||||
async listWorkflows(page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string; department?: string; initiator?: string; approver?: string; approverType?: 'current' | 'any'; slaCompliance?: string; dateRange?: string; startDate?: string; endDate?: string }) {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Build where clause with filters
|
||||
@ -605,6 +605,22 @@ export class WorkflowService {
|
||||
whereConditions.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
whereConditions.push({
|
||||
[Op.or]: [
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
whereConditions.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
whereConditions.push({
|
||||
@ -1371,6 +1387,7 @@ export class WorkflowService {
|
||||
search?: string;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
templateType?: string;
|
||||
department?: string;
|
||||
initiator?: string;
|
||||
approver?: string;
|
||||
@ -1459,6 +1476,22 @@ export class WorkflowService {
|
||||
whereConditions.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
whereConditions.push({
|
||||
[Op.or]: [
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
whereConditions.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
whereConditions.push({
|
||||
@ -1664,6 +1697,7 @@ export class WorkflowService {
|
||||
search?: string;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
templateType?: string;
|
||||
department?: string;
|
||||
slaCompliance?: string;
|
||||
dateRange?: string;
|
||||
@ -1703,6 +1737,22 @@ export class WorkflowService {
|
||||
whereConditions.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
whereConditions.push({
|
||||
[Op.or]: [
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
whereConditions.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
whereConditions.push({
|
||||
@ -1841,7 +1891,7 @@ export class WorkflowService {
|
||||
return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
|
||||
}
|
||||
|
||||
async listOpenForMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string }, sortBy?: string, sortOrder?: string) {
|
||||
async listOpenForMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string }, sortBy?: string, sortOrder?: string) {
|
||||
const offset = (page - 1) * limit;
|
||||
// Find all pending/in-progress/paused approval levels across requests ordered by levelNumber
|
||||
// Include PAUSED status so paused requests where user is the current approver are shown
|
||||
@ -1972,6 +2022,22 @@ export class WorkflowService {
|
||||
baseConditions.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
baseConditions.push({
|
||||
[Op.or]: [
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
baseConditions.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
baseConditions.push({
|
||||
@ -2076,7 +2142,7 @@ export class WorkflowService {
|
||||
return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
|
||||
}
|
||||
|
||||
async listClosedByMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string }, sortBy?: string, sortOrder?: string) {
|
||||
async listClosedByMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string }, sortBy?: string, sortOrder?: string) {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Get requests where user participated as approver
|
||||
@ -2151,21 +2217,37 @@ export class WorkflowService {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply priority filter
|
||||
if (filters?.priority && filters.priority !== 'all') {
|
||||
approverConditionParts.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
// Apply priority filter
|
||||
if (filters?.priority && filters.priority !== 'all') {
|
||||
approverConditionParts.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
approverConditionParts.push({
|
||||
[Op.or]: [
|
||||
{ title: { [Op.iLike]: `%${filters.search.trim()}%` } },
|
||||
{ description: { [Op.iLike]: `%${filters.search.trim()}%` } },
|
||||
{ requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
approverConditionParts.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
approverConditionParts.push({
|
||||
[Op.or]: [
|
||||
{ title: { [Op.iLike]: `%${filters.search.trim()}%` } },
|
||||
{ description: { [Op.iLike]: `%${filters.search.trim()}%` } },
|
||||
{ requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const approverCondition = approverConditionParts.length > 0
|
||||
? { [Op.and]: approverConditionParts }
|
||||
@ -2219,6 +2301,22 @@ export class WorkflowService {
|
||||
initiatorConditionParts.push({ priority: filters.priority.toUpperCase() });
|
||||
}
|
||||
|
||||
// Apply templateType filter
|
||||
if (filters?.templateType && filters.templateType !== 'all') {
|
||||
const templateTypeUpper = filters.templateType.toUpperCase();
|
||||
// For CUSTOM, also include null values (legacy requests without templateType)
|
||||
if (templateTypeUpper === 'CUSTOM') {
|
||||
initiatorConditionParts.push({
|
||||
[Op.or]: [
|
||||
{ templateType: 'CUSTOM' },
|
||||
{ templateType: null }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
initiatorConditionParts.push({ templateType: templateTypeUpper });
|
||||
}
|
||||
}
|
||||
|
||||
// Apply search filter (title, description, or requestNumber)
|
||||
if (filters?.search && filters.search.trim()) {
|
||||
initiatorConditionParts.push({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user