Initial commit including .env

This commit is contained in:
2026-06-22 17:43:40 +05:30
commit c577d47b75
286 changed files with 85878 additions and 0 deletions

View 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
View 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';")
}