Initial commit including .env
This commit is contained in:
181
cmd/import_enquiry/import.py
Normal file
181
cmd/import_enquiry/import.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
Import Enquiry.xlsx data → generates enquiry_import.sql
|
||||
Run locally, then copy the .sql file to the server and execute with:
|
||||
psql -U admin -d logistics -p 5433 -f enquiry_import.sql
|
||||
|
||||
Usage:
|
||||
python cmd/import_enquiry/import.py
|
||||
"""
|
||||
|
||||
import openpyxl
|
||||
import os
|
||||
import sys
|
||||
|
||||
XLSX = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "Enquiry.xlsx")
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "enquiry_import.sql")
|
||||
|
||||
|
||||
def esc(v):
|
||||
"""Escape a value for SQL single-quote string."""
|
||||
if v is None:
|
||||
return "NULL"
|
||||
s = str(v).strip()
|
||||
if s.endswith(".0") and s[:-2].isdigit():
|
||||
s = s[:-2] # strip Excel float suffix from phone numbers
|
||||
s = s.replace("'", "''")
|
||||
return f"'{s}'"
|
||||
|
||||
|
||||
def clean(v):
|
||||
if v is None:
|
||||
return ""
|
||||
s = str(v).strip()
|
||||
if s.endswith(".0") and s[:-2].isdigit():
|
||||
s = s[:-2]
|
||||
return s
|
||||
|
||||
|
||||
def build_branches(ws):
|
||||
lines = []
|
||||
current_company = None
|
||||
count = 0
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
if not any(v is not None for v in row):
|
||||
continue
|
||||
company = clean(row[6])
|
||||
if company:
|
||||
if company == "Company":
|
||||
continue
|
||||
current_company = company
|
||||
if not current_company:
|
||||
continue
|
||||
|
||||
area = esc(clean(row[7]))
|
||||
phone = esc(clean(row[8]))
|
||||
plus_code = esc(clean(row[9]))
|
||||
address = esc(clean(row[10]))
|
||||
rate = esc(clean(row[1]))
|
||||
pickup = esc(clean(row[2]))
|
||||
drop = esc(clean(row[3]))
|
||||
days = esc(clean(row[4]))
|
||||
packing = esc(clean(row[5]))
|
||||
co = esc(current_company)
|
||||
|
||||
lines.append(
|
||||
f"INSERT INTO competitor_branches "
|
||||
f"(company,area,phone,plus_code,address,rate_per_kg,offers_pickup,offers_drop,time_in_days,packing_charge,created_at,updated_at) "
|
||||
f"VALUES ({co},{area},{phone},{plus_code},{address},{rate},{pickup},{drop},{days},{packing},NOW(),NOW());"
|
||||
)
|
||||
count += 1
|
||||
return lines, count
|
||||
|
||||
|
||||
def build_pricing(ws):
|
||||
lines = []
|
||||
|
||||
all_cells = {}
|
||||
for row in ws.iter_rows():
|
||||
for cell in row:
|
||||
if cell.value is not None:
|
||||
all_cells[(cell.row, cell.column)] = cell.value
|
||||
|
||||
company_row = {col: val for (r, col), val in all_cells.items() if r == 5}
|
||||
header_row = {col: val for (r, col), val in all_cells.items() if r == 6}
|
||||
company_cols = sorted(company_row.items())
|
||||
|
||||
ranges = []
|
||||
for i, (col, name) in enumerate(company_cols):
|
||||
end = company_cols[i + 1][0] - 1 if i + 1 < len(company_cols) else max(header_row.keys())
|
||||
ranges.append((name, col, end))
|
||||
|
||||
geo_keywords = {"local", "city", "state", "metro", "national", "zonal",
|
||||
"regional", "south", "rest", "inter", "remote", "kerala",
|
||||
"karnataka", "priority", "surface", "air", "express",
|
||||
"standard", "within", "nearby", "international"}
|
||||
|
||||
count = 0
|
||||
for company, col_start, col_end in ranges:
|
||||
sub_hdrs = {col: val for col, val in header_row.items()
|
||||
if col_start <= col <= col_end}
|
||||
if not sub_hdrs:
|
||||
continue
|
||||
|
||||
cols_sorted = sorted(sub_hdrs.keys())
|
||||
weight_col = cols_sorted[0]
|
||||
zone_cols = cols_sorted[1:]
|
||||
|
||||
has_delivery_time = any("delivery time" in str(v).lower()
|
||||
for v in sub_hdrs.values())
|
||||
|
||||
data_rows_by_row = {}
|
||||
for (r, col), val in all_cells.items():
|
||||
if r >= 7 and col_start <= col <= col_end:
|
||||
data_rows_by_row.setdefault(r, {})[col] = val
|
||||
|
||||
for r in sorted(data_rows_by_row.keys()):
|
||||
row_data = data_rows_by_row[r]
|
||||
weight_slab = clean(row_data.get(weight_col, ""))
|
||||
if not weight_slab:
|
||||
continue
|
||||
|
||||
delivery_time = ""
|
||||
zone_value_cols = zone_cols
|
||||
|
||||
if has_delivery_time and zone_cols:
|
||||
delivery_time = clean(row_data.get(zone_cols[0], ""))
|
||||
zone_value_cols = zone_cols[1:]
|
||||
|
||||
for zc in zone_value_cols:
|
||||
zone_label = clean(sub_hdrs.get(zc, ""))
|
||||
rate_val = clean(row_data.get(zc, ""))
|
||||
if not rate_val:
|
||||
continue
|
||||
|
||||
zone_words = set(zone_label.lower().split())
|
||||
if geo_keywords & zone_words:
|
||||
zone = zone_label
|
||||
service_type = ""
|
||||
else:
|
||||
service_type = zone_label
|
||||
zone = ""
|
||||
|
||||
lines.append(
|
||||
f"INSERT INTO carrier_pricing "
|
||||
f"(company,weight_slab,service_type,zone,rate,delivery_time,created_at,updated_at) "
|
||||
f"VALUES ({esc(company)},{esc(weight_slab)},{esc(service_type)},{esc(zone)},{esc(rate_val)},{esc(delivery_time)},NOW(),NOW());"
|
||||
)
|
||||
count += 1
|
||||
|
||||
return lines, count
|
||||
|
||||
|
||||
def main():
|
||||
print(f"Reading {XLSX} …")
|
||||
wb = openpyxl.load_workbook(XLSX)
|
||||
|
||||
branch_lines, b_count = build_branches(wb["Enquiry"])
|
||||
pricing_lines, p_count = build_pricing(wb["Price per Kilometer"])
|
||||
|
||||
total = b_count + p_count
|
||||
print(f" competitor_branches : {b_count} rows")
|
||||
print(f" carrier_pricing : {p_count} rows")
|
||||
print(f" total : {total} INSERT statements")
|
||||
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
f.write("-- Doormile Enquiry import — generated by import.py\n")
|
||||
f.write("BEGIN;\n\n")
|
||||
f.write("-- ── Sheet 1: Competitor Branches ───────────────────────────\n")
|
||||
for line in branch_lines:
|
||||
f.write(line + "\n")
|
||||
f.write("\n-- ── Sheet 2: Carrier Pricing ──────────────────────────────\n")
|
||||
for line in pricing_lines:
|
||||
f.write(line + "\n")
|
||||
f.write("\nCOMMIT;\n")
|
||||
|
||||
print(f"\nDone. Written to: {OUT}")
|
||||
print("\nTo import on the server:")
|
||||
print(" psql -U admin -d logistics -p 5433 -f enquiry_import.sql")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
269
cmd/migrate_qdrant/main.go
Normal file
269
cmd/migrate_qdrant/main.go
Normal file
@@ -0,0 +1,269 @@
|
||||
// One-time migration: moves Qdrant auth users and client records into PostgreSQL.
|
||||
// Run from the DoormileBackend directory: go run ./cmd/migrate_qdrant
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func hash(pw string) string {
|
||||
b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Fatalf("bcrypt error: %v", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ = godotenv.Load()
|
||||
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata",
|
||||
getenv("DB_HOST", "127.0.0.1"),
|
||||
getenv("DB_USER", "admin"),
|
||||
getenv("DB_PASSWORD", "Package@321#"),
|
||||
getenv("DB_NAME", "logistics"),
|
||||
getenv("DB_PORT", "5433"),
|
||||
)
|
||||
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
log.Fatalf("open: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
if err = db.Ping(); err != nil {
|
||||
log.Fatalf("ping: %v", err)
|
||||
}
|
||||
log.Println("✅ Connected to PostgreSQL")
|
||||
|
||||
// ── 1. Ensure client_id is nullable ──────────────────────────────────────
|
||||
log.Println("🔧 Ensuring client_id is nullable in doormile_auth …")
|
||||
_, _ = db.Exec(`ALTER TABLE doormile_auth ALTER COLUMN client_id DROP NOT NULL`)
|
||||
|
||||
// ── 2. Migrate doormile_auth ──────────────────────────────────────────────
|
||||
// Source: Qdrant doormile_auth collection (5 users, original PIN = 1234)
|
||||
// admin@doormile.com added to match the hardcoded React console login.
|
||||
log.Println("\n📋 Migrating auth users …")
|
||||
|
||||
pin := "1234"
|
||||
|
||||
type authUser struct {
|
||||
email string
|
||||
role string
|
||||
password string
|
||||
}
|
||||
users := []authUser{
|
||||
{"suriya@tenext.in", "admin", "admin"},
|
||||
{"admin@doormile.com", "admin", "admin"},
|
||||
{"kamesh@doormile.com", "user", pin},
|
||||
{"parthiban@doormile.com", "user", pin},
|
||||
{"fazul@doormile.com", "user", pin},
|
||||
{"care@doormile.com", "user", pin},
|
||||
}
|
||||
|
||||
authInsert := `
|
||||
INSERT INTO doormile_auth (email, password_hash, role, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, NOW(), NOW())
|
||||
ON CONFLICT (email) DO NOTHING`
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Exec(authInsert, u.email, hash(u.password), u.role); err != nil {
|
||||
log.Printf(" ⚠️ skip %s: %v", u.email, err)
|
||||
} else {
|
||||
log.Printf(" ✓ %s [%s]", u.email, u.role)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Migrate doormile_clients ───────────────────────────────────────────
|
||||
// Exact data from Qdrant payloads provided by user.
|
||||
log.Println("\n📋 Migrating clients …")
|
||||
|
||||
var fazulID uint64
|
||||
_ = db.QueryRow(`SELECT id FROM doormile_auth WHERE email = 'fazul@doormile.com'`).Scan(&fazulID)
|
||||
|
||||
type client struct {
|
||||
firstName string
|
||||
lastName string
|
||||
phone string
|
||||
city string
|
||||
state string
|
||||
neighbourhood string
|
||||
pincode string
|
||||
businessType string
|
||||
status string
|
||||
shippingFrequency string
|
||||
logisticsSegment string
|
||||
transitFrom string
|
||||
transitTo string
|
||||
parcelVolume float64
|
||||
activeContracts int
|
||||
logisticsProvider string
|
||||
providerEfficiency string
|
||||
dataConsent string
|
||||
notes string
|
||||
surveyLat float64
|
||||
surveyLong float64
|
||||
surveyAddress string
|
||||
surveyZone string
|
||||
surveyPincode string
|
||||
registeredByID uint64
|
||||
}
|
||||
|
||||
clients := []client{
|
||||
{
|
||||
// clientId: suriya_001
|
||||
firstName: "Suriya",
|
||||
phone: "PENDING_004", // phone was empty in Qdrant payload — update manually
|
||||
city: "Coimbatore",
|
||||
state: "Tamil Nadu",
|
||||
neighbourhood: "RS Puram",
|
||||
businessType: "ecommerce",
|
||||
status: "newClient",
|
||||
shippingFrequency: "Weekly",
|
||||
transitFrom: "Coimbatore",
|
||||
transitTo: "Chennai",
|
||||
parcelVolume: 200,
|
||||
activeContracts: 5,
|
||||
logisticsProvider: "DTDC Express",
|
||||
providerEfficiency: "High Efficiency",
|
||||
dataConsent: "full",
|
||||
registeredByID: 0,
|
||||
},
|
||||
{
|
||||
// clientId: client_1780462654467
|
||||
firstName: "Abhishek",
|
||||
phone: "6374605674",
|
||||
city: "Perur",
|
||||
state: "Tamil Nadu",
|
||||
neighbourhood: "RS Puram",
|
||||
businessType: "wholesale",
|
||||
status: "newClient",
|
||||
shippingFrequency: "Weekly",
|
||||
logisticsSegment: "First Mile, Last Mile",
|
||||
transitFrom: "Perur",
|
||||
transitTo: "Bengaluru",
|
||||
parcelVolume: 0,
|
||||
activeContracts: 0,
|
||||
logisticsProvider: "Not disclosed",
|
||||
providerEfficiency: "Not disclosed",
|
||||
dataConsent: "basicOnly",
|
||||
surveyLat: 11.0050875,
|
||||
surveyLong: 76.9508337,
|
||||
surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore",
|
||||
surveyZone: "RS Puram",
|
||||
registeredByID: 0,
|
||||
},
|
||||
{
|
||||
// clientId: client_1780558882620
|
||||
firstName: "Doormile",
|
||||
phone: "9876543210",
|
||||
city: "Coimbatore",
|
||||
state: "Tamil Nadu",
|
||||
neighbourhood: "RS Puram",
|
||||
businessType: "retail",
|
||||
status: "newClient",
|
||||
shippingFrequency: "Daily",
|
||||
logisticsSegment: "First Mile, Last Mile",
|
||||
transitFrom: "Coimbatore",
|
||||
transitTo: "Bengaluru, Mumbai",
|
||||
parcelVolume: 0,
|
||||
activeContracts: 0,
|
||||
logisticsProvider: "Not disclosed",
|
||||
providerEfficiency: "Not disclosed",
|
||||
dataConsent: "basicOnly",
|
||||
surveyLat: 11.0050816,
|
||||
surveyLong: 76.9508575,
|
||||
surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore",
|
||||
surveyZone: "RS Puram",
|
||||
registeredByID: 0,
|
||||
},
|
||||
{
|
||||
// clientId: client_1781506364119 — recorded by Fazul
|
||||
firstName: "Jonathan",
|
||||
phone: "9629415740",
|
||||
city: "Perur",
|
||||
state: "Tamil Nadu",
|
||||
neighbourhood: "RS Puram",
|
||||
pincode: "641007",
|
||||
businessType: "wholesale",
|
||||
status: "newClient",
|
||||
shippingFrequency: "Daily",
|
||||
logisticsSegment: "First Mile, Middle Mile, Last Mile",
|
||||
transitFrom: "Perur",
|
||||
transitTo: "Chennai, Coimbatore, Madurai",
|
||||
parcelVolume: 450,
|
||||
activeContracts: 12,
|
||||
logisticsProvider: "XpressBees",
|
||||
providerEfficiency: "High Efficiency",
|
||||
dataConsent: "full",
|
||||
surveyLat: 11.0050812,
|
||||
surveyLong: 76.9508625,
|
||||
surveyAddress: "Indian Bank, Diwan Bahadur Road, RS Puram, Ward 24, Coimbatore",
|
||||
surveyZone: "RS Puram",
|
||||
surveyPincode: "641007",
|
||||
registeredByID: fazulID,
|
||||
},
|
||||
}
|
||||
|
||||
clientInsert := `
|
||||
INSERT INTO doormile_clients (
|
||||
first_name, last_name, phone,
|
||||
city, state, neighbourhood, pincode,
|
||||
surveylat, surveylong, survey_address, survey_zone, survey_pincode,
|
||||
business_type, status,
|
||||
shipping_frequency, logistics_segment,
|
||||
transit_from, transit_to,
|
||||
parcel_volume, active_contracts,
|
||||
logistics_provider, provider_efficiency,
|
||||
data_consent, notes,
|
||||
registered_by_id,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
$1,$2,$3,$4,$5,$6,$7,
|
||||
$8,$9,$10,$11,$12,
|
||||
$13,$14,$15,$16,$17,$18,
|
||||
$19,$20,$21,$22,$23,$24,
|
||||
$25,
|
||||
NOW(),NOW()
|
||||
)
|
||||
ON CONFLICT (phone) DO NOTHING`
|
||||
|
||||
for _, cl := range clients {
|
||||
_, err := db.Exec(clientInsert,
|
||||
cl.firstName, cl.lastName, cl.phone,
|
||||
cl.city, cl.state, cl.neighbourhood, cl.pincode,
|
||||
cl.surveyLat, cl.surveyLong, cl.surveyAddress, cl.surveyZone, cl.surveyPincode,
|
||||
cl.businessType, cl.status,
|
||||
cl.shippingFrequency, cl.logisticsSegment,
|
||||
cl.transitFrom, cl.transitTo,
|
||||
cl.parcelVolume, cl.activeContracts,
|
||||
cl.logisticsProvider, cl.providerEfficiency,
|
||||
cl.dataConsent, cl.notes,
|
||||
cl.registeredByID,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf(" ⚠️ skip %s: %v", cl.firstName, err)
|
||||
} else {
|
||||
log.Printf(" ✓ %s — %s (%s) phone=%s", cl.firstName, cl.city, cl.businessType, cl.phone)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("\n✅ Migration complete.")
|
||||
log.Println("\n⚠️ ACTION REQUIRED — Suriya client has no phone on record.")
|
||||
log.Println(" Once you have Suriya's phone number run:")
|
||||
log.Println(" UPDATE doormile_clients SET phone = '<number>' WHERE phone = 'PENDING_004';")
|
||||
}
|
||||
Reference in New Issue
Block a user