commit 6ab508560fe9487766b4afdb2b256194ab39bd18 Author: Gokul Date: Mon May 25 11:45:56 2026 +0530 intial commit diff --git a/.env b/.env new file mode 100644 index 0000000..74245a3 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +APP_PORT=1009 +DB_HOST=66.116.207.225 +DB_PORT=6432 + +DB_NAME=nearledb +DB_USER=admin +DB_PASSWORD=Package@123# + + +DO_SPACES_REGION=sgp1 +DO_SPACES_ENDPOINT=sgp1.digitaloceanspaces.com +DO_SPACES_BUCKET=nearle +DO_SPACES_ACCESS_KEY=DO00NQER7N2FRYZAB2HR +DO_SPACES_SECRET_KEY=nMDewX25IBEu1FM5dakK+v28/WbW3TzBAwq913+dxP0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b24d71e --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d543175 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Connect to server", + "type": "go", + "request": "attach", + "mode": "remote", + "remotePath": "${workspaceFolder}", + "port": 2345, + "host": "127.0.0.1" + } + + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd88c45 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# First Stage +FROM golang:1.24 + +RUN mkdir /app +ADD . /app/ +WORKDIR /app +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server . + +# Second Stage +FROM alpine + +# Fix: Alpine needs ca-certificates to verify Google API SSL certificates +RUN apk add --no-cache ca-certificates + +# Copy from first stage +COPY --from=0 /app/server /app/server +COPY nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json /app/ + +WORKDIR /app + +# Default startup command +CMD ["./server"] + + diff --git a/backups/migration_gps_fields.sql b/backups/migration_gps_fields.sql new file mode 100644 index 0000000..a8c380f --- /dev/null +++ b/backups/migration_gps_fields.sql @@ -0,0 +1,35 @@ +-- ============================================================================= +-- Migration: Add GPS Tracking Fields +-- Date: 2026-03-11 +-- Description: Adds raw GPS, velocity, speed and heading columns to the +-- deliveries and deliverylogs tables. +-- +-- All columns are nullable so that: +-- 1. Old rider app versions that don't send these fields continue to work. +-- 2. Existing rows are not affected (they will have NULL values). +-- +-- Safe to re-run: each ALTER uses IF NOT EXISTS column check pattern. +-- ============================================================================= + +-- ----------------------------------------------------------------------------- +-- Table: deliverylogs +-- ----------------------------------------------------------------------------- +ALTER TABLE deliverylogs + ADD COLUMN IF NOT EXISTS raw_latitude VARCHAR(20) NULL DEFAULT NULL COMMENT 'Raw (unfiltered) GPS latitude from device', + ADD COLUMN IF NOT EXISTS raw_longitude VARCHAR(20) NULL DEFAULT NULL COMMENT 'Raw (unfiltered) GPS longitude from device', + ADD COLUMN IF NOT EXISTS velocity_lat VARCHAR(20) NULL DEFAULT NULL COMMENT 'Kalman-filtered velocity component (latitude direction)', + ADD COLUMN IF NOT EXISTS velocity_lng VARCHAR(20) NULL DEFAULT NULL COMMENT 'Kalman-filtered velocity component (longitude direction)', + ADD COLUMN IF NOT EXISTS speed VARCHAR(10) NULL DEFAULT NULL COMMENT 'Calculated speed in m/s or km/h', + ADD COLUMN IF NOT EXISTS heading VARCHAR(10) NULL DEFAULT NULL COMMENT 'Bearing/direction in degrees (0-360)'; + +-- ============================================================================= +-- Verification queries (run after migration to confirm columns exist): +-- ============================================================================= +-- SHOW COLUMNS FROM deliveries LIKE 'raw_%'; +-- SHOW COLUMNS FROM deliveries LIKE 'velocity_%'; +-- SHOW COLUMNS FROM deliveries LIKE 'speed'; +-- SHOW COLUMNS FROM deliveries LIKE 'heading'; +-- SHOW COLUMNS FROM deliverylogs LIKE 'raw_%'; +-- SHOW COLUMNS FROM deliverylogs LIKE 'velocity_%'; +-- SHOW COLUMNS FROM deliverylogs LIKE 'speed'; +-- SHOW COLUMNS FROM deliverylogs LIKE 'heading'; diff --git a/backups/parquet/deliveries/deliveries_2026_01_27.parquet b/backups/parquet/deliveries/deliveries_2026_01_27.parquet new file mode 100644 index 0000000..4766554 Binary files /dev/null and b/backups/parquet/deliveries/deliveries_2026_01_27.parquet differ diff --git a/backups/parquet/deliveries/deliveries_2026_01_30.parquet b/backups/parquet/deliveries/deliveries_2026_01_30.parquet new file mode 100644 index 0000000..34816a8 Binary files /dev/null and b/backups/parquet/deliveries/deliveries_2026_01_30.parquet differ diff --git a/backups/parquet/deliveries/deliveries_2026_02_03.parquet b/backups/parquet/deliveries/deliveries_2026_02_03.parquet new file mode 100644 index 0000000..5533473 Binary files /dev/null and b/backups/parquet/deliveries/deliveries_2026_02_03.parquet differ diff --git a/backups/parquet/deliveries/deliveries_2026_05_13.parquet b/backups/parquet/deliveries/deliveries_2026_05_13.parquet new file mode 100644 index 0000000..1b404e9 Binary files /dev/null and b/backups/parquet/deliveries/deliveries_2026_05_13.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_2026_01_19.parquet b/backups/parquet/deliverylogs/deliverylogs_2026_01_19.parquet new file mode 100644 index 0000000..25d7831 Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_2026_01_19.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_19.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_19.parquet new file mode 100644 index 0000000..93c28f3 Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_19.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_20.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_20.parquet new file mode 100644 index 0000000..93c28f3 Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_20.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_27.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_27.parquet new file mode 100644 index 0000000..93c28f3 Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_27.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_30.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_30.parquet new file mode 100644 index 0000000..93c28f3 Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_01_30.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_02_03.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_02_03.parquet new file mode 100644 index 0000000..925aa0e Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_02_03.parquet differ diff --git a/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_05_13.parquet b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_05_13.parquet new file mode 100644 index 0000000..5d026bb Binary files /dev/null and b/backups/parquet/deliverylogs/deliverylogs_last_3_months_2026_05_13.parquet differ diff --git a/backups/parquet/orders/orders_2026_01_30.parquet b/backups/parquet/orders/orders_2026_01_30.parquet new file mode 100644 index 0000000..8398887 Binary files /dev/null and b/backups/parquet/orders/orders_2026_01_30.parquet differ diff --git a/backups/parquet/orders/orders_2026_02_03.parquet b/backups/parquet/orders/orders_2026_02_03.parquet new file mode 100644 index 0000000..002c861 Binary files /dev/null and b/backups/parquet/orders/orders_2026_02_03.parquet differ diff --git a/backups/parquet/orders/orders_last_3_months_2026_05_13.parquet b/backups/parquet/orders/orders_last_3_months_2026_05_13.parquet new file mode 100644 index 0000000..48bd57a Binary files /dev/null and b/backups/parquet/orders/orders_last_3_months_2026_05_13.parquet differ diff --git a/cmds/jobs/parquet_backup.go b/cmds/jobs/parquet_backup.go new file mode 100644 index 0000000..751434e --- /dev/null +++ b/cmds/jobs/parquet_backup.go @@ -0,0 +1,90 @@ +package main + +import ( + "os" + "path/filepath" + + "nearle/db" + "nearle/internal/backup" + "nearle/utils" + + "github.com/joho/godotenv" +) + +func main() { + + // 👇 Load .env + if err := godotenv.Load(".env"); err != nil { + utils.Logger.Fatalf("❌ Failed to load .env file: %v", err) + } + + utils.Logger.Infof("DB_USER = %s", os.Getenv("DB_USER")) + utils.Logger.Infof("DB_NAME = %s", os.Getenv("DB_NAME")) + + // 🔌 Connect DB + db.Connect() + + sqlDB, err := db.DB.DB() + if err != nil { + utils.Logger.Fatal(err) + } + + // ================================ + // 1️⃣ deliverylogs + // ================================ + utils.Info("▶ Backing up deliverylogs...") + deliveryLogsFile, err := backup.BackupDeliveryLogs(sqlDB) + if err != nil { + utils.Logger.Fatalf("❌ deliverylogs backup failed: %v", err) + } + + utils.Info("☁️ Uploading deliverylogs parquet...") + url, err := backup.UploadParquetToSpaces( + deliveryLogsFile, + "parquet/deliverylogs/"+filepath.Base(deliveryLogsFile), + ) + if err != nil { + utils.Logger.Fatalf("❌ deliverylogs upload failed: %v", err) + } + utils.Logger.Infof("✅ Uploaded: %s", url) + + // ================================ + // 2️⃣ deliveries + // ================================ + utils.Info("▶ Backing up deliveries...") + deliveriesFile, err := backup.BackupDeliveries(sqlDB) + if err != nil { + utils.Logger.Fatalf("❌ deliveries backup failed: %v", err) + } + + utils.Info("☁️ Uploading deliveries parquet...") + url, err = backup.UploadParquetToSpaces( + deliveriesFile, + "parquet/deliveries/"+filepath.Base(deliveriesFile), + ) + if err != nil { + utils.Logger.Fatalf("❌ deliveries upload failed: %v", err) + } + utils.Logger.Infof("✅ Uploaded: %s", url) + + // ================================ + // 3️⃣ orders ✅ NEW + // ================================ + utils.Info("▶ Backing up orders...") + ordersFile, err := backup.BackupOrders(sqlDB) + if err != nil { + utils.Logger.Fatalf("❌ orders backup failed: %v", err) + } + + utils.Info("☁️ Uploading orders parquet...") + url, err = backup.UploadParquetToSpaces( + ordersFile, + "parquet/orders/"+filepath.Base(ordersFile), + ) + if err != nil { + utils.Logger.Fatalf("❌ orders upload failed: %v", err) + } + utils.Logger.Infof("✅ Uploaded: %s", url) + + utils.Info("🎉 Parquet backup + S3 upload completed successfully") +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..60b96ca --- /dev/null +++ b/config/config.go @@ -0,0 +1,49 @@ +package config + +import ( + "nearle/utils" + "os" +) + +type Config struct { + Env string + Port string + DBName string + DBUser string + DBPassword string + DBPort string + DBHost string + UserContextKey string + JWTSecret string +} + +func Load() *Config { + cfg := &Config{ + Env: getEnv("ENV", "production"), + Port: getEnv("APP_PORT", "1009"), + + // ✅ STANDARDIZED DB ENV KEYS + DBName: getEnv("DB_NAME", ""), + DBUser: getEnv("DB_USER", ""), + DBPassword: getEnv("DB_PASSWORD", ""), + DBPort: getEnv("DB_PORT", "5432"), + DBHost: getEnv("DB_HOST", "localhost"), + + UserContextKey: getEnv("USER_CONTEXT_KEY", "nearle"), + JWTSecret: getEnv("JWT_SECRET_KEY", ""), + } + + // ✅ Correct validation + if cfg.DBPassword == "" { + utils.Logger.Warn("Warning: DB_PASSWORD is not set") + } + + return cfg +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/controllers/authController.go b/controllers/authController.go new file mode 100644 index 0000000..9444ece --- /dev/null +++ b/controllers/authController.go @@ -0,0 +1,1336 @@ +package controllers + +import ( + "math/rand" + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" + + "github.com/gofiber/fiber/v2" +) + +func Login(c *fiber.Ctx) error { + + var user models.User + var info models.UserInfo + var uid int + var q1 string + + if err := c.BodyParser(&user); err != nil { + return err + } + + if user.Authname != "" { + + q1 = `SELECT a.userid from app_users a WHERE a.authname= '` + user.Authname + `' and a.configid=` + strconv.Itoa(user.Configid) + + } else { + + q1 = `SELECT a.userid from app_users a WHERE a.contactno= '` + user.Contactno + `' and a.configid=` + strconv.Itoa(user.Configid) + + } + + db.DB.Raw(q1).Find(&uid) + + if uid != 0 { + + user.Userid = uid + // cno := domain.GetTenantId(uid, "") + // domain.UpdatUser(user) + info = domain.Getuserbyid(uid) + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User not found", + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": info, + }) +} + +func TenantLogin(c *fiber.Ctx) error { + + var user models.User + var info models.TenantUserInfo + var uid int + var q1 string + + if err := c.BodyParser(&user); err != nil { + return err + } + + if user.Authname != "" { + + q1 = `SELECT a.userid from app_users a WHERE a.authname= '` + user.Authname + `' and a.configid=` + strconv.Itoa(user.Configid) + + } else { + + q1 = `SELECT a.userid from app_users a WHERE a.contactno= '` + user.Contactno + `' and a.configid=` + strconv.Itoa(user.Configid) + + } + + db.DB.Raw(q1).Find(&uid) + + if uid != 0 { + + user.Userid = uid + // user.Tenantid = domain.GetTenantId(user.Contactno) + // domain.UpdatUser(user) + info = domain.GetTenantUserbyId(uid) + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User not found", + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": info, + }) +} + +// func Partnerlogin(c *fiber.Ctx) error { +// var user models.User +// var info models.UserInfo +// var uid int +// var q1 string + +// if err := c.BodyParser(&user); err != nil { +// return err +// } + +// if user.Authname != "" { +// q1 = `SELECT a.userid FROM app_users a +// WHERE a.authname = '` + user.Authname + `' +// AND a.configid = ` + strconv.Itoa(user.Configid) +// } else { +// q1 = `SELECT a.userid FROM app_users a +// WHERE a.contactno = '` + user.Contactno + `' +// AND a.configid = ` + strconv.Itoa(user.Configid) +// } + +// db.DB.Raw(q1).Scan(&uid) + +// if uid != 0 { +// user.Userid = uid +// user.Pin = rand.Intn(6000) + 1000 + +// if user.Userfcmtoken != "" { +// log.Println("Updating FCM Token for UID:", uid, "Token:", user.Userfcmtoken) +// result := db.DB.Exec(`UPDATE app_users SET userfcmtoken = ? WHERE userid = ?`, user.Userfcmtoken, uid) +// if result.Error != nil { +// log.Println("Error updating FCM token:", result.Error) +// } else { +// log.Println("FCM token updated successfully") +// } +// } + +// domain.UpdatUser(user) + +// info = domain.GetPartnerUserbyid(uid) + +// if user.Configid == 5 && user.Authname != "" { +// config := domain.GetAppconfig(user.Configid) + +// err := domain.SendLoginNotification(info.Userfcmtoken, config.Fcmkey, info.Pin) +// if err != nil { +// log.Println("FCM send error:", err) + +// } +// } +// } else { +// return c.JSON(fiber.Map{ +// "status": false, +// "code": http.StatusConflict, +// "message": "User not found", +// }) +// } + +// return c.JSON(fiber.Map{ +// "code": http.StatusOK, +// "message": "Success", +// "status": true, +// "details": info, +// }) +// } + +func Partnerlogin(c *fiber.Ctx) error { + var user models.User + var info models.UserInfo + var uid int + var q1 string + + if err := c.BodyParser(&user); err != nil { + return err + } + + if user.Authname != "" { + q1 = `SELECT a.userid FROM app_users a + WHERE a.authname = '` + user.Authname + `' + AND a.configid = ` + strconv.Itoa(user.Configid) + } else { + q1 = `SELECT a.userid FROM app_users a + WHERE a.contactno = '` + user.Contactno + `' + AND a.configid = ` + strconv.Itoa(user.Configid) + } + + db.DB.Raw(q1).Scan(&uid) + + if uid != 0 { + user.Userid = uid + user.Pin = rand.Intn(6000) + 1000 + + if user.Userfcmtoken != "" { + utils.Logger.Infow("Updating FCM Token", "uid", uid, "token", user.Userfcmtoken) + result := db.DB.Exec(`UPDATE app_users SET userfcmtoken = ? WHERE userid = ?`, user.Userfcmtoken, uid) + if result.Error != nil { + utils.Logger.Errorw("Error updating FCM token", "error", result.Error) + } else { + utils.Logger.Infow("FCM token updated successfully") + } + } + + domain.UpdatUser(user) + + info = domain.GetPartnerUserbyid(uid) + + if user.Configid == 5 && user.Authname != "" { + config := domain.GetAppconfig(user.Configid) + + err := domain.SendLoginNotification(info.Userfcmtoken, config.Fcmkey, info.Pin) + if err != nil { + utils.Logger.Errorw("FCM send error", "error", err) + + } + } + } else { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User not found", + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": info, + }) +} + +func Riderlogin(c *fiber.Ctx) error { + + var user models.User + var info models.RiderInfo + var uid int + var q1 string + + if err := c.BodyParser(&user); err != nil { + return err + } + + if user.Authname != "" { + + q1 = `SELECT a.userid from app_users a WHERE a.authname= '` + user.Authname + `' and a.configid=` + strconv.Itoa(user.Configid) + + } else { + + q1 = `SELECT a.userid from app_users a WHERE a.contactno= '` + user.Contactno + `' and a.configid=` + strconv.Itoa(user.Configid) + + } + + db.DB.Raw(q1).Find(&uid) + + if uid != 0 { + + user.Userid = uid + user.Pin = rand.Intn(6000) + 1000 + domain.UpdatUser(user) + info = domain.GetRiderUserbyid(uid) + + if user.Configid == 5 && user.Authname != "" { + + config := domain.GetAppconfig(user.Configid) + domain.SendLoginNotification(info.Userfcmtoken, config.Fcmkey, info.Pin) + + } + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User not found", + }) + + } + + utils.Logger.Debugw("Rider Login Query", "query", q1) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": info, + }) + +} + +func Riderloginv2(c *fiber.Ctx) error { + var user models.User + + if err := c.BodyParser(&user); err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Invalid request body", + }) + } + + var uid int + + if user.Contactno != "" { + uid = domain.GetUserIDByContact(user.Contactno, user.Configid) + } + + if uid == 0 && user.Authname != "" { + uid = domain.GetUserIDByAuth(user.Authname, user.Configid) + } + + if uid == 0 { + return c.JSON(fiber.Map{ + "status": false, + "code": 404, + "message": "User not found", + }) + } + + status := domain.GetUserStatus(uid) + if strings.ToLower(status) != "active" { + return c.JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "User is inactive. Contact administrator.", + }) + } + + storedPin := domain.GetStoredPin(uid) + authmode := domain.GetAuthMode(uid) + + if authmode == 1 { + // Update FCM Token + if user.Userfcmtoken != "" { + _ = domain.UpdateUserFcmToken(uid, user.Userfcmtoken) + } + + info := domain.GetRiderUserbyid(uid) + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": info, + }) + } + + if storedPin == 0 { + return c.JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "PIN not set for this user. Please set a PIN before logging in.", + "userid": uid, + "authmode": authmode, + }) + } + + if user.Pin == 0 { + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "User verified. Please enter your PIN.", + "userid": uid, + "authmode": authmode, + }) + } + + if storedPin != user.Pin { + return c.JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Invalid PIN", + "authmode": authmode, + }) + } + + if user.Userfcmtoken != "" { + if err := domain.UpdateUserFcmToken(uid, user.Userfcmtoken); err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": 500, + "message": "Failed to update FCM token", + }) + } + } + + info := domain.GetRiderUserbyid(uid) + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": info, + }) +} + +func AdminLogin(c *fiber.Ctx) error { + var user models.User + var uid int + var q1 string + + if err := c.BodyParser(&user); err != nil { + return err + } + + if user.Authname != "" { + q1 = `SELECT a.userid FROM app_users a WHERE a.authname = '` + user.Authname + `' AND a.password = '` + user.Password + `' AND a.configid = ` + strconv.Itoa(user.Configid) + } + + db.DB.Raw(q1).Scan(&uid) + + if uid == 0 { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User not found", + }) + } + + if user.Userfcmtoken != "" { + updateQuery := `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?` + if err := db.DB.Exec(updateQuery, user.Userfcmtoken, uid).Error; err != nil { + utils.Logger.Errorw("Failed to update FCM token", "error", err) + } + } + + user = domain.GetAdminUserbyid(uid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": user, + }) +} + +func AdminConsoleLogin(c *fiber.Ctx) error { + + var user models.User + var uid int + var status, dbPassword string + var query string + + if err := c.BodyParser(&user); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid request body", + }) + } + + if strings.TrimSpace(user.Authname) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "authname is required", + }) + } + + query = ` + SELECT userid, password, status + FROM app_users + WHERE authname = ? AND configid = ? + ` + db.DB.Raw(query, user.Authname, 9).Row().Scan(&uid, &dbPassword, &status) + + if uid == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 409, + "message": "Invalid Email", + }) + } + + if strings.EqualFold(status, "InActive") { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Inactive Account. Contact admin.", + }) + } + + if strings.TrimSpace(dbPassword) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 409, + "message": "Please setup a password.", + "details": fiber.Map{ + "userid": uid, + "setup": true, + }, + }) + } + + if strings.TrimSpace(user.Password) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 401, + "message": "Password is required", + "userid": uid, + }) + } + + if user.Password != dbPassword { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Incorrect password", + }) + } + + if user.Userfcmtoken != "" { + _ = db.DB.Exec( + `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?`, + user.Userfcmtoken, uid, + ) + } + + user = domain.GetAdminUserbyid(uid) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": user, + }) +} + +func CustomerLogin(c *fiber.Ctx) error { + + var data models.Customer + + if err := c.BodyParser(&data); err != nil { + return err + } + + q1 := `select a.customerid,a.authmode,a.configid,a.deviceid,a.devicetype,a.customertoken,a.firstname,a.lastname, + a.contactno,a.profileimage,a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode, + a.latitude,a.longitude,a.applocationid,a.status,a.profileimage, a.dialcode, a.deviceid, a.devicetype, a.authmode, + a.configid, a.customertoken, a.intro, b.tenantid , c.qrmode, b.locationid + from customers a + INNER JOIN tenantcustomers b ON a.customerid=b.customerid + LEFT JOIN app_location c ON a.applocationid=c.applocationid + where a.configid=2 and a.contactno= '` + data.Contactno + `' ` + + utils.Logger.Debugw("Customer Login Query", "query", q1) + + db.DB.Raw(q1).Find(&data) + + if data.Customerid == 0 { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Account not found", + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func BasicRoute(c *fiber.Ctx) error { + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": "Hello Customer Route", + }) +} + +func CreateUser(c *fiber.Ctx) error { + + var user models.User + var info models.UserInfo + + // 🔹 Parse request body + if err := c.BodyParser(&user); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid request body", + }) + } + + // 🔹 Validate mandatory fields + if user.Configid == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "configid is required", + }) + } + + // 🔹 Check duplicate user (contactno / emailid + configid) + exists, err := domain.CheckUserExists(user.Contactno, user.Email, user.Configid) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Database error", + }) + } + + if exists { + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User already exists with this contact number or email", + }) + } + + // 🔹 Start transaction + tx := db.DB.Begin() + + if err := tx.Table("app_users").Create(&user).Error; err != nil { + tx.Rollback() + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Failed to create user", + }) + } + + if err := tx.Commit().Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Transaction failed", + }) + } + + // 🔹 Fetch created user info + info = domain.Getuserbyid(user.Userid) + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "Success", + "details": info, + }) +} + +func GetUserInfo(c *fiber.Ctx) error { + uid, _ := strconv.Atoi(c.Query("userid")) + + user := domain.Getuserbyid(uid) + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": user, + }) +} +func GetAllUsers(c *fiber.Ctx) error { + roleID, _ := strconv.Atoi(c.Query("roleid", "0")) + configID, _ := strconv.Atoi(c.Query("configid", "0")) + tenantID, _ := strconv.Atoi(c.Query("tenantid", "0")) + pageno, _ := strconv.Atoi(c.Query("pageno", "1")) + pagesize, _ := strconv.Atoi(c.Query("pagesize", "10")) + status := c.Query("status", "") // 👈 added + keyword := c.Query("keyword", "") + + users := domain.GetAllUsers(roleID, configID, tenantID, pageno, pagesize, status, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": users, + }) +} + +func DeleteUser(c *fiber.Ctx) error { + type Request struct { + Userid int `json:"userid"` + } + + var req Request + + if err := c.BodyParser(&req); err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid request body", + }) + } + + if req.Userid == 0 { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Missing userid", + }) + } + + err := db.DB.Table("app_users").Where("userid = ?", req.Userid).Delete(nil).Error + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "User deleted successfully", + }) +} + +func TenantWebLogin(c *fiber.Ctx) error { + utils.Logger.Infow("Starting TenantWebLogin") + + var user models.User + var info models.TenantUserInfo + var uid, roleid int + var status, dbPassword string + var query string + + // Parse request body + if err := c.BodyParser(&user); err != nil { + utils.Logger.Errorw("Error parsing body", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid request body", + }) + } + + // Check login method: authname or contactno + if user.Authname != "" { + query = ` + SELECT userid, password, status, roleid + FROM app_users + WHERE authname = ? AND configid = ? + ` + db.DB.Raw(query, user.Authname, user.Configid).Row().Scan(&uid, &dbPassword, &status, &roleid) + } else if user.Contactno != "" { + query = ` + SELECT userid, password, status, roleid + FROM app_users + WHERE contactno = ? AND configid = ? + ` + db.DB.Raw(query, user.Contactno, user.Configid).Row().Scan(&uid, &dbPassword, &status, &roleid) + } else { + utils.Logger.Errorw("Missing authname or contactno") + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "authname or contactno required", + }) + } + + tenantFormExists := true + + if uid == 0 { + utils.Logger.Infow("Invalid Email at TenantWebLogin", "email", user.Authname) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 409, + "message": "Invalid Email", + "tenantform": tenantFormExists, + }) + } + + // Check if user is inactive + if status == "InActive" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Inactive Account. Contact admin.", + }) + } + + // Compare roleid in request vs DB + if user.Roleid != roleid { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Unauthorized email.", + }) + } + + // Check if password is not set + if strings.TrimSpace(dbPassword) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 409, + "message": "Please setup a password.", + "tenantform": tenantFormExists, + "details": fiber.Map{ + "userid": uid, + "setup": true, + }, + }) + } + + // Check if password is provided in request + if strings.TrimSpace(user.Password) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 401, + "message": "Password is required", + "tenantform": tenantFormExists, + }) + } + + // Validate password + if user.Password != dbPassword { + utils.Logger.Infow("Incorrect password attempt", "uid", uid) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Incorrect password", + "tenantform": tenantFormExists, + }) + } + + user.Userid = uid + + // Update FCM token if provided + if user.Userfcmtoken != "" { + updateQuery := ` + UPDATE app_users + SET userfcmtoken = ? + WHERE userid = ? + ` + if err := db.DB.Exec(updateQuery, user.Userfcmtoken, uid).Error; err != nil { + utils.Logger.Errorw("Error updating userfcmtoken", "error", err) + } else { + utils.Logger.Infow("Updated userfcmtoken", "userid", uid) + } + } + + info = domain.GetTenantUserbyId(uid) + + utils.Logger.Infow("Tenant found", "tenant", info.Tenantname) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": info, + }) +} + +func AppLogin(c *fiber.Ctx) error { + utils.Logger.Infow("Starting TenantWebLogin") + + var user models.User + var info models.TenantUserInfo + var uid int + var status, dbPassword string + var query string + + // Parse request body + if err := c.BodyParser(&user); err != nil { + utils.Logger.Errorw("Error parsing body", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid request body", + }) + } + + // Check login method: authname or contactno + if user.Authname != "" { + query = ` + SELECT userid, password, status + FROM app_users + WHERE authname = ? AND configid = ? + ` + db.DB.Raw(query, user.Authname, user.Configid).Row().Scan(&uid, &dbPassword, &status) + } else if user.Contactno != "" { + query = ` + SELECT userid, password, status + FROM app_users + WHERE contactno = ? AND configid = ? + ` + db.DB.Raw(query, user.Contactno, user.Configid).Row().Scan(&uid, &dbPassword, &status) + } else { + utils.Logger.Errorw("Missing authname or contactno") + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "authname or contactno required", + }) + } + + tenantFormExists := true + + if uid == 0 { + utils.Logger.Infow("Invalid Email at TenantWebLogin", "email", user.Authname) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 409, + "message": "Invalid Email", + "tenantform": tenantFormExists, + }) + } + + // Check if user is inactive + if status == "InActive" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Inactive Account. Contact admin.", + }) + } + + // Check if password is not set + if strings.TrimSpace(dbPassword) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 409, + "message": "Please setup a password.", + "tenantform": tenantFormExists, + "details": fiber.Map{ + "userid": uid, + "setup": true, + }, + }) + } + + // Check if password is provided in request + if strings.TrimSpace(user.Password) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 401, + "message": "Password is required", + "tenantform": tenantFormExists, + }) + } + + // Validate password + if user.Password != dbPassword { + utils.Logger.Infow("Incorrect password attempt", "uid", uid) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Incorrect password", + "tenantform": tenantFormExists, + }) + } + + user.Userid = uid + + // Update FCM token if provided + if user.Userfcmtoken != "" { + updateQuery := ` + UPDATE app_users + SET userfcmtoken = ? + WHERE userid = ? + ` + if err := db.DB.Exec(updateQuery, user.Userfcmtoken, uid).Error; err != nil { + utils.Logger.Errorw("Error updating userfcmtoken", "error", err) + } else { + utils.Logger.Infow("Updated userfcmtoken", "userid", uid) + } + } + + info = domain.GetTenantUserbyId(uid) + + utils.Logger.Infow("Tenant found", "tenant", info.Tenantname) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": info, + }) +} + +func CreateUserV2(c *fiber.Ctx) error { + var user models.User + var info models.UserInfo + + if err := c.BodyParser(&user); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + tx := db.DB.Begin() + + user.Status = "InActive" + + // Insert into app_users + if err := tx.Table("app_users").Create(&user).Error; err != nil { + tx.Rollback() + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "code": 409, + "message": "Failed to create user", + "status": false, + }) + } + + // Insert into tenantstaffs + staff := models.Tenantstaffs{ + Tenantid: user.Tenantid, + Moduleid: 2, + Locationid: user.Locationid, + Userid: user.Userid, + Status: "InActive", + } + + if err := tx.Table("tenantstaffs").Create(&staff).Error; err != nil { + tx.Rollback() + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "code": 409, + "message": "Failed to create tenant staff", + "status": false, + }) + } + + // Insert into app_userpools + pool := models.AppUserpools{ + Userid: user.Userid, + Partnerid: user.Partnerid, + Onduty: 1, + Latitude: "", + Longitude: "", + Status: "idle", + } + + if err := tx.Table("app_userpools").Create(&pool).Error; err != nil { + tx.Rollback() + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "code": 409, + "message": "Failed to create user pool", + "status": false, + }) + } + + // Commit transaction + if err := tx.Commit().Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Transaction commit failed", + "status": false, + }) + } + + // Fetch user info + info = domain.Getuserbyid(user.Userid) + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{ + "code": 201, + "message": "User created successfully", + "status": true, + "details": info, + }) +} + +func UpdateUserV2(c *fiber.Ctx) error { + var userInput map[string]interface{} + + if err := c.BodyParser(&userInput); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + userID, ok := userInput["userid"] + if !ok || userID == nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Missing userid", + "status": false, + }) + } + + tx := db.DB.Begin() + + // Update only fields provided for app_users + if err := tx.Table("app_users").Where("userid = ?", userID).Updates(userInput).Error; err != nil { + tx.Rollback() + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "code": 409, + "message": "Failed to update user", + "status": false, + }) + } + + // Filter relevant fields for tenantstaffs update + staffFields := map[string]interface{}{} + if tenantid, ok := userInput["tenantid"]; ok { + staffFields["tenantid"] = tenantid + } + if locationid, ok := userInput["locationid"]; ok { + staffFields["locationid"] = locationid + } + if status, ok := userInput["status"]; ok { + staffFields["status"] = status + } + + if len(staffFields) > 0 { + if err := tx.Table("tenantstaffs").Where("userid = ?", userID).Updates(staffFields).Error; err != nil { + tx.Rollback() + return c.Status(fiber.StatusConflict).JSON(fiber.Map{ + "code": 409, + "message": "Failed to update tenant staff", + "status": false, + }) + } + } + + if err := tx.Commit().Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Transaction commit failed", + "status": false, + }) + } + + info := domain.Getuserbyid(int(userID.(float64))) // type assert if coming from JSON + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "message": "User updated successfully", + "status": true, + "details": info, + }) +} + +func TenantWebLoginv2(c *fiber.Ctx) error { + utils.Logger.Infow("Unified login started") + + var user models.User + var info models.TenantUserInfo + var uid = c.Locals("uid").(int) + var dbPassword = c.Locals("password").(string) + + if err := c.BodyParser(&user); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid request body", + }) + } + + tenantFormExists := true + + // Check if password is not set + if strings.TrimSpace(dbPassword) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 409, + "message": "Please setup a password.", + "tenantform": tenantFormExists, + "details": fiber.Map{ + "userid": uid, + "setup": true, + }, + }) + } + + if strings.TrimSpace(user.Password) == "" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Password is required", + "tenantform": tenantFormExists, + }) + } + + if user.Password != dbPassword { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 401, + "message": "Incorrect password", + "tenantform": tenantFormExists, + }) + } + + user.Userid = uid + + if user.Userfcmtoken != "" { + updateQuery := `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?` + if err := db.DB.Exec(updateQuery, user.Userfcmtoken, uid).Error; err != nil { + utils.Logger.Errorw("Error updating userfcmtoken", "error", err) + } + } + + info = domain.GetTenantUserbyId(uid) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Login successful", + "details": info, + }) +} + +func CreateConsoleUser(c *fiber.Ctx) error { + + var user models.ConsoleUser + var info models.UserInfo + + // 🔹 Parse request body + if err := c.BodyParser(&user); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid request body", + }) + } + + // 🔹 Validation + if user.Configid == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "configid is required", + }) + } + + // if len(user.Applocationids) == 0 { + // return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + // "status": false, + // "code": http.StatusBadRequest, + // "message": "at least one applocationid is required", + // }) + // } + + // 🔹 Duplicate user check + exists, err := domain.CheckUserExists(user.Contactno, user.Email, user.Configid) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Database error", + }) + } + + if exists { + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "User already exists", + }) + } + + // 🔹 Start transaction + tx := db.DB.Begin() + + // 🔹 Create user + if err := tx.Table("app_users").Create(&user).Error; err != nil { + tx.Rollback() + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Failed to create user", + }) + } + + // 🔹 Assign MULTIPLE location access + // for _, locID := range user.Applocationids { + // locationConfig := models.AppLocationConfig{ + // Applocationid: locID, + // Configid: user.Configid, + // Userid: user.Userid, + // Partnerid: 0, + // Notify: "true", + // Status: "Active", + // } + + // if err := tx.Table("app_locationconfig").Create(&locationConfig).Error; err != nil { + // tx.Rollback() + // return c.Status(http.StatusConflict).JSON(fiber.Map{ + // "status": false, + // "code": http.StatusConflict, + // "message": "Failed to assign location access", + // }) + // } + // } + + // 🔹 Commit transaction + if err := tx.Commit().Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Transaction failed", + }) + } + + // 🔹 Fetch created user info + info = domain.Getuserbyid(user.Userid) + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "User created with multiple location access", + "details": info, + }) +} + +func GetAllUsersv2(c *fiber.Ctx) error { + roleID, _ := strconv.Atoi(c.Query("roleid", "0")) + configID, _ := strconv.Atoi(c.Query("configid", "0")) + tenantID, _ := strconv.Atoi(c.Query("tenantid", "0")) + pageno, _ := strconv.Atoi(c.Query("pageno", "1")) + pagesize, _ := strconv.Atoi(c.Query("pagesize", "10")) + keyword := c.Query("keyword", "") + + users := domain.GetAllUsersv2(roleID, configID, tenantID, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": users, + }) +} diff --git a/controllers/customerController.go b/controllers/customerController.go new file mode 100644 index 0000000..3b7a8e8 --- /dev/null +++ b/controllers/customerController.go @@ -0,0 +1,448 @@ +package controllers + +import ( + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "time" + + "github.com/gofiber/fiber/v2" +) + +func CreateCustomer(c *fiber.Ctx) error { + + var data models.Customers + var id int + + if err := c.BodyParser(&data); err != nil { + utils.Error("CreateCustomer body parse error", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + // return c.JSON(fiber.Map{ + // "status": false, + // "code": http.StatusConflict, + // "message": "Customer Already available", + // }) + + cid := domain.CheckCustomer(data.Contactno) + if cid != 0 { + + data.Customerid = cid + + tcid := domain.CheckTenantCustomer(data.Customerid, data.Tenantid) + if tcid == 0 { + + id = domain.CreateTenantCustomer(data) + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Customer Already available", + }) + + } + + } else { + + id = domain.CreateCustomer(data) + + } + + if id != 0 { + + result := domain.GetCustomer(id, "") + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "Successful", + "details": result, + }) + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Failed", + }) + + } + +} + +func UpdateCustomer(c *fiber.Ctx) error { + + var data models.Customers + + if err := c.BodyParser(&data); err != nil { + utils.Error("UpdateCustomer body parse error", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + result := domain.UpdateCustomer(data) + + if !result.Status { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Failed", + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Customer update successful", + }) + +} + +func GetCustomer(c *fiber.Ctx) error { + + cid, _ := strconv.Atoi(c.Query("customerid")) + cno := c.Query("contactno") + + result := domain.GetCustomer(cid, cno) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetTenantCustomers(c *fiber.Ctx) error { + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + keyword := c.Query("keyword") + + result := domain.GetTenantCustomers(tid, lid, aid, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetCustomersbytenent(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + + //aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetCustomerbytenant(tid, lid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetCustomersbyapplocation(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetCustomerbyApplocation(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetallCustomers(c *fiber.Ctx) error { + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + keyword := c.Query("keyword") // <-- new param + + result := domain.GetallCustomers(aid, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func SearchCustomer(c *fiber.Ctx) error { + + keyword := c.Query("keyword") + tid, _ := strconv.Atoi(c.Query("tenantid")) + + result := domain.SearchCustomer(keyword, tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func DeleteCustomer(c *fiber.Ctx) error { + + cid, _ := strconv.Atoi(c.Query("customerid")) + + count := GetCustomerOrderCount(cid) + + if count == 0 { + + tx := db.DB.Begin() + t1 := tx.Table("customers").Where("customerid = ?", cid).Delete(&models.Customers{}) + if t1.Error != nil { + utils.Error("DeleteCustomer t1 delete error", "error", t1.Error) + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error deleting customer", + "status": false, + }) + } + + t2 := tx.Table("customerlocations").Where("customerid = ?", cid).Delete(&models.Customerlocations{}) + if t2.Error != nil { + utils.Error("DeleteCustomer t2 delete error", "error", t2.Error) + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error deleting customer", + "status": false, + }) + } + + t3 := tx.Table("tenantcustomers").Where("customerid = ?", cid).Delete(&models.Tenantcustomers{}) + if t3.Error != nil { + utils.Error("DeleteCustomer t3 delete error", "error", t3.Error) + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error deleting customer", + "status": false, + }) + } + + tx.Commit() + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "Orders available for customers", + "status": false, + }) + +} + +func CreateCustomerLocation(c *fiber.Ctx) error { + + var data models.Customerlocations + + if err := c.BodyParser(&data); err != nil { + utils.Error("CreateCustomerLocation body parse error", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + id := domain.CreateCustomerLocation(data) + if id != 0 { + + result := domain.GetCustomerLocation(id) + return c.JSON(result) + + } else { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": "Failed", + }) + + } + +} + +func GetCustomerLocations(c *fiber.Ctx) error { + + cid, _ := strconv.Atoi(c.Query("customerid")) + + result := domain.GetCustomerLocation(cid) + return c.JSON(result) + +} + +func CreateCustomerRequest(c *fiber.Ctx) error { + var req models.CustomerRequest + + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + now := time.Now() + req.Created = now + req.Updated = now + + // Insert into database + if err := db.DB.Table("customerrequest").Create(&req).Error; err != nil { + utils.Error("CreateCustomerRequest DB error", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to create customer request", + "status": false, + }) + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{ + "code": 201, + "message": "Customer request created successfully", + "status": true, + "data": req, + }) +} + +func GetCustomerRequests(c *fiber.Ctx) error { + customerIDStr := c.Query("customerid") + pageNoStr := c.Query("pageno", "1") + pageSizeStr := c.Query("pagesize", "10") + + pageNo, _ := strconv.Atoi(pageNoStr) + pageSize, _ := strconv.Atoi(pageSizeStr) + + if pageNo < 1 { + pageNo = 1 + } + if pageSize < 1 { + pageSize = 10 + } + offset := (pageNo - 1) * pageSize + + var requests []models.CustomerRequest + query := db.DB.Table("customerrequest") + + // Optional filter by customerid + if customerIDStr != "" { + customerID, err := strconv.Atoi(customerIDStr) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid customerid", + "status": false, + }) + } + if customerID != 0 { + query = query.Where("customerid = ?", customerID) + } + } + + var total int64 + query.Count(&total) + + if err := query.Order("created desc").Offset(offset).Limit(pageSize).Find(&requests).Error; err != nil { + utils.Error("GetCustomerRequests DB error", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to fetch customer requests", + "status": false, + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "message": "Customer requests fetched successfully", + "status": true, + "data": requests, + }) +} + +func GetCustomerSummary(c *fiber.Ctx) error { + aid, _ := strconv.Atoi(c.Query("applocationid", "0")) + tid, _ := strconv.Atoi(c.Query("tenantid", "0")) + + // Initialize query builder + query := db.DB.Table("customers as a"). + Joins("INNER JOIN customerlocations b ON a.customerid = b.customerid"). + Where("b.primaryaddress = 1") + + if aid != 0 { + query = query.Where("a.applocationid = ?", aid) + } + + if tid != 0 { + query = query.Joins("INNER JOIN tenantcustomers ct ON a.customerid = ct.customerid"). + Where("ct.tenantid = ?", tid) + } + + type Summary struct { + Total int64 + Active int64 + Inactive int64 + } + + var summary Summary + + // Execute consolidated summary counts + err := query.Select(` + COUNT(DISTINCT a.customerid) AS total, + COUNT(DISTINCT CASE WHEN a.status = 0 THEN a.customerid END) AS active, + COUNT(DISTINCT CASE WHEN a.status = 1 THEN a.customerid END) AS inactive`). + Scan(&summary).Error + + if err != nil { + utils.Error("GetCustomerSummary DB error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to fetch customer summary", + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Customer summary fetched successfully", + "status": true, + "summary": summary, + }) +} diff --git a/controllers/deliveryController.go b/controllers/deliveryController.go new file mode 100644 index 0000000..ab7b82e --- /dev/null +++ b/controllers/deliveryController.go @@ -0,0 +1,1485 @@ +package controllers + +import ( + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" + + "github.com/gofiber/fiber/v2" +) + +const ( + core = `SELECT COUNT(DISTINCT a.deliveryid) AS total, + + COUNT(DISTINCT CASE WHEN a.orderstatus = 'pending' THEN a.deliveryid END) AS pending, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'accepted' THEN a.deliveryid END) AS accepted, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'arrived' THEN a.deliveryid END) AS arrived, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'picked' THEN a.deliveryid END) AS picked, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'active' THEN a.deliveryid END) AS active, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'delivered' THEN a.deliveryid END) AS delivered, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'cancelled' THEN a.deliveryid END) AS cancelled, + COUNT(DISTINCT CASE WHEN a.orderstatus = 'skipped' THEN a.deliveryid END) AS skipped` + + reports = `SELECT + c.userid,e.firstname,e.lastname,e.contactno AS ridercontact,b.tenantid,b.tenantname,a.locationid, d.locationname, + COUNT(*) AS totalorders, + SUM(CASE WHEN a.orderstatus = 'created' THEN 1 ELSE 0 END) AS orderscreated, + SUM(CASE WHEN a.orderstatus = 'pending' THEN 1 ELSE 0 END) AS orderspending, + SUM(CASE WHEN a.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS orderscancelled, + SUM(CASE WHEN a.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS orderscompleted, + SUM(CASE WHEN c.orderstatus = 'pending' THEN 1 ELSE 0 END) AS deliveriespending, + SUM(CASE WHEN c.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS deliveriescompleted, + SUM(CASE WHEN c.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS deliveriescancelled, + SUM(CASE WHEN c.paymenttype = 64 THEN c.deliveryamt ELSE 0 END) AS paylater, + SUM(CASE WHEN c.paymenttype = 43 THEN c.deliveryamt ELSE 0 END) AS payondelivery, + ROUND(SUM(CAST(COALESCE(NULLIF(c.kms, ''), '0') AS NUMERIC)), 2) AS kms, + ROUND(SUM(CAST(COALESCE(NULLIF(c.actualkms, ''), '0') AS NUMERIC)), 2) AS actualkms, + SUM(c.deliveryamt) AS charges, + SUM(CASE WHEN c.orderstatus = 'delivered' THEN c.cumulativekms ELSE 0 END) AS cumulativekms, + SUM(CASE WHEN c.orderstatus = 'delivered' THEN c.deliveryamt ELSE 0 END) AS deliveryamt, + SUM(c.previouskms) AS previouskms, + SUM(c.previouskms) AS previouskms, + SUM(CAST(COALESCE(NULLIF(c.riderkms, ''), '0') AS NUMERIC)) AS riderkms, + SUM(c.quantity) AS quantity, SUM(c.collectionamt) AS collectionamt, SUM(c.collectedamt) AS collectedamt, c.collectionstatus + FROM + tenants b + right JOIN orders a ON b.tenantid = a.tenantid + right JOIN deliveries c ON a.orderheaderid = c.orderheaderid + LEFT JOIN tenantlocations d ON a.locationid = d.locationid + LEFT JOIN app_users e ON e.userid = c.userid` + + Ridersummary = `SELECT a.userid, a.firstname, a.lastname, a.contactno AS ridercontact, MAX(b.tenantid) AS tenantid, MAX(c.tenantname) AS tenantname, MAX(b.locationid) AS locationid, MAX(d.locationname) AS locationname, + CONCAT(a.firstname, ' ', a.lastname) AS fullname, a.status, SUM(COALESCE(b.quantity,0)) AS quantity, SUM(COALESCE(b.collectionamt,0)) AS collectionamt, SUM(COALESCE(b.collectedamt,0)) AS collectedamt, COALESCE(MAX(b.collectionstatus),0) AS collectionstatus, + COUNT(b.orderid) AS totalorders, + SUM(CASE WHEN b.orderstatus = 'pending' THEN 1 ELSE 0 END) AS pending, + SUM(CASE WHEN b.orderstatus = 'assigned' THEN 1 ELSE 0 END) AS assigned, + SUM(CASE WHEN b.orderstatus = 'accepted' THEN 1 ELSE 0 END) AS accepted, + SUM(CASE WHEN b.orderstatus = 'arrived' THEN 1 ELSE 0 END) AS arrived, + SUM(CASE WHEN b.orderstatus = 'picked' THEN 1 ELSE 0 END) AS picked, + SUM(CASE WHEN b.orderstatus = 'active' THEN 1 ELSE 0 END) AS active, + SUM(CASE WHEN b.orderstatus = 'skipped' THEN 1 ELSE 0 END) AS skipped, + SUM(CASE WHEN b.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS delivered, + SUM(CASE WHEN b.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled, + SUM(CASE WHEN b.orderstatus = 'delivered' THEN CAST(COALESCE(NULLIF(b.actualkms, ''), '0') AS NUMERIC) ELSE 0 END) AS actualkms, + SUM(CASE WHEN b.orderstatus = 'delivered' THEN CAST(COALESCE(NULLIF(b.kms, ''), '0') AS NUMERIC) ELSE 0 END) AS kms, + SUM(CASE WHEN b.paymenttype = 64 THEN b.deliveryamt ELSE 0 END) AS paylater, + SUM(CASE WHEN b.paymenttype = 43 THEN b.deliveryamt ELSE 0 END) AS payondelivery, + SUM(CASE WHEN b.orderstatus = 'delivered' THEN b.cumulativekms ELSE 0 END) AS cumulativekms, + SUM(CASE WHEN b.orderstatus = 'delivered' THEN b.deliveryamt ELSE 0 END) AS deliveryamt, + SUM(b.previouskms) AS previouskms + FROM app_users a + LEFT JOIN deliveries b ON a.userid = b.userid + LEFT JOIN tenants c ON b.tenantid = c.tenantid + LEFT JOIN tenantlocations d ON b.locationid = d.locationid` +) + +func PublishLog(c *fiber.Ctx) error { + + var data []models.Deliverylogs + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.PublishLog(data) + + if err != nil { + + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "successful", + }) + +} + +func GetDeliverylogs(c *fiber.Ctx) error { + + did, _ := strconv.Atoi(c.Query("deliveryid")) + + result := domain.GetDeliverylogs(did) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetDeliveriesV1(c *fiber.Ctx) error { + + var result []models.Deliveryinfo + + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + uid, _ := strconv.Atoi(c.Query("userid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + aud, _ := strconv.Atoi(c.Query("appuserid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + stat := c.Query("status") + + var info models.DeliveryQuery + + info.Partnerid = pid + info.Tenantid = tid + info.UserID = uid + info.Appuserid = aud + info.Applocationid = aid + info.Fromdate = fdate + info.ToDate = tdate + info.Status = stat + + if tid != 0 { + + result = domain.GetTenantDeliveries(info) + + } else if pid != 0 { + + result = domain.GetPartnerDeliveries(info) + + } else if cid != 0 { + + result = domain.GetCustomerDeliveries(info) + + } else if aid != 0 { + + result = domain.GetAdminDeliveries(info) + + } else if uid != 0 { + + result = domain.GetUserDeliveries(info) + + } else if aud != 0 { + + result = domain.GetAppUserDeliveries(info) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetDeliveriesV2(c *fiber.Ctx) error { + var result []models.Deliveryinfo + + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + aud, _ := strconv.Atoi(c.Query("appuserid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + stat := c.Query("status") + keyword := c.Query("keyword") + + pagenoStr := c.Query("pageno") + pagesizeStr := c.Query("pagesize") + + var pageno, pagesize int + var err error + + if pagenoStr != "" { + pageno, err = strconv.Atoi(pagenoStr) + if err != nil || pageno <= 0 { + pageno = 1 + } + } + + if pagesizeStr != "" { + pagesize, err = strconv.Atoi(pagesizeStr) + if err != nil || pagesize <= 0 { + pagesize = 10 + } + } + + info := models.DeliveryQuery{ + Partnerid: pid, + Tenantid: tid, + Locationid: lid, + UserID: uid, + Appuserid: aud, + Applocationid: aid, + Fromdate: fdate, + ToDate: tdate, + Status: stat, + Pageno: pageno, + Pagesize: pagesize, + Keyword: keyword, + } + + utils.Logger.Infow("Searching Delivery", "keyword", keyword) + + switch { + case tid != 0 && lid != 0: + result = domain.GetTenantLocationDeliveries(info) + case tid != 0: + result = domain.GetTenantDeliveries(info) + case lid != 0: + result = domain.GetLocationDeliveries(info) + case pid != 0: + result = domain.GetPartnerDeliveries(info) + case cid != 0: + result = domain.GetCustomerDeliveries(info) + case aid != 0: + result = domain.GetAdminDeliveries(info) + case uid != 0: + result = domain.GetUserDeliveries(info) + case aud != 0: + result = domain.GetAppUserDeliveries(info) + default: + result = domain.GetDeliveries(info) + } + + if result == nil { + result = []models.Deliveryinfo{} + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetDeliveriesV3(c *fiber.Ctx) error { + var result []models.Deliveryinfo + + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + uid, _ := strconv.Atoi(c.Query("userid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + aud, _ := strconv.Atoi(c.Query("appuserid")) + pageno, _ := strconv.Atoi(c.Query("pageno", "1")) + pagesize, _ := strconv.Atoi(c.Query("pagesize", "")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + stat := c.Query("status") + keyword := c.Query("keyword") + + var info models.DeliveryQuery + + info.Partnerid = pid + info.Tenantid = tid + info.UserID = uid + info.Appuserid = aud + info.Applocationid = aid + info.Fromdate = fdate + info.ToDate = tdate + info.Status = stat + info.Pageno = pageno + info.Pagesize = pagesize + info.Keyword = keyword + + utils.Logger.Infow("Searching Delivery", "keyword", keyword) + + // Choose which delivery function to call + switch { + case tid != 0: + result = domain.GetTenantDeliveries(info) + case pid != 0: + result = domain.GetPartnerDeliveries(info) + case cid != 0: + result = domain.GetCustomerDeliveries(info) + case aid != 0: + result = domain.GetAdminDeliveries(info) + case uid != 0: + result = domain.GetUserDeliveriesv1(info) + case aud != 0: + result = domain.GetAppUserDeliveries(info) + default: + result = domain.GetDeliveries(info) + } + + // Ensure result is not nil (avoid "null" in JSON) + if result == nil { + result = []models.Deliveryinfo{} + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetDeliveries(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + uid, _ := strconv.Atoi(c.Query("userid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + aud, _ := strconv.Atoi(c.Query("appuserid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + stat := c.Query("status") + + var info models.DeliveryQuery + + info.Partnerid = pid + info.Tenantid = tid + info.UserID = uid + info.Appuserid = aud + info.Applocationid = aid + info.Fromdate = fdate + info.ToDate = tdate + info.Status = stat + + data := domain.GetDeliveries(info) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetReportSummary(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + uid, _ := strconv.Atoi(c.Query("userid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.ReportSummary + var q1 string + + var params []interface{} + q1 = reports + + if tid != 0 { + q1 += " WHERE a.tenantid=? " + params = append(params, tid) + } else if pid != 0 { + q1 += " WHERE a.partnerid=? " + params = append(params, pid) + } else if uid != 0 { + q1 += " WHERE c.userid=? " + params = append(params, uid) + } else if aid != 0 { + q1 += " WHERE c.applocationid=? " + params = append(params, aid) + } else { + q1 += " WHERE 1=1 " + } + + if fdate != "" && tdate != "" { + q1 += " AND a.orderdate::date between ?::date and ?::date " + params = append(params, fdate, tdate) + } + + q1 += " GROUP BY a.tenantid, b.tenantname, c.userid, e.firstname, e.lastname, e.contactno, a.locationid, d.locationname, c.collectionstatus" + + utils.Logger.Debugw("ReportSummary SQL generated", "query", q1) + + db.DB.Raw(q1, params...).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetReportLocationSummary(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + uid, _ := strconv.Atoi(c.Query("userid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + keyword := c.Query("keyword") + + var data []models.ReportSummary + var q1 string + + var params []interface{} + q1 = reports + " WHERE 1=1 " + + if tid != 0 { + q1 += " AND a.tenantid=? " + params = append(params, tid) + } + if lid != 0 { + q1 += " AND a.locationid=? " + params = append(params, lid) + } + if pid != 0 { + q1 += " AND a.partnerid=? " + params = append(params, pid) + } + if uid != 0 { + q1 += " AND c.userid=? " + params = append(params, uid) + } + if aid != 0 { + q1 += " AND c.applocationid=? " + params = append(params, aid) + } + if keyword != "" { + q1 += " AND LOWER(d.locationname) LIKE LOWER(?) " + params = append(params, "%"+keyword+"%") + } + if fdate != "" && tdate != "" { + q1 += " AND a.orderdate::date BETWEEN ?::date AND ?::date " + params = append(params, fdate, tdate) + } + + q1 += " GROUP BY a.locationid, b.tenantid, b.tenantname, c.userid, e.firstname, e.lastname, e.contactno, d.locationname, c.collectionstatus" + + utils.Logger.Debugw("ReportLocationSummary SQL generated", "query", q1) + + db.DB.Raw(q1, params...).Scan(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetRiderLocationReportSummary(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + uid, _ := strconv.Atoi(c.Query("userid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.ReportSummary + var q1 string + + base := ` + SELECT b.tenantid,b.tenantname,a.locationid,d.locationname,e.userid,e.firstname,e.lastname, + COUNT(*) AS totalorders, + SUM(CASE WHEN a.orderstatus = 'created' THEN 1 ELSE 0 END) AS orderscreated, + SUM(CASE WHEN a.orderstatus = 'pending' THEN 1 ELSE 0 END) AS orderspending, + SUM(CASE WHEN a.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS orderscancelled, + SUM(CASE WHEN a.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS orderscompleted, + SUM(CASE WHEN c.orderstatus = 'pending' THEN 1 ELSE 0 END) AS deliveriespending, + SUM(CASE WHEN c.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS deliveriescompleted, + SUM(CASE WHEN c.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS deliveriescancelled, + SUM(CASE WHEN c.paymenttype = 64 THEN c.deliveryamt ELSE 0 END) AS paylater, + SUM(CASE WHEN c.paymenttype = 43 THEN c.deliveryamt ELSE 0 END) AS payondelivery, + ROUND(SUM(CAST(COALESCE(NULLIF(c.kms, ''), '0') AS NUMERIC)), 2) AS kms, + ROUND(SUM(CAST(COALESCE(NULLIF(c.actualkms, ''), '0') AS NUMERIC)), 2) AS actualkms, + SUM(c.deliveryamt) AS charges + FROM tenants b + RIGHT JOIN orders a ON b.tenantid = a.tenantid + RIGHT JOIN deliveries c ON a.orderheaderid = c.orderheaderid + LEFT JOIN tenantlocations d ON a.locationid = d.locationid + LEFT JOIN app_users e ON e.userid = c.userid + ` + + // WHERE conditions + var where []string + var params []interface{} + + // Tenant filter + if tid != 0 { + where = append(where, "a.tenantid=?") + params = append(params, tid) + } + + // Partner filter + if pid != 0 { + where = append(where, "a.partnerid=?") + params = append(params, pid) + } + + // Rider (userid) filter + if uid != 0 { + where = append(where, "c.userid=?") + params = append(params, uid) + } + + // Location filter + if aid != 0 { + where = append(where, "a.locationid=?") + params = append(params, aid) + } + + // Date filter + if fdate != "" && tdate != "" { + where = append(where, "a.orderdate::date BETWEEN ?::date AND ?::date") + where = append(where, "c.deliverydate::date BETWEEN ?::date AND ?::date") + params = append(params, fdate, tdate, fdate, tdate) + } + + // Build WHERE clause + if len(where) > 0 { + q1 = base + " WHERE " + strings.Join(where, " AND ") + } else { + q1 = base + } + + // GROUP BY + q1 += " GROUP BY a.locationid, b.tenantid, b.tenantname, d.locationname, e.userid, e.firstname, e.lastname" + + utils.Logger.Debugw("RiderLocationReportSummary SQL generated", "query", q1) + + db.DB.Raw(q1, params...).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetRiderSummary(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + + fdate := c.Query("fromdate") + tdate := c.Query("todate") + keyword := c.Query("keyword") + + var data []models.Ridersummary + + var params []interface{} + q1 := Ridersummary + " WHERE a.configid = 6" + + if uid != 0 { + q1 += " AND a.userid=?" + params = append(params, uid) + } + + if tid != 0 { + q1 += " AND b.tenantid=?" + params = append(params, tid) + } + + if lid != 0 { + q1 += " AND b.locationid=?" + params = append(params, lid) + } + + if aid != 0 { + q1 += " AND a.applocationid=?" + params = append(params, aid) + } + + if pid != 0 { + q1 += " AND a.partnerid=?" + params = append(params, pid) + } + + if fdate != "" && tdate != "" { + q1 += " AND b.deliverydate::date BETWEEN ?::date AND ?::date" + params = append(params, fdate, tdate) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + q1 += " AND (LOWER(a.firstname) LIKE ? OR LOWER(a.lastname) LIKE ? OR LOWER(CONCAT(a.firstname, ' ', a.lastname)) LIKE ?)" + params = append(params, k, k, k) + } + + q1 += " GROUP BY a.userid, a.firstname, a.lastname, a.contactno, a.status" + + utils.Logger.Debugw("RiderSummary SQL generated", "query", q1) + + db.DB.Raw(q1, params...).Scan(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetRiderLocationSummary(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.Ridersummary + var q1 string + + var params []interface{} + q1 = Ridersummary + " WHERE a.configid=6 " + + if tid != 0 { + q1 += " AND b.tenantid=? " + params = append(params, tid) + } + if lid != 0 { + q1 += " AND b.locationid=? " + params = append(params, lid) + } + if aid != 0 { + q1 += " AND a.applocationid=? " + params = append(params, aid) + } + if pid != 0 { + q1 += " AND a.partnerid=? " + params = append(params, pid) + } + if fdate != "" && tdate != "" { + q1 += " AND b.deliverydate::date BETWEEN ?::date AND ?::date " + params = append(params, fdate, tdate) + } + + q1 += " GROUP BY a.userid, b.locationid, a.firstname, a.lastname, a.contactno, b.tenantid, c.tenantname, d.locationname, a.status" + + db.DB.Raw(q1, params...).Scan(&data) + + utils.Logger.Debugw("RiderLocationSummary SQL generated", "query", q1) + + return c.JSON(fiber.Map{ + "code": 200, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetDeliverySummary(c *fiber.Ctx) error { + + // 🔹 Query params + appuid, _ := strconv.Atoi(c.Query("appuserid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + + status := c.Query("status") + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data models.DeliverySummary + var qb strings.Builder + var params []interface{} + + qb.WriteString(core + ` + FROM deliveries a + INNER JOIN tenants b ON a.tenantid = b.tenantid + INNER JOIN app_users c ON a.userid = c.userid + INNER JOIN tenantlocations e ON a.locationid = e.locationid + INNER JOIN app_location f ON a.applocationid = f.applocationid + INNER JOIN app_locationconfig g ON f.applocationid = g.applocationid + WHERE b.moduleid = 6 + AND g.status = 'Active' + `) + + if appuid != 0 { + qb.WriteString(" AND g.userid = ?") + params = append(params, appuid) + } + + if tid != 0 { + qb.WriteString(" AND a.tenantid = ?") + params = append(params, tid) + } + + if lid != 0 { + qb.WriteString(" AND a.locationid = ?") + params = append(params, lid) + } + + if aid != 0 { + qb.WriteString(" AND a.applocationid = ?") + params = append(params, aid) + } + + if uid != 0 { + qb.WriteString(" AND a.userid = ?") + params = append(params, uid) + } + + if status != "" && strings.ToLower(status) != "all" { + qb.WriteString(" AND a.orderstatus = ?") + params = append(params, status) + } + + // 🔹 DATE FILTER (INDEX-FRIENDLY) + if fdate != "" && tdate != "" { + qb.WriteString(` + AND a.deliverydate >= (CONCAT(?::text, ' 00:00:00'))::timestamp + AND a.deliverydate <= (CONCAT(?::text, ' 23:59:59'))::timestamp + `) + params = append(params, fdate, tdate) + } + + finalQuery := qb.String() + + utils.Logger.Infow("DeliverySummary SQL generated", "query", finalQuery, "params", params) + + db.DB.Raw(finalQuery, params...).Scan(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "status": true, + "message": "Success", + "details": data, + }) +} + +func GetlocationdeliverySummary(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var data []models.Ordersummary + var q1 string + var params []interface{} + + q1 = `SELECT b.locationname, a.applocationid, COUNT(*) AS total, + SUM(CASE WHEN orderstatus = 'created' THEN 1 ELSE 0 END) AS created, + SUM(CASE WHEN orderstatus = 'pending' THEN 1 ELSE 0 END) AS pending, + SUM(CASE WHEN orderstatus = 'processing' THEN 1 ELSE 0 END) AS processing, + SUM(CASE WHEN orderstatus = 'delivered' THEN 1 ELSE 0 END) AS delivered, + SUM(CASE WHEN orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled + FROM orders a + INNER JOIN app_location b ON a.applocationid = b.applocationid` + + // Apply tenant filter only if tenantID != 0 + if tenantID != 0 { + q1 += " WHERE a.tenantid = ?" + params = append(params, tenantID) + } + + q1 += " GROUP BY a.applocationid, b.locationname" + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetDeliveryInsight(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var locations []models.OrderInsight + var params []interface{} + + // Query 1: Get distinct locations + q1 := `SELECT DISTINCT a.applocationid, b.locationname + FROM deliveries a + INNER JOIN app_location b ON a.applocationid = b.applocationid + WHERE b.status = 'Active'` + + if tenantID != 0 { + q1 += " AND a.tenantid = ?" + params = append(params, tenantID) + } + + if err := db.DB.Raw(q1, params...).Scan(&locations).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + // Query 2: Fetch delivery insights for each location + q2 := `SELECT + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 1 THEN 1 ELSE 0 END), 0) AS jan, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 2 THEN 1 ELSE 0 END), 0) AS feb, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 3 THEN 1 ELSE 0 END), 0) AS mar, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 4 THEN 1 ELSE 0 END), 0) AS apr, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 5 THEN 1 ELSE 0 END), 0) AS may, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 6 THEN 1 ELSE 0 END), 0) AS jun, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 7 THEN 1 ELSE 0 END), 0) AS jul, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 8 THEN 1 ELSE 0 END), 0) AS aug, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 9 THEN 1 ELSE 0 END), 0) AS sep, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 10 THEN 1 ELSE 0 END), 0) AS oct, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 11 THEN 1 ELSE 0 END), 0) AS nov, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.deliverydate) = 12 THEN 1 ELSE 0 END), 0) AS dece + FROM deliveries a + WHERE a.applocationid = ? AND EXTRACT(YEAR FROM a.deliverydate) = EXTRACT(YEAR FROM CURRENT_DATE)` + + if tenantID != 0 { + q2 += " AND a.tenantid = ?" + } + + // Fetch monthly delivery counts for each location + for i := range locations { + var orderMonths models.Ordermonths + + if tenantID != 0 { + if err := db.DB.Raw(q2, locations[i].Applocationid, tenantID).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } else { + if err := db.DB.Raw(q2, locations[i].Applocationid).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } + + locations[i].Ordermonths = &orderMonths + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": locations, + }) +} + +func DeliverySummary(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("partnerid")) + + var data models.DeliverySummary + + q1 := `SELECT + COUNT(*) AS deliveries, + SUM(CASE WHEN orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled, + SUM(round(kms, 2)) AS kms, + SUM(deliveryamt) AS amount + FROM + deliveries + WHERE + partnerid =?` + + db.DB.Raw(q1, pid).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func CreateDelivery(c *fiber.Ctx) error { + + var data models.Deliveries + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateDelivery(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func CreateDeliveries(c *fiber.Ctx) error { + + var data []models.Deliveries + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateDeliveries(data) + + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + +} + +func CreateDeliveriesV2(c *fiber.Ctx) error { + + var data []models.Deliveries + + if err := c.BodyParser(&data); err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Invalid JSON body", + }) + } + + utils.Logger.Infow("CreateDeliveriesV2 Call", "records", len(data)) + + ids, err := domain.CreateDeliveriesV2(data) + if err != nil { + return c.Status(409).JSON(fiber.Map{ + "status": false, + "code": 409, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": 201, + "message": "Deliveries created successfully", + "deliveryids": ids, + }) +} + +func UpdateDelivery(c *fiber.Ctx) error { + + var data models.UpdateDeliveryStatus + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateDelivery(data) + + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + +} + +func GetDeliveryQueues(c *fiber.Ctx) error { + + fdate := c.Query("fromdate") + tdate := c.Query("todate") + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetDeliveryQueues(uid, fdate, tdate) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetDeliveryQueuesV1(c *fiber.Ctx) error { + + fdate := c.Query("fromdate") + tdate := c.Query("todate") + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetDeliveryQueuesV1(uid, fdate, tdate) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetDeliveryQueuesPicked(c *fiber.Ctx) error { + + fdate := c.Query("fromdate") + tdate := c.Query("todate") + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetDeliveryQueuesPicked(uid, fdate, tdate) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetlocationdeliverySummaryDaily(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var data []models.Ordersummarylocation + var q1 string + var params []interface{} + + q1 = ` + SELECT + b.locationid, + b.locationname, + COALESCE(COUNT(a.orderid), 0) AS total, + COALESCE(SUM(CASE WHEN a.orderstatus = 'created' THEN 1 ELSE 0 END), 0) AS created, + COALESCE(SUM(CASE WHEN a.orderstatus = 'pending' THEN 1 ELSE 0 END), 0) AS pending, + COALESCE(SUM(CASE WHEN a.orderstatus = 'processing' THEN 1 ELSE 0 END), 0) AS processing, + COALESCE(SUM(CASE WHEN a.orderstatus = 'delivered' THEN 1 ELSE 0 END), 0) AS delivered, + COALESCE(SUM(CASE WHEN a.orderstatus = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled + FROM tenantlocations b + LEFT JOIN orders a ON a.locationid = b.locationid + ` + + // ✅ Apply tenant filter on tenantlocations (not orders only) + if tenantID != 0 { + q1 += " WHERE b.tenantid = ?" + params = append(params, tenantID) + } + + q1 += " GROUP BY b.locationid, b.locationname" + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetDeliveryInsightDaily(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var locations []models.OrderInsightv1 + var params []interface{} + + q1 := `SELECT DISTINCT l.locationid, l.locationname + FROM tenantlocations l + LEFT JOIN deliveries d ON l.locationid = d.locationid + WHERE l.status = 'Active'` + + if tenantID != 0 { + q1 += " AND l.tenantid = ?" + params = append(params, tenantID) + } + + if err := db.DB.Raw(q1, params...).Scan(&locations).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + q2 := `SELECT + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 1 THEN 1 ELSE 0 END), 0) AS jan, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 2 THEN 1 ELSE 0 END), 0) AS feb, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 3 THEN 1 ELSE 0 END), 0) AS mar, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 4 THEN 1 ELSE 0 END), 0) AS apr, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 5 THEN 1 ELSE 0 END), 0) AS may, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 6 THEN 1 ELSE 0 END), 0) AS jun, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 7 THEN 1 ELSE 0 END), 0) AS jul, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 8 THEN 1 ELSE 0 END), 0) AS aug, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 9 THEN 1 ELSE 0 END), 0) AS sep, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 10 THEN 1 ELSE 0 END), 0) AS oct, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 11 THEN 1 ELSE 0 END), 0) AS nov, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM d.deliverydate) = 12 THEN 1 ELSE 0 END), 0) AS dece + FROM deliveries d + WHERE d.locationid = ? AND EXTRACT(YEAR FROM d.deliverydate) = EXTRACT(YEAR FROM CURRENT_DATE)` + + if tenantID != 0 { + q2 += " AND d.tenantid = ?" + } + + for i := range locations { + var orderMonths models.Ordermonths + + if tenantID != 0 { + if err := db.DB.Raw(q2, locations[i].Locationid, tenantID).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } else { + if err := db.DB.Raw(q2, locations[i].Locationid).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } + + locations[i].Ordermonths = &orderMonths + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": locations, + }) +} + +func PublishLogv1(c *fiber.Ctx) error { + var data []models.Deliverylogs + + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid JSON body", + }) + } + + err := domain.PublishLogv1(data) + if err != nil { + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successfully saved logs to Redis", + }) +} + +func GetDeliverylogsv1(c *fiber.Ctx) error { + did, _ := strconv.Atoi(c.Query("deliveryid")) + + result, err := domain.GetDeliverylogsv1(did) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + "details": []models.Deliverylogs{}, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetDeliveryLogsv1(c *fiber.Ctx) error { + deliveryID := c.Query("deliveryid") + if deliveryID == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "deliveryid is required", + "status": false, + }) + } + + logs, err := domain.GetDeliveryLogsv1(deliveryID) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "details": logs, + "status": true, + }) +} + +func GetRiderByDelivery(c *fiber.Ctx) error { + + fromdate := strings.TrimSpace(c.Query("fromdate")) + todate := strings.TrimSpace(c.Query("todate")) + keyword := strings.TrimSpace(c.Query("keyword")) + + if fromdate == "" || todate == "" { + return c.Status(400).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "fromdate and todate are required", + }) + } + + applocationidStr := strings.TrimSpace(c.Query("applocationid")) + tenantidStr := strings.TrimSpace(c.Query("tenantid")) + locationidStr := strings.TrimSpace(c.Query("locationid")) + + applocationid, _ := strconv.Atoi(applocationidStr) + tenantid, _ := strconv.Atoi(tenantidStr) + locationid, _ := strconv.Atoi(locationidStr) + + var users []models.User + var params []interface{} + + query := ` + SELECT b.userid,b.authname,b.firstname,b.lastname,b.password,b.email,b.dialcode,b.contactno,b.configid,b.authmode,b.roleid,b.pin,b.deviceid,b.devicetype,b.userfcmtoken, + b.address,b.suburb,b.city,b.state,b.postcode,b.partnerid,a.tenantid,a.locationid,a.applocationid,b.status,b.shiftid,b.hashsalt + FROM deliveries a + LEFT JOIN app_users b ON a.userid = b.userid + WHERE a.deliverydate::date BETWEEN ?::date AND ?::date + ` + params = append(params, fromdate, todate) + + if tenantid != 0 { + query += " AND a.tenantid = ?" + params = append(params, tenantid) + } + if locationid != 0 { + query += " AND a.locationid = ?" + params = append(params, locationid) + } + if applocationid != 0 { + query += " AND a.applocationid = ?" + params = append(params, applocationid) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + query += ` + AND ( + LOWER(b.firstname) LIKE ? OR + LOWER(b.lastname) LIKE ? OR + LOWER(b.contactno) LIKE ? + ) + ` + params = append(params, k, k, k) + } + + query += " GROUP BY b.userid ORDER BY b.userid DESC" + + if err := db.DB.Raw(query, params...).Scan(&users).Error; err != nil { + return c.Status(500).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Database error: " + err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "status": true, + "details": users, + }) +} + +func GetLastDeliveryByContact(c *fiber.Ctx) error { + deliverycontactno := c.Query("deliverycontactno") + fromdate := c.Query("fromdate") + todate := c.Query("todate") + + var data []models.Deliveries + var query string + var args []interface{} + + dateCondition := "" + if fromdate != "" && todate != "" { + dateCondition = " AND deliverydate::date BETWEEN ?::date AND ?::date " + args = append(args, fromdate, todate) + } else if fromdate != "" { + dateCondition = " AND deliverydate::date >= ?::date " + args = append(args, fromdate) + } else if todate != "" { + dateCondition = " AND deliverydate::date <= ?::date " + args = append(args, todate) + } + + if deliverycontactno == "" { + + query = ` + SELECT d.* + FROM deliveries d + INNER JOIN ( + SELECT deliverycontactno, MAX(deliveryid) AS maxid + FROM deliveries + WHERE 1=1 ` + dateCondition + ` + GROUP BY deliverycontactno + ) t ON d.deliveryid = t.maxid + ORDER BY d.deliveryid DESC; + ` + } else { + + query = ` + SELECT * + FROM deliveries + WHERE deliverycontactno = ? ` + dateCondition + ` + ORDER BY deliveryid DESC + LIMIT 1; + ` + + args = append([]interface{}{deliverycontactno}, args...) + } + + if err := db.DB.Raw(query, args...).Scan(&data).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": err.Error(), + "status": false, + }) + } + + return c.Status(200).JSON(fiber.Map{ + "code": 200, + "message": "Success", + "details": data, + "status": true, + }) +} + +func GetUserReportSummary(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + // pid, _ := strconv.Atoi(c.Query("partnerid")) + uid, _ := strconv.Atoi(c.Query("userid")) + // aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.UserReportSummary + var q1 string + + var params []interface{} + q1 = reports + + if tid != 0 { + q1 += " WHERE a.tenantid = ?" + params = append(params, tid) + } else if uid != 0 { + q1 += " WHERE c.userid = ?" + params = append(params, uid) + } else { + q1 += " WHERE 1=1" + } + + if fdate != "" && tdate != "" { + q1 += " AND a.orderdate::date BETWEEN ?::date AND ?::date" + params = append(params, fdate, tdate) + } + + q1 += ` GROUP BY + c.userid, e.firstname, e.lastname, e.contactno, + b.tenantid, b.tenantname` + + utils.Logger.Debugw("RiderLocationReportSummary SQL generated", "query", q1) + + db.DB.Raw(q1, params...).Scan(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetUserDeliveryLogs(c *fiber.Ctx) error { + userid, err := strconv.Atoi(c.Query("userid")) + if err != nil || userid == 0 { + return c.Status(400).JSON(fiber.Map{ + "code": 400, + "message": "Invalid userid", + "status": false, + "details": []models.Deliverylogs{}, + }) + } + + fromdate := strings.TrimSpace(c.Query("fromdate")) + todate := strings.TrimSpace(c.Query("todate")) + + result, err := domain.GetUserDeliveryLogs(userid, fromdate, todate) + if err != nil { + return c.JSON(fiber.Map{ + "code": 409, + "message": err.Error(), + "status": false, + "details": []models.Deliverylogs{}, + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "message": "Success", + "status": true, + "details": result, + }) +} diff --git a/controllers/invoiceController.go b/controllers/invoiceController.go new file mode 100644 index 0000000..dfd0641 --- /dev/null +++ b/controllers/invoiceController.go @@ -0,0 +1,153 @@ +package controllers + +import ( + "nearle/domain" + "nearle/models" + "net/http" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +func InvoiceSeqno(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + seqno := domain.GetSequenceno(tid, "INV") + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "Details": seqno, + }) + +} + +func CreateInvoice(c *fiber.Ctx) error { + + var data models.Tenantsales + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateInvoice(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} + +func GetallInvoice(c *fiber.Ctx) error { + + status, _ := strconv.Atoi(c.Query("billstatus")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + + result := domain.GetAllInvoice(status, tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetInvoiceOrders(c *fiber.Ctx) error { + + fdate := c.Query("fromdate") + tdate := c.Query("todate") + tid, _ := strconv.Atoi(c.Query("tenantid")) + + result := domain.GetInvoiceOrders(tid, fdate, tdate) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetInvoiceInsight(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + result := domain.GetInvoiceInsight(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func UpdateInvoice(c *fiber.Ctx) error { + + var data models.Tenantsales + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateInvoice(data) + + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} + +func UpdateInvoiceStatus(c *fiber.Ctx) error { + + var data models.InvoiceStatus + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateInvoiceStatus(data) + + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} diff --git a/controllers/notificationController.go b/controllers/notificationController.go new file mode 100644 index 0000000..7d05e5f --- /dev/null +++ b/controllers/notificationController.go @@ -0,0 +1,141 @@ +package controllers + +import ( + "context" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + + "sync" + + firebase "firebase.google.com/go" + "firebase.google.com/go/messaging" + "github.com/gofiber/fiber/v2" + "google.golang.org/api/option" +) + +var ( + fcmApp *firebase.App + fcmClient *messaging.Client + fcmOnce sync.Once + fcmInitErr error +) + +// getFCMClient handles the singleton initialization of Firebase +func getFCMClient() (*messaging.Client, error) { + fcmOnce.Do(func() { + ctx := context.Background() + opt := option.WithCredentialsFile("nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json") + + app, err := firebase.NewApp(ctx, nil, opt) + if err != nil { + fcmInitErr = err + return + } + fcmApp = app + + client, err := app.Messaging(ctx) + if err != nil { + fcmInitErr = err + return + } + fcmClient = client + }) + return fcmClient, fcmInitErr +} + +func NotifyUser(c *fiber.Ctx) error { + // Parse the request body + var body struct { + Token string `json:"token"` + Notification models.FcmNotification `json:"notification"` + Data map[string]string `json:"data"` + } + + if err := c.BodyParser(&body); err != nil { + utils.Error("NotifyUser parsing body error", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + // Use shared client instead of initializing on every request + client, err := getFCMClient() + if err != nil { + utils.Error("NotifyUser Firebase initialization error", "error", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "FCM Initialization error: " + err.Error(), + "status": false, + }) + } + + // Construct the message + message := &messaging.Message{ + Token: body.Token, + Notification: &messaging.Notification{ + Title: body.Notification.Title, + Body: body.Notification.Body, + }, + Android: &messaging.AndroidConfig{ + Priority: "high", + Notification: &messaging.AndroidNotification{ + Sound: "ring", + }, + }, + Data: body.Data, + } + + // Send the message + _, err = client.Send(context.Background(), message) + if err != nil { + utils.Error("NotifyUser FCM send error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + + // Return structured response + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "FCM message sent successfully!", + "status": true, + }) +} + +func NotifyUsers(c *fiber.Ctx) error { + + var data models.Notifications + + if err := c.BodyParser(&data); err != nil { + utils.Error("NotifyUsers parsing body error", "error", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + result := domain.SendNotifications(data) + + if !result { + utils.Error("NotifyUsers bulk send failed") + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Failed to send notifications", + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) +} diff --git a/controllers/orderController.go b/controllers/orderController.go new file mode 100644 index 0000000..dadc081 --- /dev/null +++ b/controllers/orderController.go @@ -0,0 +1,1655 @@ +package controllers + +import ( + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +const ( + base = `SELECT COUNT(*) AS total, + SUM(CASE WHEN orderstatus = 'created' THEN 1 ELSE 0 END) AS created, + SUM(CASE WHEN orderstatus = 'pending' THEN 1 ELSE 0 END) AS pending, + SUM(CASE WHEN orderstatus = 'processing' THEN 1 ELSE 0 END) AS processing, + SUM(CASE WHEN orderstatus = 'delivered' THEN 1 ELSE 0 END) AS delivered, + SUM(CASE WHEN orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled` +) + +func CreateCustomerOrder(c *fiber.Ctx) error { + + var data models.Customerorder + + if err := c.BodyParser(&data); err != nil { + return err + } + + tx := db.DB.Begin() + + data.Orders.Orderid = domain.GetSequenceno(data.Orders.Tenantid, "ORD") + + if data.Orders.Customerid != 0 && data.Orders.Deliveryid != 0 { + + // utils.Logger.Debug("coming to existing for creating order with location") + + pid, err := domain.UpdateCustomerv2(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + utils.Logger.Infow("Pickup Location ID updated", "pid", pid) + + data.Orders.Pickuplocationid = pid + + did, err1 := domain.UpdateCustomerv2(data.Drop) + if err1 != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err1.Error(), + "status": false, + }) + + } + + utils.Logger.Infow("Delivery Location ID updated", "did", did) + data.Orders.Deliverylocationid = did + + t1 := tx.Create(&data.Orders) + if t1.Error != nil { + + utils.Logger.Errorw("Tx Create Error", "error", t1.Error) + + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": t1.Error, + "status": false, + }) + } + + } else { + + if data.Orders.Customerid == 0 { + + cid := domain.CheckCustomer(data.Pickup.Contactno) + if cid != 0 { + + pid, err := domain.UpdateCustomerv2(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + utils.Logger.Infow("Customer exists, updating", "pid", pid) + + data.Orders.Pickuplocationid = pid + + } else { + + cid, err := domain.CreateCustomerv2(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } + + utils.Logger.Infow("Created new pickup customer", "customerid", cid.Customerid) + + data.Orders.Customerid = cid.Customerid + data.Orders.Pickuplocationid = cid.Locationid + + } + + } + + if data.Orders.Deliveryid == 0 { + + utils.Logger.Infow("Creating new drop customer") + + cid := domain.CheckCustomer(data.Drop.Contactno) + if cid != 0 { + + pid, err := domain.UpdateCustomerv2(data.Drop) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + data.Orders.Deliverylocationid = pid + + } else { + + did, err := domain.CreateCustomerv2(data.Drop) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } + + utils.Logger.Infow("Created new drop customer", "customerid", did.Customerid) + + data.Orders.Deliveryid = did.Customerid + data.Orders.Deliverylocationid = did.Locationid + + } + + } + + t2 := tx.Create(&data.Orders) + if t2.Error != nil { + + utils.Logger.Errorw("Tx Create Error", "error", t2.Error) + tx.Rollback() + return t2.Error + } + + } + + domain.UpdateSeqno(data.Orders.Tenantid, "ORD") + + err := tx.Commit().Error + + result := domain.GetOrderbyid(data.Orders.Orderheaderid) + + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func CreateCustomerOrderv2(c *fiber.Ctx) error { + + var data models.Customerorder + + if err := c.BodyParser(&data); err != nil { + return err + } + + tx := db.DB.Begin() + + data.Orders.Orderid = domain.GetSequenceno(data.Orders.Tenantid, "ORD") + + if data.Orders.Customerid != 0 && data.Orders.Deliveryid != 0 { + + utils.Logger.Infow("Normal creating order") + + err := domain.UpdateCustomerv1(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + err1 := domain.UpdateCustomerv1(data.Drop) + if err1 != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err1.Error(), + "status": false, + }) + + } + + t1 := tx.Create(&data.Orders) + if t1.Error != nil { + + utils.Logger.Errorw("Tx Create Error", "error", t1.Error) + + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": t1.Error, + "status": false, + }) + } + + } else { + + utils.Logger.Infow("Creating customer and order") + + if data.Orders.Customerid == 0 { + + cid := domain.CheckCustomer(data.Pickup.Contactno) + if cid != 0 { + + data.Pickup.Customerid = cid + + err := domain.UpdateCustomerv1(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + data.Orders.Customerid = cid + + } else { + + cid, err := domain.CreateCustomerv1(data.Pickup) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } + + data.Orders.Customerid = cid + + } + + } + + if data.Orders.Deliveryid == 0 { + + dcid := domain.CheckCustomer(data.Drop.Contactno) + + if dcid != 0 { + + data.Drop.Customerid = dcid + + err := domain.UpdateCustomerv1(data.Drop) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + data.Orders.Deliveryid = dcid + + } else { + + did, err := domain.CreateCustomerv1(data.Drop) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + } + + data.Orders.Deliveryid = did + + } + + } + + t2 := tx.Create(&data.Orders) + if t2.Error != nil { + + utils.Logger.Errorw("Tx Create Error", "error", t2.Error) + tx.Rollback() + return t2.Error + } + + } + + domain.UpdateSeqno(data.Orders.Tenantid, "ORD") + + err := tx.Commit().Error + + utils.Logger.Infow("Order created with header ID", "orderheaderid", data.Orders.Orderheaderid) + + result := domain.GetOrderbyidV2(data.Orders.Orderheaderid) + + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func CreateOrder(c *fiber.Ctx) error { + // Wrapper to parse "orders" key + type OrderWrapper struct { + Orders models.Orders `json:"orders"` + } + + var wrapper OrderWrapper + + // ✅ Parse the JSON body + if err := c.BodyParser(&wrapper); err != nil { + utils.Logger.Errorw("BodyParser error", "error", err) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + data := wrapper.Orders + utils.Logger.Infow("Creating Order", "tenantid", data.Tenantid) + + // ❌ Check for missing tenantid + if data.Tenantid == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Tenant ID is required", + "status": false, + }) + } + + // ✅ Fallback if orderdate is missing + if strings.TrimSpace(data.Orderdate) == "" { + data.Orderdate = time.Now().Format("2006-01-02 15:04:05") + } + + // ✅ Start DB transaction + tx := db.DB.Begin() + + // ✅ Generate order ID + data.Orderid = domain.GetSequenceno(data.Tenantid, "ORD") + + // ✅ Insert order + if err := tx.Create(&data).Error; err != nil { + tx.Rollback() + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to create order", + "status": false, + }) + } + + // ✅ Update order sequence + domain.UpdateSeqno(data.Tenantid, "ORD") + + // ✅ Commit transaction + if err := tx.Commit().Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Transaction failed", + "status": false, + }) + } + + // ✅ Fetch inserted order by ID + result := domain.GetOrderbyid(data.Orderheaderid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func CreateOrders(c *fiber.Ctx) error { + + var orders []models.Orders + + if err := c.BodyParser(&orders); err != nil { + return err + } + + tx := db.DB.Begin() + + for i := range orders { + + // utils.Logger.Debugf("tenantid: %v", orders[i].Tenantid) + + orders[i].Orderid = domain.GetSequenceno(orders[i].Tenantid, "ORD") + t1 := tx.Create(&orders[i]) + if t1.Error != nil { + utils.Logger.Errorw("Tx Create Error", "error", t1.Error) + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error creating order", + "status": false, + }) + } + domain.UpdateSeqno(orders[i].Tenantid, "ORD") + } + + tx.Commit() + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} + +func UpdateOrder(c *fiber.Ctx) error { + + var orders models.Orders + + if err := c.BodyParser(&orders); err != nil { + return err + } + + tx := db.DB.Begin() + + t1 := tx.Where("orderheaderid=?", orders.Orderheaderid).Updates(&orders) + if t1.Error != nil { + utils.Error("UpdateOrder t1 updates error", "error", t1.Error) + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error updating order", + "status": false, + }) + } + + tx.Commit() + + return c.JSON(fiber.Map{ + "code": http.StatusAccepted, + "message": "Success", + "status": true, + }) + +} + +func UpdateMultipleOrders(c *fiber.Ctx) error { + + var orders []models.Orders + + // Parse JSON array input + if err := c.BodyParser(&orders); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + tx := db.DB.Begin() + + for _, order := range orders { + + // Update each order by orderheaderid + if err := tx.Model(&models.Orders{}). + Where("orderheaderid = ?", order.Orderheaderid). + Updates(map[string]interface{}{ + "orderstatus": order.Orderstatus, + "cancelled": order.Cancelled, + }).Error; err != nil { + + tx.Rollback() + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to update order " + strconv.Itoa(order.Orderheaderid), + "status": false, + }) + } + } + + if err := tx.Commit().Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Transaction commit failed", + "status": false, + }) + } + + return c.Status(http.StatusAccepted).JSON(fiber.Map{ + "code": http.StatusAccepted, + "message": "Orders updated successfully", + "status": true, + }) +} + +func GetOrdersv2(c *fiber.Ctx) error { + + var result []models.OrderInfo + + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + mid, _ := strconv.Atoi(c.Query("customerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("appuserid")) + Pageno, _ := strconv.Atoi(c.Query("pageno")) + Pagesize, _ := strconv.Atoi(c.Query("pagesize")) + stat := c.Query("status") + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + if tid != 0 { + + result = domain.GetTenantOrdersv2(stat, fdate, tdate, tid, Pageno, Pagesize) + + } else if pid != 0 { + + result = domain.GetPartnerOrdersv2(stat, fdate, tdate, pid, Pageno, Pagesize) + + } else if cid != 0 { + + result = domain.GetCustomerOrdersv2(stat, fdate, tdate, cid, mid, Pageno, Pagesize) + + } else if aid != 0 { + + result = domain.GetAdminOrdersv2(stat, fdate, tdate, aid, Pageno, Pagesize) + + } else if uid != 0 { + + result = domain.GetUserOrdersv2(stat, fdate, tdate, uid, Pageno, Pagesize) + + } else { + + result = domain.GetAllOrdersv2(stat, fdate, tdate, Pageno, Pagesize) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetOrders(c *fiber.Ctx) error { + var result []models.OrderInfo + aid, _ := strconv.Atoi(c.Query("applocationid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + mid, _ := strconv.Atoi(c.Query("moduleid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + uid, _ := strconv.Atoi(c.Query("appuserid")) + configid, _ := strconv.Atoi(c.Query("configid")) + + stat := c.Query("status") + fdate := c.Query("fromdate") + tdate := c.Query("todate") + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + keyword := c.Query("keyword") + + if pageno <= 0 { + pageno = 1 + } + if pagesize <= 0 { + pagesize = 10 + } + + var info models.DeliveryQuery + info.Partnerid = pid + info.Tenantid = tid + info.UserID = uid + info.Appuserid = uid + info.Locationid = lid + info.Configid = configid + info.Fromdate = fdate + info.ToDate = tdate + info.Status = stat + info.Pageno = pageno + info.Pagesize = pagesize + info.Keyword = keyword + info.Applocationid = aid + + if tid != 0 && lid != 0 && aid != 0 { + result = domain.GetTenantLocationAppOrders(info) + } else if tid != 0 && lid != 0 { + result = domain.GetTenantLocationOrders(info) + } else if tid != 0 && aid != 0 { + result = domain.GetTenantAppOrders(info) + } else if tid != 0 { + result = domain.GetTenantOrders(info) + } else if aid != 0 { + result = domain.GetAppOrders(info) + } else if pid != 0 { + result = domain.GetPartnerOrders(stat, fdate, tdate, pid, pageno, pagesize, keyword) + } else if cid != 0 { + result = domain.GetCustomerOrders(stat, fdate, tdate, cid, mid, pageno, pagesize, keyword) + } else if lid != 0 { + result = domain.GetLocationOrders(stat, fdate, tdate, lid, pageno, pagesize, keyword) + } else if uid != 0 { + result = domain.GetUserOrders(stat, fdate, tdate, uid, pageno, pagesize, keyword) + } else { + result = domain.GetAllOrders(stat, fdate, tdate, aid, pageno, pagesize, keyword) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetOrdersbytenant(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + stat := c.Query("status") + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var q1 string + var data []models.OrderInfo + + var params []interface{} + params = append(params, tid) + + if stat != "" { + + q1 = `SELECT a.orderheaderid,a.tenantid,a.partnerid,a.locationid,a.orderid,a.orderdate,a.orderstatus,a.pending, + a.customerid,b.firstname AS customername,b.contactno,a.pickupaddress,a.pickuplat,a.pickuplong, + a.ordernotes,a.deliverylocationid,a.deliveryaddress, a.deliverylat ,a.deliverylong + FROM orders a + INNER JOIN customers b ON a.customerid=b.customerid + where a.tenantid=? and a.orderstatus='created' and orderdate between ? and ? order by a.orderheaderid desc` + params = append(params, fdate, tdate) + + } else { + + q1 = `SELECT a.orderheaderid,a.tenantid,a.partnerid,a.locationid,a.orderid,a.orderdate,a.orderstatus,a.pending, + a.customerid,b.firstname AS customername,b.contactno,a.pickupaddress,a.pickuplat,a.pickuplong, + a.ordernotes,a.deliverylocationid,a.deliveryaddress, a.deliverylat ,a.deliverylong + FROM orders a + INNER JOIN customers b ON a.customerid=b.customerid + where a.tenantid=? and a.orderstatus='cancelled' and orderdate between ? and ? order by a.orderheaderid desc` + params = append(params, fdate, tdate) + + } + + db.DB.Raw(q1, params...).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetTenantLocationSummary(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + + var data []models.TenantLocationSummary + var q1 string + + if lid == 0 { + + q1 = `SELECT + a.locationid,b.locationname,b.suburb as locationsuburb, + COUNT(*) AS total, + COUNT(CASE WHEN a.orderstatus = 'created' THEN 1 ELSE NULL END) AS created, + COUNT(CASE WHEN a.orderstatus = 'pending' THEN 1 ELSE NULL END) AS pending, + COUNT(CASE WHEN a.orderstatus = 'delivered' THEN 1 ELSE NULL END) AS delivered, + COUNT(CASE WHEN a.orderstatus = 'cancelled' THEN 1 ELSE NULL END) AS cancelled + FROM orders a inner JOIN tenantlocations b ON a.locationid=b.locationid + WHERE a.tenantid= ? GROUP BY a.locationid` + db.DB.Raw(q1, tid).Find(&data) + + } else { + + q1 = `SELECT + a.locationid,b.locationname, + COUNT(*) AS total, + COUNT(CASE WHEN a.orderstatus = 'created' THEN 1 ELSE NULL END) AS created, + COUNT(CASE WHEN a.orderstatus = 'pending' THEN 1 ELSE NULL END) AS pending, + COUNT(CASE WHEN a.orderstatus = 'delivered' THEN 1 ELSE NULL END) AS delivered, + COUNT(CASE WHEN a.orderstatus = 'cancelled' THEN 1 ELSE NULL END) AS cancelled + FROM orders a inner JOIN tenantlocations b ON a.locationid=b.locationid + WHERE a.tenantid= ? and a.locationid= ? GROUP BY a.locationid` + db.DB.Raw(q1, tid, lid).Find(&data) + + } + + // utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1, tid).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetlocationOrderSummary(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var data []models.Ordersummary + var q1 string + var params []interface{} + + q1 = ` + SELECT b.locationname,a.applocationid, COUNT(*) AS total, + SUM(CASE WHEN orderstatus = 'created' THEN 1 ELSE 0 END) AS created, + SUM(CASE WHEN orderstatus = 'pending' THEN 1 ELSE 0 END) AS pending, + SUM(CASE WHEN orderstatus = 'processing' THEN 1 ELSE 0 END) AS processing, + SUM(CASE WHEN orderstatus = 'delivered' THEN 1 ELSE 0 END) AS delivered, + SUM(CASE WHEN orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled + FROM orders a + INNER JOIN app_location b ON a.applocationid=b.applocationid` + + if tenantID != 0 { + q1 += " WHERE a.tenantid = ?" + params = append(params, tenantID) + } + + q1 += " GROUP BY a.applocationid, b.locationname" + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetOrderInsight(c *fiber.Ctx) error { + + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var locations []models.OrderInsight + var params []interface{} + + // Query 1: Get distinct locations + q1 := ` + SELECT DISTINCT + a.applocationid, + b.locationname + FROM orders a + INNER JOIN app_location b + ON a.applocationid = b.applocationid + WHERE b.status = 'Active' + ` + + if tenantID != 0 { + q1 += " AND a.tenantid = ?" + params = append(params, tenantID) + } + + // ✅ OLD STYLE DB USAGE + if err := db.DB.Raw(q1, params...).Scan(&locations).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + + // Query 2: Monthly order counts + q2 := ` + SELECT + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 1 THEN 1 ELSE 0 END), 0) AS jan, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 2 THEN 1 ELSE 0 END), 0) AS feb, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 3 THEN 1 ELSE 0 END), 0) AS mar, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 4 THEN 1 ELSE 0 END), 0) AS apr, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 5 THEN 1 ELSE 0 END), 0) AS may, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 6 THEN 1 ELSE 0 END), 0) AS jun, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 7 THEN 1 ELSE 0 END), 0) AS jul, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 8 THEN 1 ELSE 0 END), 0) AS aug, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 9 THEN 1 ELSE 0 END), 0) AS sep, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 10 THEN 1 ELSE 0 END), 0) AS oct, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 11 THEN 1 ELSE 0 END), 0) AS nov, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 12 THEN 1 ELSE 0 END), 0) AS dece + FROM orders a + WHERE a.applocationid = ? + AND EXTRACT(YEAR FROM a.orderdate) = EXTRACT(YEAR FROM CURRENT_DATE) + ` + + if tenantID != 0 { + q2 += " AND a.tenantid = ?" + } + + // Fetch monthly data per location + for i := range locations { + var orderMonths models.Ordermonths + + if tenantID != 0 { + if err := db.DB.Raw( + q2, + locations[i].Applocationid, + tenantID, + ).Scan(&orderMonths).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + } else { + if err := db.DB.Raw( + q2, + locations[i].Applocationid, + ).Scan(&orderMonths).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + } + + locations[i].Ordermonths = &orderMonths + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": locations, + }) +} + +// func GetOrderInsight(c *fiber.Ctx) error { + +// var data []models.OrderInsight + +// q1 := `SELECT distinct a.applocationid,b.locationname from orders a INNER JOIN app_location b ON a.applocationid=b.applocationid +// WHERE b.status='Active'` + +// q2 := `SELECT a.applocationid,b.locationname, +// SUM(CASE WHEN MONTH(a.orderdate) = 1 THEN 1 ELSE 0 END) AS jan, +// SUM(CASE WHEN MONTH(a.orderdate) = 2 THEN 1 ELSE 0 END) AS feb, +// SUM(CASE WHEN MONTH(a.orderdate) = 3 THEN 1 ELSE 0 END) AS mar, +// SUM(CASE WHEN MONTH(a.orderdate) = 4 THEN 1 ELSE 0 END) AS apr, +// SUM(CASE WHEN MONTH(a.orderdate) = 5 THEN 1 ELSE 0 END) AS may, +// SUM(CASE WHEN MONTH(a.orderdate) = 6 THEN 1 ELSE 0 END) AS jun, +// SUM(CASE WHEN MONTH(a.orderdate) = 7 THEN 1 ELSE 0 END) AS jul, +// SUM(CASE WHEN MONTH(a.orderdate) = 8 THEN 1 ELSE 0 END) AS aug, +// SUM(CASE WHEN MONTH(a.orderdate) = 9 THEN 1 ELSE 0 END) AS sep, +// SUM(CASE WHEN MONTH(a.orderdate) = 10 THEN 1 ELSE 0 END) AS oct, +// SUM(CASE WHEN MONTH(a.orderdate) = 11 THEN 1 ELSE 0 END) AS nov, +// SUM(CASE WHEN MONTH(a.orderdate) = 12 THEN 1 ELSE 0 END) AS dece +// FROM orders a +// INNER JOIN app_location b ON a.applocationid=b.applocationid +// where b.status='Active' and a.applocationid=? and year(a.orderdate)=year(curdate()) +// GROUP BY b.locationname, a.applocationid +// ORDER BY month(a.orderdate) asc` + +// err := db.DB.Raw(q1).Preload("Ordermonths", func(db *gorm.DB) *gorm.DB { +// return db.Raw(q2) +// }).Find(&data).Error + +// //err := db.DB.Raw(q1).Find(&data).Error + +// if err != nil { + +// return c.JSON(fiber.Map{ +// "code": http.StatusConflict, +// "message": err.Error(), +// "status": false, +// }) + +// } + +// return c.JSON(fiber.Map{ +// "code": http.StatusOK, +// "message": "Success", +// "status": true, +// "details": data, +// }) + +// } + +func GetOrderSummary(c *fiber.Ctx) error { + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data models.Ordersummary + var q1 string + + var params []interface{} + q1 = base + ` +FROM orders a +INNER JOIN tenants b + ON a.tenantid = b.tenantid +WHERE a.categoryid <> 2 +AND b.moduleid = 6` + if tid != 0 { + q1 += " AND a.tenantid=?" + params = append(params, tid) + } + if lid != 0 && (tid != 0 || pid == 0) { + q1 += " AND a.locationid=?" + params = append(params, lid) + } + if pid != 0 { + q1 += " AND a.partnerid=?" + params = append(params, pid) + } + if cid != 0 { + q1 += " AND a.customerid=?" + params = append(params, cid) + } + if aid != 0 { + q1 += " AND a.applocationid=?" + params = append(params, aid) + } + if fdate != "" && tdate != "" { + q1 += " AND a.deliverytime >= ? AND a.deliverytime < (?::date + INTERVAL '1 day')" + params = append(params, fdate, tdate) + } + + utils.Logger.Debugw("CancelOrder-Query", "q1", q1) + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.JSON(fiber.Map{ + "code": 500, + "status": false, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Success", + "details": data, + }) +} + +func GetCustomerOrderCount(cid int) int { + + var count int + q1 := "SELECT count(*) as count FROM orders WHERE customerid=?" + + db.DB.Raw(q1, cid).Find(&count) + return count + +} + +func UpdateOrderStatus(input models.Orders) error { + + tx := db.DB.Begin() + + t1 := tx.Where("orderheaderid=?", input.Orderheaderid).Updates(&input) + if t1.Error != nil { + utils.Error("UpdateOrderStatus t1 updates error", "error", t1.Error) + tx.Rollback() + return tx.Error + } + + tx.Commit() + return nil + +} + +// func CreateOrderv3(c *fiber.Ctx) error { +// type OrderWrapper struct { +// Orders models.Orders `json:"orders"` +// } + +// var wrapper OrderWrapper + +// // Parse JSON body +// if err := c.BodyParser(&wrapper); err != nil { +// utils.Logger.Errorw("BodyParser error", "error", err) +// return c.Status(http.StatusBadRequest).JSON(fiber.Map{ +// "code": http.StatusBadRequest, +// "message": "Invalid request body", +// "status": false, +// }) +// } + +// data := wrapper.Orders + +// // Check required field +// if data.Tenantid == 0 { +// return c.Status(http.StatusBadRequest).JSON(fiber.Map{ +// "code": http.StatusBadRequest, +// "message": "Tenant ID is required", +// "status": false, +// }) +// } + +// // Default values if missing +// if strings.TrimSpace(data.Orderdate) == "" { +// data.Orderdate = time.Now().Format("2006-01-02 15:04:05") +// } + +// if strings.TrimSpace(data.Deliverytime) == "" { +// data.Deliverytime = time.Now().Format("2006-01-02 15:04:05") +// } + +// // Start transaction +// tx := db.DB.Begin() + +// // Generate Order ID +// data.Orderid = domain.GetSequenceno(data.Tenantid, "ORD") + +// // Insert order +// if err := tx.Create(&data).Error; err != nil { +// tx.Rollback() +// log.Println("Create order error:", err) +// return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ +// "code": http.StatusInternalServerError, +// "message": "Failed to create order", +// "status": false, +// }) +// } + +// // Insert order items +// for _, item := range data.Items { +// item.Orderheaderid = data.Orderheaderid +// item.Tenantid = data.Tenantid +// item.Locationid = data.Locationid + +// if err := tx.Table("orderdetails").Create(&item).Error; err != nil { +// tx.Rollback() +// log.Println("Insert item error:", err) +// return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ +// "code": http.StatusInternalServerError, +// "message": "Failed to insert order item", +// "status": false, +// }) +// } +// } + +// // Update sequence +// if err := domain.UpdateSeqno(data.Tenantid, "ORD"); err != nil { +// log.Println("Sequence update error:", err) +// } + +// // Commit +// if err := tx.Commit().Error; err != nil { +// log.Println("Commit failed:", err) +// return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ +// "code": http.StatusInternalServerError, +// "message": "Transaction failed", +// "status": false, +// }) +// } + +// // Return order data +// var order models.Orders +// if err := db.DB.Where("orderheaderid = ?", data.Orderheaderid).First(&order).Error; err != nil { +// log.Println("Fetch order error:", err) +// return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ +// "code": http.StatusInternalServerError, +// "message": "Failed to fetch order details", +// "status": false, +// }) +// } + +// // 🔽 Manually fetch order items and assign to Items +// var items []models.OrderDetail +// if err := db.DB.Table("orderdetails").Where("orderheaderid = ?", data.Orderheaderid).Find(&items).Error; err != nil { +// log.Println("Fetch order items error:", err) +// } +// order.Items = items + +// return c.Status(http.StatusOK).JSON(fiber.Map{ +// "code": http.StatusOK, +// "message": "Order created successfully", +// "status": true, +// "details": order, +// }) + +// } + +func CreateOrderv3(c *fiber.Ctx) error { + type OrderWrapper struct { + Orders models.Orders `json:"orders"` + } + + var wrapper OrderWrapper + + if err := c.BodyParser(&wrapper); err != nil { + utils.Logger.Errorw("BodyParser error", "error", err) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + data := wrapper.Orders + + if data.Tenantid == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Tenant ID is required", + "status": false, + }) + } + + if strings.TrimSpace(data.Orderdate) == "" { + data.Orderdate = time.Now().Format("2006-01-02 15:04:05") + } + + if strings.TrimSpace(data.Deliverytime) == "" { + data.Deliverytime = time.Now().Format("2006-01-02 15:04:05") + } + + tx := db.DB.Begin() + + data.Orderid = domain.GetSequenceno(data.Tenantid, "ORD") + + if err := tx.Create(&data).Error; err != nil { + tx.Rollback() + utils.Logger.Errorw("Create order error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to create order", + "status": false, + }) + } + + for _, item := range data.Items { + item.Orderheaderid = data.Orderheaderid + item.Tenantid = data.Tenantid + item.Locationid = data.Locationid + + if err := tx.Table("orderdetails").Create(&item).Error; err != nil { + tx.Rollback() + utils.Logger.Errorw("Insert item error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to insert order item", + "status": false, + }) + } + + stock := models.Productstock{ + Tenantid: data.Tenantid, + Stockdate: time.Now(), + Locationid: data.Locationid, + Productid: item.Productid, + Quantity: int(item.Orderqty), + Stocktype: "out", + Status: "Active", + } + + if err := tx.Table("productstocks").Create(&stock).Error; err != nil { + tx.Rollback() + utils.Logger.Errorw("Insert product stock error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to insert product stock", + "status": false, + }) + } + } + + if err := domain.UpdateSeqno(data.Tenantid, "ORD"); err != nil { + utils.Logger.Errorw("Sequence update error", "error", err) + } + + if err := tx.Commit().Error; err != nil { + utils.Logger.Errorw("Commit failed", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Transaction failed", + "status": false, + }) + } + + var order models.Orders + if err := db.DB.Where("orderheaderid = ?", data.Orderheaderid).First(&order).Error; err != nil { + utils.Logger.Errorw("Fetch order error", "error", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to fetch order details", + "status": false, + }) + } + + var items []models.OrderDetail + if err := db.DB.Table("orderdetails").Where("orderheaderid = ?", data.Orderheaderid).Find(&items).Error; err != nil { + utils.Logger.Errorw("Fetch order items error", "error", err) + } + order.Items = items + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Order created successfully", + "status": true, + "details": order, + }) +} + +func GetOrderDetails(c *fiber.Ctx) error { + orderHeaderIDStr := c.Query("orderheaderid") + if orderHeaderIDStr == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "orderheaderid is required", + "status": false, + "details": []interface{}{}, + }) + } + + orderHeaderID, err := strconv.Atoi(orderHeaderIDStr) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "invalid orderheaderid", + "status": false, + "details": []interface{}{}, + }) + } + + details, orderAmount, totalTaxAmount, err := domain.GetOrderDetailsByHeaderID(orderHeaderID) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to fetch order details", + "status": false, + "details": []interface{}{}, + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "pricedetails": fiber.Map{ + "orderamount": orderAmount, + "totaltaxamount": totalTaxAmount, + }, + "details": details, + "message": "Success", + "status": true, + }) +} + +func GetCustomerOrders(c *fiber.Ctx) error { + customerID := c.Query("customerid") + tenantID := c.Query("tenantid") + moduleID := c.Query("moduleid") + fromDate := c.Query("fromdate") + toDate := c.Query("todate") + orderStatus := c.Query("orderstatus") + keyword := c.Query("keyword") + pageNo, _ := strconv.Atoi(c.Query("pageno", "1")) + pageSize, _ := strconv.Atoi(c.Query("pagesize", "10")) + + if pageNo < 1 { + pageNo = 1 + } + if pageSize < 1 { + pageSize = 10 + } + offset := (pageNo - 1) * pageSize + + // Ensure response is [] not null + orders := make([]models.CustomerOrder, 0) + var orderdetails models.OrderDetails + + baseQuery := ` + SELECT DISTINCT a.orderheaderid, a.applocationid, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, + a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverytime, + a.pending, a.processing, a.ready, a.delivered AS delivered, a.cancelled, + a.deliverycharge, a.kms, a.customerid, a.pickupaddress, a.pickuplat, a.pickuplong, + a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, + a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, + a.deliverycustomer, a.deliverycontactno, a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, a.taxamount, + b.tenantname, b.tenanttoken, b.primarycontact AS tenantcontactno, b.postcode AS tenantpostcode, b.suburb AS tenantsuburb, b.city AS tenantcity, + c.locationname, c.contactno AS locationcontactno, c.postcode AS locationpostcode, c.suburb AS locationsuburb, c.city AS locationcity, + d.locationname AS applocation, f.orderstatus AS deliverystatus, + f.deliveryid, f.assigntime, f.starttime, f.arrivaltime, f.pickuptime, f.deliverytime AS finaldeliverytime, f.canceltime, + f.droplat, f.droplon, g.firstname AS rider + FROM orders a + INNER JOIN tenants b ON a.tenantid = b.tenantid + INNER JOIN tenantlocations c ON a.locationid = c.locationid + INNER JOIN app_location d ON a.applocationid = d.applocationid + INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid + LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid + LEFT JOIN app_users g ON f.userid = g.userid + LEFT JOIN orderdetails h ON h.orderheaderid = a.orderheaderid + WHERE 1=1 + ` + + params := []interface{}{} + + if customerID != "" { + baseQuery += " AND a.customerid = ?" + params = append(params, customerID) + } + if tenantID != "" && tenantID != "0" { + baseQuery += " AND a.tenantid = ?" + params = append(params, tenantID) + } + if moduleID != "" { + baseQuery += " AND a.moduleid = ?" + params = append(params, moduleID) + } + if fromDate != "" && toDate != "" { + baseQuery += " AND a.orderdate::date BETWEEN ?::date AND ?::date" + params = append(params, fromDate, toDate) + } + if orderStatus != "" { + baseQuery += " AND a.orderstatus = ?" + params = append(params, orderStatus) + } + if keyword != "" { + baseQuery += " AND h.productname LIKE ?" + params = append(params, "%"+keyword+"%") + } + + // ✅ Pagination enabled + baseQuery += " ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?" + params = append(params, pageSize, offset) + + utils.Logger.Debugw("Executing query", "query", baseQuery, "params", params) + + if err := db.DB.Raw(baseQuery, params...).Scan(&orders).Error; err != nil { + utils.Logger.Errorw("Query execution error", "error", err) + return c.Status(500).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Failed to fetch customer orders", + "error": err.Error(), + }) + } + + for i := range orders { + orderDetails, orderAmount, totalTaxAmount, _ := domain.GetOrderDetailsByHeaderID(orders[i].Orderheaderid) + orders[i].OrderDetails = orderDetails + orders[i].Orderamount = orderAmount + orderdetails.Totaltaxamount = totalTaxAmount + } + + return c.JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Customer orders fetched successfully", + "data": orders, + }) +} + +func GetOrderInsightDaily(c *fiber.Ctx) error { + dbConn, ok := c.Locals("DB").(*gorm.DB) + if !ok || dbConn == nil { + return c.JSON(fiber.Map{ + "code": 500, + "message": "Database connection not found", + "status": false, + }) + } + + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var locations []models.OrderInsightv1 + var params []interface{} + + // ✅ Query 1: Get ALL locations (even without orders) + q1 := `SELECT b.locationid, b.locationname + FROM tenantlocations b + WHERE b.status = 'Active'` + + if tenantID != 0 { + q1 += " AND b.tenantid = ?" + params = append(params, tenantID) + } + + if err := dbConn.Raw(q1, params...).Scan(&locations).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + // ✅ Query 2: Fetch order insights for each location + q2 := `SELECT + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 1 THEN 1 ELSE 0 END), 0) AS jan, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 2 THEN 1 ELSE 0 END), 0) AS feb, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 3 THEN 1 ELSE 0 END), 0) AS mar, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 4 THEN 1 ELSE 0 END), 0) AS apr, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 5 THEN 1 ELSE 0 END), 0) AS may, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 6 THEN 1 ELSE 0 END), 0) AS jun, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 7 THEN 1 ELSE 0 END), 0) AS jul, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 8 THEN 1 ELSE 0 END), 0) AS aug, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 9 THEN 1 ELSE 0 END), 0) AS sep, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 10 THEN 1 ELSE 0 END), 0) AS oct, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 11 THEN 1 ELSE 0 END), 0) AS nov, + COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 12 THEN 1 ELSE 0 END), 0) AS dece + FROM orders a + WHERE a.locationid = ? AND EXTRACT(YEAR FROM a.orderdate) = EXTRACT(YEAR FROM CURRENT_DATE)` + + if tenantID != 0 { + q2 += " AND a.tenantid = ?" + } + + // ✅ Attach monthly order counts for each location + for i := range locations { + var orderMonths models.Ordermonths + + if tenantID != 0 { + if err := dbConn.Raw(q2, locations[i].Locationid, tenantID).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } else { + if err := dbConn.Raw(q2, locations[i].Locationid).Scan(&orderMonths).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + } + + locations[i].Ordermonths = &orderMonths + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": locations, + }) +} + +func GetOrderSummaryDaily(c *fiber.Ctx) error { + tid, _ := strconv.Atoi(c.Query("tenantid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + cid, _ := strconv.Atoi(c.Query("customerid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.Ordersummarydaily + var q1 string + + // 👇 join with tenants instead of locations + baseWithTenant := base + `, t.tenantid, t.tenantname + FROM orders o + INNER JOIN tenants t ON o.tenantid = t.tenantid` + + // Filters + var params []interface{} + q1 = baseWithTenant + + if tid != 0 { + q1 += " WHERE o.tenantid = ?" + params = append(params, tid) + } else if pid != 0 { + q1 += " WHERE o.partnerid = ?" + params = append(params, pid) + } else if cid != 0 { + q1 += " WHERE o.customerid = ?" + params = append(params, cid) + } else if lid != 0 { + q1 += " WHERE o.locationid = ?" + params = append(params, lid) + } else { + q1 += " WHERE 1=1" + } + + if fdate != "" && tdate != "" { + q1 += " AND o.orderdate::date BETWEEN ?::date AND ?::date" + params = append(params, fdate, tdate) + } + + // 👇 group by tenantid & tenantname + q1 += ` GROUP BY t.tenantid, t.tenantname` + + utils.Logger.Debugf("Query: %s", q1) + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetLocationOrderSummaryDaily(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + tenantID, _ := strconv.Atoi(tenantIDStr) + + var data []models.Ordersummarylocation + var q1 string + var params []interface{} + + q1 = ` + SELECT + l.locationid, l.locationname, + COALESCE(COUNT(o.orderid), 0) AS total, + COALESCE(SUM(CASE WHEN o.orderstatus = 'created' THEN 1 ELSE 0 END), 0) AS created, + COALESCE(SUM(CASE WHEN o.orderstatus = 'pending' THEN 1 ELSE 0 END), 0) AS pending, + COALESCE(SUM(CASE WHEN o.orderstatus = 'processing' THEN 1 ELSE 0 END), 0) AS processing, + COALESCE(SUM(CASE WHEN o.orderstatus = 'delivered' THEN 1 ELSE 0 END), 0) AS delivered, + COALESCE(SUM(CASE WHEN o.orderstatus = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled + FROM tenantlocations l + LEFT JOIN orders o + ON l.locationid = o.locationid + AND l.tenantid = o.tenantid + ` + + if tenantID != 0 { + q1 += " WHERE l.tenantid = ?" + params = append(params, tenantID) + } + + q1 += " GROUP BY l.locationid, l.locationname ORDER BY l.locationid" + + if err := db.DB.Raw(q1, params...).Scan(&data).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetCustomerOrderByLocation(c *fiber.Ctx) error { + cid, _ := strconv.Atoi(c.Query("customerid")) + + result, err := domain.GetCustomerOrderByLocation(cid) + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + "details": []interface{}{}, // return [] on error + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} diff --git a/controllers/partnerController.go b/controllers/partnerController.go new file mode 100644 index 0000000..dc2e407 --- /dev/null +++ b/controllers/partnerController.go @@ -0,0 +1,1015 @@ +package controllers + +import ( + "context" + "encoding/json" + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" +) + +func GetActiveRiders(c *fiber.Ctx) error { + pid, _ := strconv.Atoi(c.Query("partnerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + keyword := c.Query("keyword") + + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + result := domain.GetActiveRiders(pid, aid, uid, keyword, pageno, pagesize) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetActiveRidersv2(c *fiber.Ctx) error { + pid, _ := strconv.Atoi(c.Query("partnerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + + result := domain.GetActiveRidersv2(pid, aid, uid, tid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetRiderShifts(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetRiderShifts(aid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetRiderPricing(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetRiderPricing(aid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetRiderPool(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("partnerid")) + + result := domain.GetRiderPool(pid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetRiderInfo(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetRiderInfo(uid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetRiderInfov2(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetRiderInfov2(uid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetRiderDetails(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetRiderDetails(uid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetAllRiders(c *fiber.Ctx) error { + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + status := c.Query("status") + keyword := c.Query("keyword") + + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + if pageno == 0 { + pageno = 1 + } + + result := domain.GetAllRiders(aid, pid, uid, status, keyword, pageno, pagesize) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetPartners(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("user")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetPartners(aid, pid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetLocationConfig(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + cid, _ := strconv.Atoi(c.Query("configid")) + + result := domain.GetLocationConfig(uid, cid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetPartnerusers(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetPartnerUsers(aid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetAdminToken(c *fiber.Ctx) error { + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + uid, _ := strconv.Atoi(c.Query("userid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetAdmintoken(pid, uid, tid, aid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func UpdatePartnerUser(c *fiber.Ctx) error { + + var data models.Tenants + + if err := c.BodyParser(&data); err != nil { + return err + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": data, + }) + +} + +func CreateRider(c *fiber.Ctx) error { + + var data models.Rider + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateRider(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func UpdateRiderSettings(c *fiber.Ctx) error { + + var data models.Rider + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateRiderSettings(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func UpdateRiderInfo(c *fiber.Ctx) error { + + var data models.Rider + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateRiderInfo(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func CreateRiderShift(c *fiber.Ctx) error { + + var data models.Ridershifts + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateRiderShift(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func CreateRiderLog(c *fiber.Ctx) error { + + var data models.Riderlogs + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateRiderLog(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func CreateBreakLog(c *fiber.Ctx) error { + + var data models.Riderbreaks + + if err := c.BodyParser(&data); err != nil { + return err + } + + id, err := domain.CreateBreakLog(data) + if err != nil || id == 0 { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } + data = domain.GetBreaklog(id) + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": data, + }) +} + +func UpdateBreakLog(c *fiber.Ctx) error { + + var data models.Riderbreaks + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateBreakLog(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusAccepted, + "message": "Success", + "status": true, + }) + + } + +} + +func UpdateRiderLog(c *fiber.Ctx) error { + + var data models.Riderlogs + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateRiderLog(data) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + + } + +} + +func GetRiderLog(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + data, err := domain.GetRiderLog(uid) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": data, + }) + + } + +} + +func GetRiderOrderCount(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + data, err := domain.GetRiderOrderCount(uid) + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } else { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": data, + }) + + } + +} + +func GetRiderLogs(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("partnerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + keyword := c.Query("keyword") + + data := domain.GetRiderLogs(pid, aid, fdate, tdate, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func CreateBreakLogv1(c *fiber.Ctx) error { + var data models.Riderbreaks + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": err.Error(), + "status": false, + }) + } + + id, err := domain.CreateBreakLogv1(data) + if err != nil || id == 0 { + msg := "Failed to create break log" + if err != nil { + msg = err.Error() + } + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "code": http.StatusConflict, + "message": msg, + "status": false, + }) + } + + data = domain.GetBreaklogv1(id) + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": data, + }) +} + +func UpdateBreakLogv1(c *fiber.Ctx) error { + var data models.Riderbreaks + + if err := c.BodyParser(&data); err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + err := domain.UpdateBreakLogv1(data) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusAccepted, + "message": "Success", + "status": true, + }) +} + +func GetDeliveryStats(c *fiber.Ctx) error { + userid := c.Query("userid") + if userid == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Missing userid parameter", + }) + } + + var stats models.DeliveryStats + + deliveredQuery := ` + SELECT + SUM(CASE WHEN deliverydate::date = CURRENT_DATE THEN 1 ELSE 0 END) AS today, + SUM(CASE WHEN EXTRACT(WEEK FROM deliverydate) = EXTRACT(WEEK FROM CURRENT_DATE) AND EXTRACT(YEAR FROM deliverydate) = EXTRACT(YEAR FROM CURRENT_DATE) THEN 1 ELSE 0 END) AS week, + SUM(CASE WHEN EXTRACT(YEAR FROM deliverydate) = EXTRACT(YEAR FROM CURRENT_DATE) AND EXTRACT(MONTH FROM deliverydate) = EXTRACT(MONTH FROM CURRENT_DATE) THEN 1 ELSE 0 END) AS month, + COUNT(*) AS total + FROM deliveries + WHERE orderstatus = 'delivered' AND userid = ?; + ` + + if err := db.DB.Raw(deliveredQuery, userid).Scan(&stats).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": 500, + "message": "Failed to fetch delivered stats", + }) + } + + cancelledQuery := ` + SELECT + COALESCE(SUM(CASE WHEN orderstatus = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled + FROM deliveries + WHERE userid = ?; + ` + + if err := db.DB.Raw(cancelledQuery, userid).Scan(&stats.Cancelled).Error; err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": 500, + "message": "Failed to fetch cancelled stats", + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Delivery stats fetched successfully", + "data": stats, + }) +} + +func GetRiderWeeklyKms(c *fiber.Ctx) error { + uidStr := c.Query("userid") + if uidStr == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Missing userid parameter", + "status": false, + }) + } + + uid, err := strconv.Atoi(uidStr) + if err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid userid", + "status": false, + }) + } + + data, err := domain.GetRiderWeeklyKms(uid) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to fetch rider kms", + "status": false, + "error": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Rider kms fetched successfully", + "status": true, + "total_kms": data.TotalKms, + "overall_kms": data.OverallKms, + "details": data.Details, + }) +} + +func CreateRiderLogv1(c *fiber.Ctx) error { + var data models.Riderlogs + + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + err := domain.CreateRiderLogv1(data) + if err != nil { + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) +} + +func UpdateRiderLogv1(c *fiber.Ctx) error { + var data models.Riderlogs + + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + err := domain.UpdateRiderLogv1(data) + if err != nil { + return c.Status(http.StatusConflict).JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + } + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) +} + +func CreateRiderSupport(c *fiber.Ctx) error { + + var data models.RiderSupport + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateRiderSupport(data) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + } else { + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + } +} + +func GetRiderSupport(c *fiber.Ctx) error { + + uid, _ := strconv.Atoi(c.Query("userid")) + + data, err := domain.GetRiderSupport(uid) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err, + "status": false, + }) + } else { + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "data": data, + }) + } +} + +func GetRiderLogsv1(c *fiber.Ctx) error { + fromdate := c.Query("fromdate") + todate := c.Query("todate") + keyword := c.Query("keyword") + + partneridStr := c.Query("partnerid") + partnerid, _ := strconv.Atoi(partneridStr) + + if fromdate == "" || todate == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "fromdate and todate are required", + "status": false, + }) + } + + logs, err := domain.GetRiderLogsv1(fromdate, todate, keyword, partnerid) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "details": logs, + "status": true, + }) +} + +func DeleteRiderLogs(c *fiber.Ctx) error { + useridStr := c.Query("userid") + fromStr := c.Query("fromdate") + toStr := c.Query("todate") + + if useridStr == "" || fromStr == "" || toStr == "" { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "userid, fromdate and todate are required", + }) + } + + userid, _ := strconv.Atoi(useridStr) + + layout := "2006-01-02" + fromDate, err := time.Parse(layout, fromStr) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Invalid fromdate format (YYYY-MM-DD)", + }) + } + + toDate, err := time.Parse(layout, toStr) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Invalid todate format (YYYY-MM-DD)", + }) + } + + ctx := context.Background() + + logs, err := db.Rdb.LRange(ctx, "riderlogs", 0, -1).Result() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "code": 500, + "message": "Failed to read riderlogs from Redis", + }) + } + + db.Rdb.Del(ctx, "riderlogs") + + deletedCount := 0 + + for _, item := range logs { + var log models.Deliverylogs + json.Unmarshal([]byte(item), &log) + + logDate, err := time.Parse("2006-01-02", log.Logdate) + if err != nil { + continue + } + + if log.Userid == userid && (logDate.Equal(fromDate) || logDate.After(fromDate)) && + (logDate.Equal(toDate) || logDate.Before(toDate)) { + + deletedCount++ + continue + } + + db.Rdb.RPush(ctx, "riderlogs", item) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Filtered rider logs deleted successfully", + "deleted": deletedCount, + }) +} + +func GetAllRidersSummary(c *fiber.Ctx) error { + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + keyword := c.Query("keyword") + + result := domain.GetAllRidersSummary(aid, pid, uid, keyword) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetActiveRidersSummary(c *fiber.Ctx) error { + pid, _ := strconv.Atoi(c.Query("partnerid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + keyword := c.Query("keyword") + + result := domain.GetActiveRidersSummary(pid, aid, uid, keyword) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetUserLocationSummary(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + uid, _ := strconv.Atoi(c.Query("userid")) + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + var data []models.UserReportSummary + var where []string + var args []interface{} + + where = append(where, "a.configid = 6") + + if fdate != "" && tdate != "" { + where = append(where, "b.deliverydate::date BETWEEN ?::date AND ?::date") + args = append(args, fdate, tdate) + } + + if tid != 0 { + where = append(where, "b.tenantid = ?") + args = append(args, tid) + } + + if lid != 0 { + where = append(where, "b.locationid = ?") + args = append(args, lid) + } + + if aid != 0 { + where = append(where, "a.applocationid = ?") + args = append(args, aid) + } + + if pid != 0 { + where = append(where, "a.partnerid = ?") + args = append(args, pid) + } + + if uid != 0 { + where = append(where, "a.userid = ?") + args = append(args, uid) + } + + q := Ridersummary + + if len(where) > 0 { + q += " WHERE " + strings.Join(where, " AND ") + } + + q += ` + GROUP BY + a.userid, + b.locationid + ORDER BY + a.userid + ` + + utils.Logger.Debugw("RiderSummary SQL generated", "query", q) + + if err := db.DB.Raw(q, args...).Scan(&data).Error; err != nil { + return c.Status(500).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": 200, + "message": "Success", + "status": true, + "details": data, + }) +} diff --git a/controllers/paymentsController.go b/controllers/paymentsController.go new file mode 100644 index 0000000..c142e1d --- /dev/null +++ b/controllers/paymentsController.go @@ -0,0 +1,55 @@ +package controllers + +import ( + "nearle/db" + "nearle/models" + "net/http" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +func CreatePaymentRequest(c *fiber.Ctx) error { + + var data models.Paymentrequests + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := db.DB.Create(&data).Error + + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) +} + +func GetPaymentRequests(c *fiber.Ctx) error { + + var data []models.RequestInfo + + pid, _ := strconv.Atoi(c.Query("partnerid")) + status, _ := strconv.Atoi(c.Query("status")) + + q1 := "select * from paymentrequests where partnerid=? and status=?" + db.DB.Raw(q1, pid, status).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} diff --git a/controllers/platform.go b/controllers/platform.go new file mode 100644 index 0000000..83dc997 --- /dev/null +++ b/controllers/platform.go @@ -0,0 +1,37 @@ +package controllers + +import ( + "nearle/domain" + "net/http" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +func GetModules(c *fiber.Ctx) error { + + mid, _ := strconv.Atoi(c.Query("moduleid")) + + result := domain.GetModules(mid) + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetSmsProvider(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("templatetypeid")) + + result := domain.GetSmsprovider(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} diff --git a/controllers/productsController.go b/controllers/productsController.go new file mode 100644 index 0000000..7968edf --- /dev/null +++ b/controllers/productsController.go @@ -0,0 +1,868 @@ +package controllers + +import ( + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" +) + +func GetProductInfo(c *fiber.Ctx) error { + + pid, _ := strconv.Atoi(c.Query("productid")) + + utils.Logger.Debugf("productid: %d", pid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} + +func GetProducts(c *fiber.Ctx) error { + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) + +} + +func GetProductCategory(c *fiber.Ctx) error { + data := domain.GetProductCategory() + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetProductSubCategory(c *fiber.Ctx) error { + categoryID, _ := strconv.Atoi(c.Query("categoryid")) + tenantID, _ := strconv.Atoi(c.Query("tenantid")) + + data := domain.GetProductSubCategory(categoryID, tenantID) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func CreateProductVariant(c *fiber.Ctx) error { + var input models.Productvariant + + if err := c.BodyParser(&input); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + if err := domain.CreateProductVariant(input); err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "Failed", + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) +} + +func CreateProduct(c *fiber.Ctx) error { + var product models.Products + + if err := c.BodyParser(&product); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid request body", + "status": false, + }) + } + + if err := domain.CreateProduct(&product); err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to create product", + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Product created successfully", + "status": true, + }) +} + +func GetAllProducts(c *fiber.Ctx) error { + categoryID, _ := strconv.Atoi(c.Query("categoryid")) + subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid")) + productID, _ := strconv.Atoi(c.Query("productid")) + applocationID, _ := strconv.Atoi(c.Query("applocationid")) + tenantID, _ := strconv.Atoi(c.Query("tenantid")) + locationID, _ := strconv.Atoi(c.Query("locationid")) + keyword := c.Query("keyword", "") + productStatus := c.Query("productstatus", "") + approve := c.Query("approve", "") + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + details, err := domain.FetchFilteredProducts( + categoryID, subcategoryID, productID, applocationID, tenantID, + locationID, keyword, productStatus, approve, pageno, pagesize, + ) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "Failed", + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "data": details, + }) +} + +func UpdateProduct(c *fiber.Ctx) error { + + var data models.Products + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateProduct(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Product update successful", + }) + +} + +func DeleteProduct(c *fiber.Ctx) error { + // Get product ID from query + pidStr := c.Query("productid") + pid, err := strconv.Atoi(pidStr) + if err != nil || pid <= 0 { + return c.JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "Invalid product ID", + "status": false, + }) + } + + // Start transaction + tx := db.DB.Begin() + if tx.Error != nil { + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to start transaction", + "status": false, + }) + } + + // Delete product + if err := tx.Table("products").Where("productid = ?", pid).Delete(&models.Products{}).Error; err != nil { + tx.Rollback() + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Error deleting product", + "status": false, + }) + } + + // Commit transaction + if err := tx.Commit().Error; err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": "Failed to commit transaction", + "status": false, + }) + } + + // Success response + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + }) +} + +func GetProductsBySubcategory(c *fiber.Ctx) error { + // log.Println("Handler: GetProductsBySubcategory called") + + // Required query param: categoryid + categoryIDStr := c.Query("categoryid") + if categoryIDStr == "" { + utils.Error("Error: categoryid is missing") + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "categoryid is required", + "data": fiber.Map{}, + }) + } + + categoryID, err := strconv.Atoi(categoryIDStr) + if err != nil { + utils.Logger.Errorf("Error: Invalid categoryid: %s", categoryIDStr) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid categoryid", + "data": fiber.Map{}, + }) + } + + // Optional tenantid + tenantIDStr := c.Query("tenantid") + tenantID := 0 + if tenantIDStr != "" { + tenantID, err = strconv.Atoi(tenantIDStr) + if err != nil { + utils.Logger.Errorf("Error: Invalid tenantid: %s", tenantIDStr) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid tenantid", + "data": fiber.Map{}, + }) + } + } + + // Optional query params + applocationIDStr := c.Query("applocationid", "0") + applocationID, _ := strconv.Atoi(applocationIDStr) + + productIDStr := c.Query("productid") + keyword := c.Query("keyword") + + locationIDStr := c.Query("locationid") + locationID := 0 + if locationIDStr != "" { + locationID, _ = strconv.Atoi(locationIDStr) + } + + productID := 0 + if productIDStr != "" { + productID, err = strconv.Atoi(productIDStr) + if err != nil { + utils.Logger.Errorf("Error: Invalid productid: %s", productIDStr) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid productid", + "data": fiber.Map{}, + }) + } + } + + // Fetch subcategories + var subcategories []models.Subcategory + utils.Logger.Infof("Fetching subcategories for categoryid: %d", categoryID) + if err := db.DB.Table("productsubcategories"). + Where("categoryid = ?", categoryID). + Find(&subcategories).Error; err != nil { + utils.Logger.Errorf("Error fetching subcategories: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Error fetching subcategories", + "data": fiber.Map{}, + }) + } + + // Build product query + var allProducts []models.Products + query := db.DB. + Table("products a"). + Joins("LEFT JOIN productlocations pl ON pl.productid = a.productid"). + Where("a.categoryid = ?", categoryID) + + if tenantID > 0 { + query = query.Where("a.tenantid = ?", tenantID) + } + + if locationID > 0 { + utils.Logger.Infof("Filtering by locationid: %d", locationID) + query = query.Where("pl.locationid = ?", locationID) + } else { + utils.Info("No locationid provided or value is 0 — showing all locations") + } + + if applocationID > 0 { + query = query.Where("a.applocationid = ?", applocationID) + } + + if productID > 0 { + query = query.Where("a.productid = ?", productID) + } + + if keyword != "" { + utils.Info("Applying keyword filter") + like := "%" + strings.ToLower(keyword) + "%" + query = query.Where( + db.DB.Where("LOWER(a.productname) LIKE ?", like). + Or("LOWER(a.unitvalue) LIKE ?", like). + Or("LOWER(CAST(a.productcost AS CHAR)) LIKE ?", like), + ) + } + + if err := query.Select("a.*").Find(&allProducts).Error; err != nil { + utils.Logger.Errorf("Error fetching products: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Error fetching products", + "data": fiber.Map{}, + }) + } + + // Group products under subcategories + var details []models.SubcategoryProductResponse + var uncategorizedProducts []models.Products + + for _, subcat := range subcategories { + var subcatProducts []models.Products + for _, prod := range allProducts { + if prod.Subcategoryid != nil && *prod.Subcategoryid == subcat.Subcategoryid { + subcatProducts = append(subcatProducts, prod) + } + } + if len(subcatProducts) > 0 { + details = append(details, models.SubcategoryProductResponse{ + SubcategoryID: subcat.Subcategoryid, + SubcategoryName: subcat.Subcategoryname, + Products: subcatProducts, + }) + } + } + + for _, prod := range allProducts { + if prod.Subcategoryid == nil { + uncategorizedProducts = append(uncategorizedProducts, prod) + } + } + if len(uncategorizedProducts) > 0 { + details = append(details, models.SubcategoryProductResponse{ + SubcategoryID: 0, + SubcategoryName: "Uncategorized", + Products: uncategorizedProducts, + }) + } + + // Tenant info block only if tenantID is passed + if tenantID > 0 { + var tenantInfo = fiber.Map{} + var tenantFound bool + for _, p := range allProducts { + if p.Tenantid != nil { + var tenant struct { + Tenantname string + Address string + Licenseno string + Primaryemail string + Primarycontact string + Locationname string + Pickuplocationid int + Suburb string + City string + Latitude string + Longitude string + Postcode string + } + + err := db.DB.Raw(` + SELECT + t.tenantname, + t.address, + t.licenseno, + t.primaryemail, + t.primarycontact, + l.locationid AS pickuplocationid, + l.suburb, + l.city, + l.latitude, + l.longitude, + l.postcode, + a.locationname + FROM tenants t + LEFT JOIN tenantlocations l ON t.tenantid = l.tenantid + LEFT JOIN app_location a ON l.applocationid = a.applocationid + WHERE t.tenantid = ? AND t.applocationid = ? + LIMIT 1 + `, *p.Tenantid, applocationID).Scan(&tenant).Error + + if err == nil { + tenantInfo = fiber.Map{ + "tenantname": tenant.Tenantname, + "address": tenant.Address, + "licenseno": tenant.Licenseno, + "primaryemail": tenant.Primaryemail, + "primarycontact": tenant.Primarycontact, + "locationname": tenant.Locationname, + "pickuplocationid": tenant.Pickuplocationid, + "suburb": tenant.Suburb, + "city": tenant.City, + "pickuplat": tenant.Latitude, + "pickuplong": tenant.Longitude, + "postcode": tenant.Postcode, + "details": details, + } + tenantFound = true + } + break + } + } + + if tenantFound { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Success", + "data": tenantInfo, + }) + } + } + + // Return only details if tenantid is not provided + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Success", + "data": fiber.Map{ + "details": details, + }, + }) +} + +func GetProductCount(c *fiber.Ctx) error { + tenantID, _ := strconv.Atoi(c.Query("tenantid")) + categoryID, _ := strconv.Atoi(c.Query("categoryid")) + subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid")) + approve := c.Query("approve", "") // New line + + result, err := domain.Getproductcount(tenantID, categoryID, subcategoryID, approve) + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "failed", + "status": false, + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetProductByVariant(c *fiber.Ctx) error { + + tenantID, _ := strconv.Atoi(c.Query("tenantid")) + variantid, _ := strconv.Atoi(c.Query("variantid")) + + result, err := domain.GetproductbyVariant(tenantID, variantid) + + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "failed", + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetCatalougeProducts(c *fiber.Ctx) error { + tenantId, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + subcategoryid, _ := strconv.Atoi(c.Query("subcategoryid")) + keyword := c.Query("keyword") + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + data := domain.GetCatalougeProducts(tenantId, locationid, subcategoryid, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetStockstatement(c *fiber.Ctx) error { + tenantId, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + subcategoryid, _ := strconv.Atoi(c.Query("subcategoryid")) + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + keyword := c.Query("keyword") + + data := domain.GetStockStatement(tenantId, locationid, subcategoryid, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetProductVariants(c *fiber.Ctx) error { + tenantId, _ := strconv.Atoi(c.Query("tenantid")) + subcategoryId, _ := strconv.Atoi(c.Query("subcategoryid")) + + data := domain.GetProductVariants(tenantId, subcategoryId) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func CreateProductLocation(c *fiber.Ctx) error { + var data []models.Productlocations + + if err := c.BodyParser(&data); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + err := domain.CreateProductLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "Success", + }) + +} + +func UpdateProductLocation(c *fiber.Ctx) error { + + var data models.Productlocations + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateProductLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Product update successful", + }) + +} + +func CreateProductStock(c *fiber.Ctx) error { + var stocks []models.Productstock + + if err := c.BodyParser(&stocks); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + for i := range stocks { + + stocks[i].Stockdate = time.Now() + + } + + // Insert all stock records + if err := db.DB.Table("productstocks").Create(&stocks).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to create product stocks", + "status": false, + }) + } + + // Collect and dedupe product IDs to update their status + idMap := make(map[int]struct{}) + var productIDs []int + for _, s := range stocks { + if s.Productid <= 0 { + continue + } + if _, ok := idMap[s.Productid]; !ok { + idMap[s.Productid] = struct{}{} + productIDs = append(productIDs, s.Productid) + } + } + + // Only update if we have product ids + if len(productIDs) > 0 { + if err := db.DB.Table("products"). + Where("productid IN ?", productIDs). + Update("productstatus", "available").Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to update product status", + "status": false, + }) + } + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{ + "code": 201, + "message": "Product stocks created successfully", + "status": true, + "details": stocks, + }) +} + +func UpdateProductStock(c *fiber.Ctx) error { + var stock models.Productstock + + // Parse body + if err := c.BodyParser(&stock); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + // Validate required ID + if stock.Productstockid == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Missing productstockid", + "status": false, + }) + } + + // Attempt update + if err := domain.UpdateProductStock(&stock); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to update product stock", + "status": false, + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "message": "Product stock updated successfully", + "status": true, + }) +} + +func GetProductStocks(c *fiber.Ctx) error { + tenantID := c.Query("tenantid") + locationID := c.Query("locationid") + + stocks, err := domain.GetProductStocks(tenantID, locationID) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to fetch product stocks", + "status": false, + }) + } + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "code": 200, + "message": "Product stocks fetched successfully", + "status": true, + "data": stocks, + }) +} + +func GetLocationProducts(c *fiber.Ctx) error { + + tenantid, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + subcategoryid, _ := strconv.Atoi(c.Query("subcategoryid")) + keyword := c.Query("keyword") + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + result := domain.GetLocationProducts(tenantid, locationid, subcategoryid, pageno, pagesize, keyword) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "details": result, + }) +} + +func GetSubCategoryWiseSummary(c *fiber.Ctx) error { + tenantId, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + subcategoryid, _ := strconv.Atoi(c.Query("subcategoryid")) + + data, err := domain.GetSubCategoryWiseSummary(tenantId, locationid, subcategoryid) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Failed to fetch subcategory-wise summary", + }) + } + + if data == nil { + data = []models.SubCategorySummary{} + } + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Subcategory-wise summary fetched successfully", + "status": true, + "details": data, + }) +} + +func GetLocationProductSummary(c *fiber.Ctx) error { + tenantid, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + + result := domain.GetLocationProductSummary(tenantid, locationid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "details": result, + }) +} + +func GetStockStatementSummary(c *fiber.Ctx) error { + tenantId, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + + data := domain.GetStockStatementSummary(tenantId, locationid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func CreateProductDiscount(c *fiber.Ctx) error { + + var data models.ProductDiscount + + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid JSON body", + }) + } + + if len(data.Locationid) > 0 { + data.LocationidStr = strings.Join(data.Locationid, ",") + } + + data.Status = "Active" + + if err := domain.CreateProductDiscount(&data); err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "Product discount created successfully", + "data": data, + }) +} + +func GetProductDiscounts(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid := c.Query("locationid") + data := domain.GetProductDiscounts(tid, lid) + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Success", + "details": data, + }) +} diff --git a/controllers/tenantController.go b/controllers/tenantController.go new file mode 100644 index 0000000..72b04c4 --- /dev/null +++ b/controllers/tenantController.go @@ -0,0 +1,1109 @@ +package controllers + +import ( + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "strconv" + "strings" + "time" + + "net/http" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func Createtenant(c *fiber.Ctx) error { + + var data models.Tenants + var seq models.Ordersequences + + var result models.Tenantinfo + + if err := c.BodyParser(&data); err != nil { + return err + } + + tid := domain.Checktenantbyno(data.Primarycontact) + if tid != 0 { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "Tenant Already Exists", + "status": false, + }) + + } + tx := db.DB.Begin() + + t1 := tx.Create(&data) + if t1.Error != nil { + utils.Error("Createtenant t1 error", "error", t1.Error) + tx.Rollback() + } + + seq.Tenantid = data.Tenantid + + t2 := tx.Table("ordersequences").Create(&seq) + if t2.Error != nil { + utils.Error("Createtenant t2 error", "error", t2.Error) + tx.Rollback() + } + + err := tx.Commit().Error + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + result = domain.GetTeanantById(data.Tenantid) + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Successfully Created", + "status": true, + "details": result, + }) + +} + +func CreateLocation(c *fiber.Ctx) error { + + var data models.Tenantlocations + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Location Successfully Created", + "status": true, + }) + +} + +func CreatetenantUser(c *fiber.Ctx) error { + + var data models.Tenants + var result models.UserInfo + + if err := c.BodyParser(&data); err != nil { + return err + } + + tid := domain.Checktenantbyno(data.Primarycontact) + if tid != 0 { + + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": "Tenant Already Exists", + "status": false, + }) + + } + + status, err := domain.CreateTenantUser(data) + if !status { + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": err.Error(), + "status": false, + }) + + } else { + + result = domain.Getuserbyno(data.Primarycontact) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Successfully Created", + "status": true, + "details": result, + }) + +} + +func GetTenantInfo(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + data := domain.GetTeanantById(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetTenantStatus(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + data := domain.GetTeanantById(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetTenantPricing(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + aid, _ := strconv.Atoi(c.Query("applocationid")) + + data := domain.GetTeanantPricing(tid, aid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetPricingList(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + data := domain.GetPricinglist(tid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func CreateStaff(c *fiber.Ctx) error { + + var data models.User + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := db.DB.Table("app_users").Create(&data).Error + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + +} + +func CreatePricing(c *fiber.Ctx) error { + + var data models.Tenantpricing + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := db.DB.Table("tenantpricing").Create(&data).Error + if err != nil { + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Success", + "status": true, + }) + +} + +func UpdateStaff(c *fiber.Ctx) error { + + var data models.User + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := db.DB.Table("app_users").Where("userid=?", data.Userid).Updates(&data).Error + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err.Error(), + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "User update successful", + }) + +} + +func GetStaffs(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + + var data []models.StaffInfo + + q1 := `SELECT a.userid,a.firstname,a.lastname,concat(a.firstname,'',a.lastname) as fullname,a.email,a.contactno,a.address,a.suburb,a.city,a.state,a.postcode,a.userfcmtoken,a.pin,a.applocationid, + a.roleid,a.partnerid,a.tenantid,a.locationid,b.locationname + from app_users a INNER JOIN tenantlocations b ON a.locationid=b.locationid where a.tenantid=?` + + db.DB.Raw(q1, tid).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetTenants(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + pid, _ := strconv.Atoi(c.Query("partnerid")) + Stat := strings.ToUpper(c.Query("status")) + + var data []models.Tenantinfo + + query := db.DB.Table("tenants as a"). + Select("a.*, b.subcategoryname"). + Joins("INNER JOIN app_subcategory b ON b.subcategoryid = a.subcategoryid") + + // 🔥 Fixed conditions (always applied) + query = query.Where("a.tenanttype = ? AND a.approved = ?", "E", 1) + + // 🔍 Dynamic filters + if aid != 0 { + query = query.Where("a.applocationid = ?", aid) + } + if pid != 0 { + query = query.Where("a.partnerid = ?", pid) + } + if Stat != "" { + query = query.Where("UPPER(a.status) = ?", Stat) + } + + query.Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func SearchTenant(c *fiber.Ctx) error { + + status := c.Query("status") + searchstr := c.Query("keyword") + + var data []models.Tenantinfo + + // Use query builder for safer and cleaner code + query := db.DB.Table("tenants as a"). + Select("a.*, b.subcategoryname, c.firstname, c.lastname, concat(c.firstname,' ',c.lastname) AS accountname"). + Joins("INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid"). + Joins("LEFT JOIN app_users c on c.userid=a.partneruserid") + + if status != "pending" { + query = query.Where("a.approved = 1 AND lower(a.status) = ?", strings.ToLower(status)) + } else { + query = query.Where("a.approved = 0") + } + + if searchstr != "" { + query = query.Where("lower(a.tenantname) like ?", strings.ToLower(searchstr)+"%") + } + + query.Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +// func GetAllTenants(c *fiber.Ctx) error { + +// pageno, _ := strconv.Atoi(c.Query("pageno")) +// pagesize, _ := strconv.Atoi(c.Query("pagesize")) +// status := c.Query("status") +// aid, _ := strconv.Atoi(c.Query("applocationid")) + +// offset := (pageno - 1) * pagesize + +// var data []models.Tenantinfo +// var q1 string + +// if status == "active" { + +// if aid != 0 { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=1 and a.status='Active' and a.applocationid=` + strconv.Itoa(aid) + ` ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } else { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation +// FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=1 and a.status='Active' ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } + +// } else if status == "inactive" { + +// if aid != 0 { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=1 and a.status='InActive' and a.applocationid=` + strconv.Itoa(aid) + ` ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } else { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation +// FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=1 and a.status='InActive' ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } + +// } else if status == "pending" { + +// if aid != 0 { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=0 and a.applocationid=` + strconv.Itoa(aid) + ` ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } else { + +// q1 = `SELECT a.*,b.subcategoryname, c.locationname as applocation +// FROM tenants a +// INNER JOIN app_subcategory b on a.subcategoryid=b.subcategoryid +// INNER JOIN app_location c on a.applocationid=c.applocationid +// where a.approved=0 ORDER BY a.tenantid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + +// } + +// } + +// // println(q1) + +// db.DB.Raw(q1).Find(&data) + +// return c.JSON(fiber.Map{ +// "code": http.StatusOK, +// "message": "Success", +// "status": true, +// "details": data, +// }) + +// } + +func GetAllTenants(c *fiber.Ctx) error { + + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + status := c.Query("status") + aid, _ := strconv.Atoi(c.Query("applocationid")) + moduleid, _ := strconv.Atoi(c.Query("moduleid")) + tenanttype := c.Query("tenanttype") + keyword := c.Query("keyword") + + details := domain.Getalltenants(pageno, pagesize, aid, moduleid, status, tenanttype, keyword) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": details, + }) +} + +func GetTenantSummary(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid", "0")) + moduleid, _ := strconv.Atoi(c.Query("moduleid", "0")) + tenanttype := c.Query("tenanttype", "0") + keyword := c.Query("keyword", "") + + type Result struct { + Total int64 + Active int64 + Inactive int64 + Pending int64 + } + + var result Result + + query := db.DB.Table("tenants"). + Select(` + COUNT(*) AS total, + COUNT(*) FILTER (WHERE approved = 1 AND status = 'Active') AS active, + COUNT(*) FILTER (WHERE approved = 1 AND status = 'InActive') AS inactive, + COUNT(*) FILTER (WHERE approved = 0) AS pending + `) + + // Apply filters + if aid != 0 { + query = query.Where("applocationid = ?", aid) + } + + if moduleid != 0 { + query = query.Where("moduleid = ?", moduleid) + } + + if tenanttype != "0" { + query = query.Where("tenanttype = ?", tenanttype) + } + + if keyword != "" { + k := "%" + keyword + "%" + query = query.Where("tenantname ILIKE ? OR primarycontact ILIKE ?", k, k) + } + + // Execute single query + err := query.Scan(&result).Error + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Summary fetched successfully", + "status": true, + "summary": fiber.Map{ + "total": result.Total, + "active": result.Active, + "inactive": result.Inactive, + "pending": result.Pending, + }, + }) +} + +func GetCloudStore(c *fiber.Ctx) error { + + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + status := c.Query("status") + aid, _ := strconv.Atoi(c.Query("applocationid")) + + offset := (pageno - 1) * pagesize + + var data []models.Tenantinfo + + query := db.DB.Table("tenants as a"). + Select("a.*, c.locationname as applocation"). + Joins("INNER JOIN app_location c on a.applocationid=c.applocationid"). + Where("a.tenanttype = ?", "C") + + if status == "active" { + query = query.Where("a.approved = 1 AND a.status = ?", "Active") + } else if status == "inactive" { + query = query.Where("a.approved = 1 AND a.status = ?", "InActive") + } else if status == "pending" { + query = query.Where("a.approved = 0") + } + + if aid != 0 { + query = query.Where("a.applocationid = ?", aid) + } + + query.Order("a.tenantid desc").Limit(pagesize).Offset(offset).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) + +} + +func GetTenantLocations(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + keyword := strings.ToLower(c.Query("keyword")) + + var data []models.Tenantlocations + + db.DB.Table("tenantlocations as a"). + Select("a.*, b.roleid"). + Joins("LEFT JOIN app_users b ON a.locationid = b.locationid AND a.tenantid = b.tenantid"). + Where("a.tenantid = ?", tid). + Scopes(func(db *gorm.DB) *gorm.DB { + if keyword != "" { + k := "%" + keyword + "%" + return db.Where("LOWER(a.locationname) LIKE ? OR LOWER(a.contactno) LIKE ?", k, k) + } + return db + }). + Find(&data) + + var result []map[string]interface{} + + for _, loc := range data { + + // ✅ Build slots in required format + slots := []map[string]string{} + + if loc.Slot1 != "" { + slots = append(slots, map[string]string{ + "name": "Slot 1", + "time": loc.Slot1, + }) + } + if loc.Slot2 != "" { + slots = append(slots, map[string]string{ + "name": "Slot 2", + "time": loc.Slot2, + }) + } + if loc.Slot3 != "" { + slots = append(slots, map[string]string{ + "name": "Slot 3", + "time": loc.Slot3, + }) + } + if loc.Slot4 != "" { + slots = append(slots, map[string]string{ + "name": "Slot 4", + "time": loc.Slot4, + }) + } + if loc.Slot5 != "" { + slots = append(slots, map[string]string{ + "name": "Slot 5", + "time": loc.Slot5, + }) + } + + m := map[string]interface{}{ + "locationid": loc.Locationid, + "tenantid": loc.Tenantid, + "applocationid": loc.Applocationid, + "moduleid": loc.Moduleid, + "locationname": loc.Locationname, + "email": loc.Email, + "contactno": loc.Contactno, + "latitude": loc.Latitude, + "longitude": loc.Longitude, + "address": loc.Address, + "suburb": loc.Suburb, + "city": loc.City, + "state": loc.State, + "postcode": loc.Postcode, + "opentime": loc.Opentime, + "closetime": loc.Closetime, + "partnerid": loc.Partnerid, + "deliveryradius": loc.Deliveryradius, + "deliverymins": loc.Deliverymins, + "cancelsecs": loc.Cancelsecs, + "slots": slots, + "status": loc.Status, + } + + result = append(result, m) + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetTenantSlot(c *fiber.Ctx) error { + + var data models.Tenantslot + + q1 := `SELECT * FROM tenantslot` + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1).Find(&data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} + +func GetTenantLocation(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + + var data models.Tenantlocations + + db.DB.Table("tenantlocations").Where("tenantid = ? AND locationid = ?", tid, lid).Find(&data) + + result := map[string]interface{}{ + "locationid": data.Locationid, + "tenantid": data.Tenantid, + "applocationid": data.Applocationid, + "moduleid": data.Moduleid, + "locationname": data.Locationname, + "email": data.Email, + "contactno": data.Contactno, + "latitude": data.Latitude, + "longitude": data.Longitude, + "address": data.Address, + "suburb": data.Suburb, + "city": data.City, + "state": data.State, + "postcode": data.Postcode, + "opentime": data.Opentime, + "closetime": data.Closetime, + "partnerid": data.Partnerid, + "deliveryradius": data.Deliveryradius, + "deliverymins": data.Deliverymins, + "cancelsecs": data.Cancelsecs, + "slots": []map[string]string{ + {"slot1": data.Slot1}, + {"slot2": data.Slot2}, + {"slot3": data.Slot3}, + {"slot4": data.Slot4}, + {"slot5": data.Slot5}, + }, + "status": data.Status, + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func UpdateTenant(c *fiber.Ctx) error { + + var data models.Tenants + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateTenant(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Tenant update successful", + }) + +} + +func UpdateLocation(c *fiber.Ctx) error { + + var data models.Tenantlocations + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Location update successful", + }) + +} + +func Createtenantcustomer(c *fiber.Ctx) error { + var req models.CreateTenantCustomerRequest + + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid request body", + "data": fiber.Map{}, + }) + } + + tenantcustomers := models.TenantCustomer{ + TenantID: req.TenantID, + LocationID: req.LocationID, + CustomerID: req.CustomerID, + CustomerLocationID: req.CustomerLocationID, + Status: req.Status, + } + + if err := db.DB.Create(&tenantcustomers).Error; err != nil { + utils.Logger.Errorf("Error inserting tenant customer: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "status": false, + "message": "Failed to create tenant customer", + "data": fiber.Map{}, + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Tenant customer created successfully", + "data": tenantcustomers, + }) +} + +func GetCustomerTenants(c *fiber.Ctx) error { + customerID, err := strconv.Atoi(c.Query("customerid")) + if err != nil || customerID == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid customerid", + "status": false, + "data": fiber.Map{}, + }) + } + + // categoryIDStr := c.Query("categoryid") + // var categoryID int + // if categoryIDStr != "" { + // categoryID, _ = strconv.Atoi(categoryIDStr) + // } + + data := domain.GetCustomerLocations(customerID) + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "data": data, + }) +} + +func CreateTenantLocation(c *fiber.Ctx) error { + + var data models.Tenantlocations + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.CreateTenantLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "code": http.StatusConflict, + "message": err.Error(), + "status": false, + }) + + } + + return c.JSON(fiber.Map{ + "code": http.StatusCreated, + "message": "Tenant Location Successfully Created", + "status": true, + }) + +} + +func UpdateTenantLocation(c *fiber.Ctx) error { + + var data models.Tenantlocations + + if err := c.BodyParser(&data); err != nil { + return err + } + + err := domain.UpdateTenantLocation(data) + + if err != nil { + return c.JSON(fiber.Map{ + "status": false, + "code": http.StatusConflict, + "message": err, + }) + + } + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusAccepted, + "message": "Tenant Location update successful", + }) + +} + +func GetLocationSummary(c *fiber.Ctx) error { + tenantID, err := strconv.Atoi(c.Query("tenantid")) + if err != nil || tenantID <= 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid tenantid", + }) + } + + summary := domain.FetchLocationSummary(tenantID) + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "status": true, + "message": "Success", + "details": summary, + }) +} + +func GetTenantStaffSummary(c *fiber.Ctx) error { + tenantID, err := strconv.Atoi(c.Query("tenantid")) + if err != nil || tenantID == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid tenantid", + "details": fiber.Map{}, + }) + } + + summary := domain.FetchTenantStaffSummary(tenantID) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "status": true, + "message": "Success", + "details": summary, + }) +} + +func GetTenantRiderSummary(c *fiber.Ctx) error { + tenantID, err := strconv.Atoi(c.Query("tenantid")) + if err != nil || tenantID == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "status": false, + "message": "Invalid tenantid", + "details": fiber.Map{}, + }) + } + + summary := domain.FetchTenantRiderSummary(tenantID) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "status": true, + "message": "Success", + "details": summary, + }) +} + +func CreateTenantRequest(c *fiber.Ctx) error { + var request models.TenantRequest + + if err := c.BodyParser(&request); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid request body", + "status": false, + }) + } + + // Set timestamps if not already set + request.Requestdate = time.Now() + request.Created = time.Now() + request.Updated = time.Now() + + // Insert into tenantrequests table + if err := db.DB.Table("tenantrequests").Create(&request).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to create tenant request", + "status": false, + }) + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{ + "code": 201, + "message": "Tenant request created successfully", + "status": true, + "data": request, + }) +} + +func GetTenantRequests(c *fiber.Ctx) error { + tenantIDStr := c.Query("tenantid") + pageNoStr := c.Query("pageno", "1") + pageSizeStr := c.Query("pagesize", "10") + + pageNo, _ := strconv.Atoi(pageNoStr) + pageSize, _ := strconv.Atoi(pageSizeStr) + + if pageNo < 1 { + pageNo = 1 + } + if pageSize < 1 { + pageSize = 10 + } + offset := (pageNo - 1) * pageSize + + var requests []models.TenantRequest + query := db.DB.Table("tenantrequests") + + // Optional filter by tenantid + if tenantIDStr != "" { + tenantID, err := strconv.Atoi(tenantIDStr) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "code": 400, + "message": "Invalid tenantid", + "status": false, + }) + } + if tenantID != 0 { + query = query.Where("tenantid = ?", tenantID) + } + } + + var total int64 + query.Count(&total) + + if err := query.Order("created desc").Offset(offset).Limit(pageSize).Find(&requests).Error; err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "code": 500, + "message": "Failed to fetch tenant requests", + "status": false, + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "code": 200, + "message": "Tenant requests fetched successfully", + "status": true, + "data": requests, + }) +} + +func CreateTenantPromotion(c *fiber.Ctx) error { + var data models.Tenantpromotions + + // Parse body (Locationid comes as []string) + if err := c.BodyParser(&data); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid JSON body", + }) + } + + if len(data.Locationid) > 0 { + data.LocationidStr = strings.Join(data.Locationid, ",") + } + + // Save to DB + if err := domain.CreateTenantPromotion(&data); err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": err.Error(), + }) + } + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "Promotion created successfully", + "data": data, + }) +} + +func GetTenantPromotions(c *fiber.Ctx) error { + + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid := c.Query("locationid") // user sends single location id + + data := domain.GetTenantPromotions(tid, lid) + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Success", + "details": data, + }) +} diff --git a/controllers/trackControllor.go b/controllers/trackControllor.go new file mode 100644 index 0000000..3b6ce5e --- /dev/null +++ b/controllers/trackControllor.go @@ -0,0 +1,17 @@ +package controllers + +import ( + "net/http" + + "github.com/gofiber/fiber/v2" +) + +func PublishLogs(c *fiber.Ctx) error { + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusConflict, + "message": "Heloo", + }) + +} diff --git a/controllers/utilsController.go b/controllers/utilsController.go new file mode 100644 index 0000000..d896e3b --- /dev/null +++ b/controllers/utilsController.go @@ -0,0 +1,1509 @@ +package controllers + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "nearle/db" + "nearle/domain" + "nearle/models" + "nearle/utils" + "net/http" + "net/url" + "time" + + // "os" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/redis/go-redis/v9" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +func GetApplocations(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetApplocations(aid) + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetApplocationsv2(c *fiber.Ctx) error { + + userid, _ := strconv.Atoi(c.Query("userid")) + + result := domain.GetApplocationsv2(userid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetApplocationConfig(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetApplocationConfig(aid) + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) + +} + +func GetApplocationadmins(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetApplocationadmins(aid) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +func GetAppPricing(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + cid, _ := strconv.Atoi(c.Query("configid")) + pid, _ := strconv.Atoi(c.Query("pricingtypeid")) + + result := domain.GetAppPricing(aid, cid, pid) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetAllAppPricing(c *fiber.Ctx) error { + + aid, _ := strconv.Atoi(c.Query("applocationid")) + + result := domain.GetAllAppPricing(aid) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func CreateAppPricing(c *fiber.Ctx) error { + + var data models.Apppricing + + if err := c.BodyParser(&data); err != nil { + return err + } + + result := domain.CreateAppProcing(data) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetAppConfig(c *fiber.Ctx) error { + + cid, _ := strconv.Atoi(c.Query("configid")) + + result := domain.GetAppconfig(cid) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} +func GetAllAppConfig(c *fiber.Ctx) error { + + result := domain.GetAllAppconfig() + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetApptypes(c *fiber.Ctx) error { + + tag := c.Query("tag") + + result := domain.GetApptypes(tag) + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetSubcategories(c *fiber.Ctx) error { + mid, _ := strconv.Atoi(c.Query("moduleid")) // 0 means all modules + cid, _ := strconv.Atoi(c.Query("categoryid")) // 0 means all categories + + result := domain.GetSubcategories(mid, cid) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +func GetCategories(c *fiber.Ctx) error { + mid, _ := strconv.Atoi(c.Query("moduleid", "0")) // default to 0 + + result := domain.GetCategories(mid) + + // Ensure result is never null + if result == nil { + result = []models.AppCategory{} + } + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) +} + +const ( + fcmURL = "https://fcm.googleapis.com/v1/projects/nearle-gear/messages:send" + scope = "https://www.googleapis.com/auth/firebase.messaging" + serviceAcc = "nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json" // Path to your service account JSON +) + +var client *http.Client + +// Structure of FCM message + +// GetClientFromServiceAccount reads the service account JSON file and generates a new token +func GetClientFromServiceAccount() (*http.Client, error) { + data, err := ioutil.ReadFile(serviceAcc) + if err != nil { + return nil, fmt.Errorf("failed to read service account file: %v", err) + } + + config, err := google.JWTConfigFromJSON(data, scope) + if err != nil { + return nil, fmt.Errorf("failed to parse service account JSON: %v", err) + } + + // Use the token source to generate an HTTP client that automatically refreshes the token + client := config.Client(oauth2.NoContext) + return client, nil +} + +func sendFCMMessage(token, title, body string) error { + if client == nil { + var err error + client, err = GetClientFromServiceAccount() + if err != nil { + return fmt.Errorf("failed to get client: %v", err) + } + } + + notification := Notification{ + Title: title, + Body: body, + } + + fcmRequest := FCMRequestBody{} + fcmRequest.Message.Notification = notification + fcmRequest.Message.Token = token + + requestBody, err := json.Marshal(fcmRequest) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", fcmURL, bytes.NewBuffer(requestBody)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return fmt.Errorf("failed to send message: %s", string(bodyBytes)) + } + + return nil +} + +func NotifyAdmin(c *fiber.Ctx) error { + body := new(struct { + Token []string `json:"token"` + Notification struct { + Title string `json:"title"` + Body string `json:"body"` + Sound string `json:"sound"` + Type string `json:"type"` + } `json:"notification"` + }) + + if err := c.BodyParser(body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + if len(body.Token) == 0 { + return fiber.NewError(fiber.StatusBadRequest, "No tokens provided") + } + + // Only send FCM to provided tokens + var successCount, failureCount int + for _, token := range body.Token { + err := sendFCMMessage(token, body.Notification.Title, body.Notification.Body) + + if err != nil { + utils.Logger.Errorf("❌ Failed to send to %s: %v", token, err) + failureCount++ + } else { + utils.Logger.Infof("✅ Notification sent to %s", token) + successCount++ + } + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "message": "Admin notifications sent", + "success": successCount, + "failure": failureCount, + }) +} + +func NotifyTenant(c *fiber.Ctx) error { + + body := new(struct { + Token []string `json:"token"` + TenantID int `json:"tenantid"` + ModuleID int `json:"moduleid"` + LocationID int `json:"locationid"` + CustomerID int `json:"customerid"` + Notification struct { + Title string `json:"title"` + Body string `json:"body"` + Sound string `json:"sound"` + Type string `json:"type"` + } `json:"notification"` + }) + + if err := c.BodyParser(body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + if len(body.Token) == 0 { + return fiber.NewError(fiber.StatusBadRequest, "No tokens provided") + } + + successCount := 0 + failureCount := 0 + + // Loop through each token and send notification + insert DB log + for _, token := range body.Token { + + err := sendFCMMessage(token, body.Notification.Title, body.Notification.Body) + + successCode := 0 + if err != nil { + utils.Logger.Errorf("❌ Failed to send to %s: %v", token, err) + failureCount++ + } else { + utils.Logger.Infof("✅ Notification sent to %s", token) + successCount++ + successCode = 1 + } + + // Insert into tenantnotifications table + insertQuery := ` + INSERT INTO tenantnotifications + (title, message, tenantid, moduleid, locationid, customerid, notificationdate, successcode) + VALUES (?, ?, ?, ?, ?, ?, NOW(), ?) + ` + + if dbErr := db.DB.Exec( + insertQuery, + body.Notification.Title, + body.Notification.Body, + body.TenantID, + body.ModuleID, + body.LocationID, + body.CustomerID, + successCode, + ).Error; dbErr != nil { + utils.Logger.Errorf("❌ DB Insert Error: %v", dbErr) + } + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "message": "Tenant notifications processed", + "success": successCount, + "failure": failureCount, + }) +} + +func GetTenantNotifications(c *fiber.Ctx) error { + tenantid, _ := strconv.Atoi(c.Query("tenantid")) + locationid, _ := strconv.Atoi(c.Query("locationid")) + customerid, _ := strconv.Atoi(c.Query("customerid")) + moduleid, _ := strconv.Atoi(c.Query("moduleid")) + + pageno, _ := strconv.Atoi(c.Query("pageno")) + pagesize, _ := strconv.Atoi(c.Query("pagesize")) + + if pageno == 0 { + pageno = 1 + } + if pagesize == 0 { + pagesize = 10 + } + + result, _ := domain.GetTenantNotifications( + tenantid, locationid, customerid, moduleid, + pageno, pagesize, + ) + + return c.JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Successful", + "details": result, + }) +} + +type Notification struct { + Title string `json:"title"` + Body string `json:"body"` +} + +type FCMRequestBody struct { + Message struct { + Notification Notification `json:"notification"` + Token string `json:"token"` + } `json:"message"` +} + +type WebhookPayload struct { + ID string `json:"id"` + Event string `json:"event"` + Timestamp string `json:"timestamp"` + Vendor string `json:"vendor"` + FailedAttempts int `json:"failedAttempts"` + WebhookID string `json:"webhookId"` + Payload interface{} `json:"payload"` + Context interface{} `json:"context"` +} + +func WebhookHandler(c *fiber.Ctx) error { + utils.Info("➡️ Incoming webhook request") + + var webhook WebhookPayload + if err := c.BodyParser(&webhook); err != nil { + utils.Logger.Errorf("❌ Failed to parse webhook JSON: %v", err) + utils.Logger.Errorf("Request body: %s", string(c.Body())) + return c.Status(fiber.StatusBadRequest).SendString("Invalid JSON") + } + + utils.Logger.Infof("✅ Received event: %s at %s", webhook.Event, webhook.Timestamp) + utils.Logger.Infof("🧾 Payload: %+v", webhook.Payload) + + return c.SendStatus(fiber.StatusOK) +} + +func RegisterWebhookHandler(c *fiber.Ctx) error { + // Shopfront GraphQL API endpoint + url := "https://testshopfront.onshopfront.com/api/v2/graphql" + + // Webhook registration mutation + payload := map[string]string{ + "query": ` +mutation { + registerWebhook(input: { + name: "Sale Webhook", + url: "https://jupiter.nearle.app/live/api/v1/utils/webhooks", + events: [sale_created] + }) { + id + name + url + events + active + } +}`, + } + + // Marshal payload + body, _ := json.Marshal(payload) + + // Create POST request + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiI2OTA5MmM3ZmY0YzgzNmExNTBmZWIyOGU3N2RkOWYzMDFjNjM4ZGQ5ODViOGExZjMzYjdhODY1MzFhMzQxNjM0NTQ2ZjMxMTY0M2JjMGUzNSIsImlhdCI6MTc1MzExMTY5NC44OTAyNjksIm5iZiI6MTc1MzExMTY5NC44OTAyNzQsImV4cCI6MTc1NDMyMTI5NC44Nzg2OTYsInN1YiI6IiIsInNjb3BlcyI6WyJjcmVhdGVfd2ViaG9va3MiXX0.WtE2eYES3MWdcvQZIu0AubIzUAv839-c_UoSUdCx5ez2Px03u-YZbCX114LxAhcLIZbQBiyaFB24pW3ojQf_RIdIvDnWPoMo7RWDDVYiHT9xHbe6Crht4ORkaYF2IOijXCYgDRgXT3whBNVldFhFAyfhcj9qVehJdmpRh5QGmt0PT9LWoy7XeFR_ksmOPjufvv1JZLITYilT7SijrkpSvDsqUBlCXj86ifIFY9q3A2zexb7GlSvdE_RmRegPr0kLA23i4J4uwo9BBPDaJaIGYIP4C6x5j9fYBOmf0v3cSt1teoVGBFedjrizH4qvWazDZl44OlwlE6QQYCXpmjgFWXtLeiI54JQ-tygCbxi7xB_O4v8oqj_G4aP-aM-PykC5ge11ekJuCJXhsLcJajgztcjVVz8Bmnh0U5nnRG9Qx0szTiuy07p6y73IN4gWfLFjRd16pryhPjAHzI5bzbRGG_Pd0M5NcT7krZkQGEDkpuNgDKni6GgLQBy4My_mskJXjTw9lYP5i2Okn9CywJ5qaPNRMWW8eiEpb8qwzXxS2IRQQSKnpkT88P1LcSUMBhermYq60KFuh1RKYG5jNoWo2xwCsNJygCCk4iwtAvlhBoAZiOPwYJcgSM6jPdwqoUuRys_tqs938zIuf9hTdDosm5WM9z1sxXX-nqSfLqFuN_M") // Replace with valid token + + // ✅ Add debug logs + utils.Logger.Infof("Request body: %s", string(body)) + utils.Logger.Infof("Authorization header: %s", req.Header.Get("Authorization")) + + // Send the request + resp, err := http.DefaultClient.Do(req) + if err != nil { + utils.Logger.Errorf("Failed to register webhook: %v", err) + return c.Status(500).SendString("Webhook registration failed") + } + defer resp.Body.Close() + + respBody, _ := io.ReadAll(resp.Body) + utils.Logger.Infof("Webhook registration response: %s", respBody) + + return c.Status(resp.StatusCode).SendString(string(respBody)) +} + +type WebhookInput struct { + Topic string `json:"topic"` + DeliveryUrl string `json:"delivery_url"` +} + +func Createwebhook(c *fiber.Ctx) error { + code := c.Query("code") + vendor := c.Query("vendor") + + // ✅ Handle OAuth callback + if code != "" && vendor != "" { + utils.Logger.Infof("✅ Received OAuth Code: %s", code) + utils.Logger.Infof("✅ Vendor: %s", vendor) + + // 🔁 Exchange code for access token + tokenResp, err := http.PostForm("https://"+vendor+".onshopfront.com/oauth/token", url.Values{ + "client_id": []string{"kY3Dfvohj8BV4lEoUDKSEsyRGnlGpXi8"}, + "client_secret": []string{"Qk2eUXHaveIFEMNTny0yvVB1aHWvIY5J7blKwdHx"}, + "code": []string{code}, + "grant_type": []string{"client_credentials"}, + "redirect_uri": []string{"https://jupiter.nearle.app/live/api/v1/utils/createwebhook"}, + }) + + if err != nil { + utils.Logger.Errorf("❌ Token exchange error: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "message": "Failed to exchange code for access token", + }) + } + defer tokenResp.Body.Close() + + var tokenData map[string]interface{} + json.NewDecoder(tokenResp.Body).Decode(&tokenData) + utils.Logger.Infof("🔐 Token response: %v", tokenData) + + accessToken, ok := tokenData["access_token"].(string) + if !ok || accessToken == "" { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Access token missing in response", + }) + } + + // ✅ Send accessToken back or store it (in DB, session, etc.) + return c.JSON(fiber.Map{ + "status": true, + "message": "OAuth successful", + "access_token": accessToken, + }) + } + + // ✅ Handle webhook registration + var input WebhookInput + if err := c.BodyParser(&input); err != nil { + utils.Logger.Errorf("❌ BodyParser Error: %v", err) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "message": "Invalid request body", + }) + } + + if input.Topic == "" || input.DeliveryUrl == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "message": "Topic and Delivery URL are required", + }) + } + + payload := map[string]string{ + "topic": input.Topic, + "delivery_url": input.DeliveryUrl, + } + jsonPayload, _ := json.Marshal(payload) + + // ❗ Replace this with actual token from DB/session + accessToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiIwNmI3Y2M0YTA4YzY1NDhlMmYzNTU3Nzg0MDAyNDdmMjk0ZTliOTBkMzQ4MjNjODZkNjJhNTU4NjZhZWE5M2EwNTZlYjQ4ZjM2NTQ3MTE0YyIsImlhdCI6MTc1MzM0MjczNi42MDc2NTcsIm5iZiI6MTc1MzM0MjczNi42MDc2NjIsImV4cCI6MTc1NDU1MjMzNi41OTQ3MTksInN1YiI6IiIsInNjb3BlcyI6WyJjcmVhdGVfd2ViaG9va3MiXX0.jl4ln8Qv2apX4A_uxohS2Rqj6hShBQD1M2tGV-8fkUPrxg8anh-4aDEApFiaZuB8PkfereU5nPlMP_r5Hs5vWNTIgZyp6A-uwUp89c7NZyQng9aCfmRCN0yN0VeV4SWMKasp6uFacG-_MrzToQPtsYGGam7tG8j5apfcy3spXvJSC7CwwfRxL-Kpi1mJ-t272onWZblEhLrwiMbKc_6B5mVCV8TkCsY6YS8UDT5SC93X8_l2X3wjg2MP-fL9zUWz13qK4xHVDvqpx2mc0fG-ZI7oJpafC5j8GszVkobKqQTwMACuJSt5l6AgWcau4uPKglMIzUqapipSuu4jjKvQCQQAwLaL8Xc9Zy_vtMgGepdNEcuRn7uZqg1CMHCUKqvYWu-YU_4hWlzHVUu3A4SPcs5ZfxIj5D5smMffQb_2FQm4PThMlJKD4T697VLm0jKfDN0P8c3PINhiEpixJFh-njlCCNUXylO4SxG2Q8Yg_nBMLGuTevsSZaZLXd4_Zvsw3tZpoDOGWyZ0-fjBcbCvjmq_eY2S1emdwHYNhoFo8j0FJjKuEnwj0XylaQu6bt1PF8E_LQit9SK2umVMRAtb7ayocoz9iSi6NP3416aI7B97pUwqWKzy9or7LKtydAfzfuFCzlhuXf_iW3VhQR8QlFJtUuKIt7Nfu3gGAm6pk0" + + req, err := http.NewRequest("POST", "https://testshopfront.onshopfront.com/api/v1/webhooks", bytes.NewBuffer(jsonPayload)) + if err != nil { + utils.Logger.Errorf("❌ Failed to create request: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "message": "Failed to create webhook request", + }) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + utils.Logger.Errorf("❌ Error calling Shopfront API: %v", err) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "message": "Error sending request to Shopfront", + }) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + utils.Logger.Infof("🔁 Shopfront Response: %s", string(body)) + + return c.Status(resp.StatusCode).Send(body) +} + +func WebhookReceiver(c *fiber.Ctx) error { + var webhookData struct { + ID string `json:"id"` + Event string `json:"event"` + Timestamp string `json:"timestamp"` + Vendor string `json:"vendor"` + FailedAttempts int `json:"failedAttempts"` + WebhookID string `json:"webhookId"` + Payload map[string]interface{} `json:"payload"` + Context interface{} `json:"context"` + } + + if err := c.BodyParser(&webhookData); err != nil { + utils.Logger.Errorf("❌ Error parsing webhook payload: %v", err) + return c.SendStatus(fiber.StatusBadRequest) // 400 + } + + // Log webhook info for debugging + utils.Info("🔔 Webhook received:") + utils.Logger.Infof("ID: %s", webhookData.ID) + utils.Logger.Infof("Event: %s", webhookData.Event) + utils.Logger.Infof("Timestamp: %s", webhookData.Timestamp) + utils.Logger.Infof("Vendor: %s", webhookData.Vendor) + utils.Logger.Infof("Payload: %v", webhookData.Payload) + + // Return 200 OK with no body + return c.SendStatus(fiber.StatusOK) +} + +func GetShopfrontOrders(c *fiber.Ctx) error { + url := "https://testshopfront.onshopfront.com/graphql" + accessToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiI5NzMzZjk3ZjlmNjAxMWM4NDc4NDE3Y2JlN2MzM2VjZjM0YzJiNWUyNDMzNjBmNTliNTg3NzgzZWZkOGYxOGQ0NjQ3MjZmMjMzMzAwOTE2YSIsImlhdCI6MTc1MzQyOTE2Ni4zMzkxNiwibmJmIjoxNzUzNDI5MTY2LjMzOTE2NSwiZXhwIjoxNzU0NjM4NzY2LjMyMjUzOSwic3ViIjoiIiwic2NvcGVzIjpbIioiXX0.1ne-a29-wS_HUn3vVnuUA1ikXeXfLtz6AMktFfbj1Zc4L1ih_PJAHvzODbgErv9pR-6AucDyxH2ZlJSb7MAL-V93yDMGK672n4lxXNDcVqgZEGjCrn7WlyopLhZDhjKJMzRFn1CN4FkW2P31Jgl55W0Ff_prX13usLy_Cad8rNlnp15TaTgJed-m40hsOeWEjQDP_LJWQCHSf2wXNPx-tpRe1FEbh6QQNV4nT3TRqUQEhWvd_nQXiqnZaqY0q6Xbgq_Vn-laNNySIvtyISaoS0jxfrSF2856FBhF-WKIzMfQs1oeQioZL-2IH1QGKrbCJfV9Z4gTaGfVLV8ZAWp1XB3CmpDFgEJih_H76u1NvIXs8S343jq6klV1GKaqvuYfz8RfwtNx7mfP6mOwve3SkRSuHZOedzcgKjdETL8AWx7AT-6gbSdGX49ktqD0NbixzfBu2MGkBn6j7vMVgtNqfrYHcfZAP65gRlo9UGsKAvAFbAZnGoO-FHC3R9iSOM2llmLNg65OoaPazavOfnKhLWzk06gS3t_8HJenqTM_7tp6YsW-W57k5y5MzJYcHvXSplvG8VYUFEkRo6PeGq7cib_78IopAp3vgxODSaDa9ETkn2RHGLS8IyMK6pAP7OLpZyc6lxNgDqnZE3S2kAqdrOgvrld3NzP4hWpwA3dka8" + + query := `{ + "query": "query { orders(first: 10) { edges { node { id invoiceNumber orderDate status totalCost } } } }" + }` + + req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(query))) + if err != nil { + utils.Logger.Errorf("❌ Request creation failed: %v", err) + return c.Status(500).JSON(fiber.Map{"error": "Request creation failed"}) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + utils.Logger.Errorf("❌ Request failed: %v", err) + return c.Status(500).JSON(fiber.Map{"error": "Request execution failed"}) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + return c.Status(resp.StatusCode).Send(body) +} + +func RegisterInventoryWebhook(c *fiber.Ctx) error { + // Log or process the incoming payload + var payload map[string]interface{} + if err := c.BodyParser(&payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid payload", + }) + } + utils.Logger.Infof("📦 Inventory Webhook Received: %v", payload) + + // Always return 2xx status with no body + return c.SendStatus(fiber.StatusNoContent) // 204 +} + +func GetAppModule(c *fiber.Ctx) error { + + result := domain.GetAppModule() + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": result, + }) + +} + +func GetUserBonusSummary(c *fiber.Ctx) error { + + userid, err := strconv.Atoi(c.Query("userid")) + if err != nil || userid == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Valid userid is required", + }) + } + + data, err := domain.GetUserBonusSummary(userid) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": 500, + "message": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Success", + "details": data, + }) +} + +func CreateAppLocationConfig(c *fiber.Ctx) error { + + var data models.AppLocationConfigRequest + + if err := c.BodyParser(&data); err != nil { + return err + } + + result := domain.CreateAppLocationConfig(data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": result, + }) +} + +func UpdateAppLocationConfig(c *fiber.Ctx) error { + + var data models.AppLocationConfigRequest + + if err := c.BodyParser(&data); err != nil { + return err + } + + result := domain.UpdateAppLocationConfig(data) + + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": result, + }) +} + +func GetUserRoles(c *fiber.Ctx) error { + + roles, err := domain.GetUserRoles() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "success": false, + "message": "Failed to fetch roles", + "error": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "code": 200, + "message": "Success", + "details": roles, + }) +} + +// ============================================================================ +// REDIS USER STORAGE ENDPOINTS +// ============================================================================ + +// StoreUserRedis - POST /api/v1/utils/users/redis +// Store user data in Redis with ZSET index and HASH storage +func CreateUserRedis(c *fiber.Ctx) error { + ctx := context.Background() + + // Parse request body + var userData struct { + UserID int `json:"userid"` + Username string `json:"username"` + FirstName string `json:"firstname"` + LastName string `json:"lastname"` + ContactNo string `json:"contactno"` + UserFCMToken string `json:"userfcmtoken"` + AppVersion string `json:"app_version"` + DeviceInfo string `json:"device_info"` + } + + if err := c.BodyParser(&userData); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid request body", + }) + } + + // Validate required fields + if userData.UserID == 0 || userData.Username == "" || userData.FirstName == "" || userData.LastName == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "userid, username, firstname, and lastname are required", + }) + } + + // Check Redis connection + pong, err := db.Rdb.Ping(ctx).Result() + utils.Logger.Infof("[Redis] Ping: %s Error: %v", pong, err) + if err != nil { + utils.Logger.Errorf("❌ Redis connection error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Redis connection error: " + err.Error(), + }) + } + + // Marshal user data to JSON + jsonData, err := json.Marshal(userData) + if err != nil { + utils.Logger.Errorf("❌ JSON marshal error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to serialize user data", + }) + } + + // Store in HASH: "user:{userid}" + userKey := fmt.Sprintf("user:%d", userData.UserID) + if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil { + utils.Logger.Errorf("❌ Redis SET error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to store user in Redis", + }) + } + + utils.Logger.Infof("✅ User stored in HASH — key=%s", userKey) + + // Add to ZSET with timestamp as score: "user" → {score: timestamp, member: userid} + now := time.Now().Unix() + zadd := db.Rdb.ZAdd(ctx, "user", redis.Z{ + Score: float64(now), + Member: fmt.Sprintf("%d", userData.UserID), + }) + if zadd.Err() != nil { + utils.Logger.Errorf("❌ Redis ZADD error: %v", zadd.Err()) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to index user in ZSET", + }) + } + + utils.Logger.Infof("✅ User indexed in ZSET — key=user, member=%d, score=%d", userData.UserID, now) + + return c.Status(http.StatusCreated).JSON(fiber.Map{ + "status": true, + "code": http.StatusCreated, + "message": "User stored successfully in Redis", + "data": userData, + }) +} + +// GetUserRedis - GET /api/v1/utils/users/redis/:userid +// Retrieve user data from Redis by userid +func GetUserRedis(c *fiber.Ctx) error { + ctx := context.Background() + + userIDStr := c.Query("userid") + pageStr := c.Query("page") + pageSizeStr := c.Query("pagesize") + + // 🔹 CASE 1: Single user + if userIDStr != "" { + userID, err := strconv.Atoi(userIDStr) + if err != nil || userID == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Valid userid parameter is required", + }) + } + + userKey := fmt.Sprintf("user:%d", userID) + + userData, err := db.Rdb.Get(ctx, userKey).Result() + if err != nil { + return c.Status(http.StatusNotFound).JSON(fiber.Map{ + "status": false, + "code": http.StatusNotFound, + "message": "User not found", + }) + } + + var user map[string]interface{} + json.Unmarshal([]byte(userData), &user) + + return c.JSON(fiber.Map{ + "status": true, + "data": user, + }) + } + + // 🔹 CASE 2: ALL users with pagination + + var ( + page int + pageSize int + err error + ) + + if pageStr != "" { + page, err = strconv.Atoi(pageStr) + if err != nil || page <= 0 { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "invalid page", + }) + } + } + + if pageSizeStr != "" { + pageSize, err = strconv.Atoi(pageSizeStr) + if err != nil || pageSize <= 0 { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "invalid pagesize", + }) + } + } + + var start, end int64 + + if page > 0 && pageSize > 0 { + offset := (page - 1) * pageSize + start = int64(offset) + end = int64(offset + pageSize - 1) + } else { + start = 0 + end = -1 // fetch ALL + } + + // ✅ Get user IDs from ZSET + userIDs, err := db.Rdb.ZRevRange(ctx, "user", start, end).Result() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to fetch users", + }) + } + + // Convert to keys + var keys []string + for _, id := range userIDs { + keys = append(keys, fmt.Sprintf("user:%s", id)) + } + + // ✅ Use MGET (fast) + values, err := db.Rdb.MGet(ctx, keys...).Result() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to fetch user data", + }) + } + + var users []map[string]interface{} + + for _, val := range values { + if val == nil { + continue + } + + var user map[string]interface{} + json.Unmarshal([]byte(val.(string)), &user) + + users = append(users, user) + } + + // ✅ total count (optional) + // total, _ := db.Rdb.ZCard(ctx, "user").Result() + + return c.JSON(fiber.Map{ + + "code": http.StatusOK, + "data": users, + "message": "Success", + "status": true, + }) +} + +// UpdateUserRedis - PUT /api/v1/utils/users/redis/:userid +// Update user data in Redis +func UpdateUserRedis(c *fiber.Ctx) error { + ctx := context.Background() + + userIDStr := c.Params("userid") + userID, err := strconv.Atoi(userIDStr) + if err != nil || userID == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Valid userid parameter is required", + }) + } + + // Parse request body + var userData map[string]interface{} + if err := c.BodyParser(&userData); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Invalid request body", + }) + } + + // Check Redis connection + _, err = db.Rdb.Ping(ctx).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis connection error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Redis connection error", + }) + } + + // Check if user exists + userKey := fmt.Sprintf("user:%d", userID) + exists, err := db.Rdb.Exists(ctx, userKey).Result() + if err != nil || exists == 0 { + utils.Logger.Warnf("⚠️ User not found for update — key=%s", userKey) + return c.Status(http.StatusNotFound).JSON(fiber.Map{ + "status": false, + "code": http.StatusNotFound, + "message": "User not found", + }) + } + + // Add userid to update data + userData["userid"] = userID + + // Marshal updated data + jsonData, err := json.Marshal(userData) + if err != nil { + utils.Logger.Errorf("❌ JSON marshal error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to serialize user data", + }) + } + + // Update in HASH + if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil { + utils.Logger.Errorf("❌ Redis SET error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to update user in Redis", + }) + } + + utils.Logger.Infof("✅ User updated in HASH — key=%s", userKey) + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "User updated successfully", + "data": userData, + }) +} + +// DeleteUserRedis - DELETE /api/v1/utils/users/redis/:userid +// Delete user from both HASH and ZSET +func DeleteUserRedis(c *fiber.Ctx) error { + ctx := context.Background() + + userIDStr := c.Params("userid") + userID, err := strconv.Atoi(userIDStr) + if err != nil || userID == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Valid userid parameter is required", + }) + } + + // Check Redis connection + _, err = db.Rdb.Ping(ctx).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis connection error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Redis connection error", + }) + } + + userKey := fmt.Sprintf("user:%d", userID) + userIDStr = fmt.Sprintf("%d", userID) + + // Delete from HASH + delResult, err := db.Rdb.Del(ctx, userKey).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis DEL error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to delete user", + }) + } + + utils.Logger.Infof("✅ User deleted from HASH — key=%s, deleted=%d", userKey, delResult) + + // Remove from ZSET + remResult, err := db.Rdb.ZRem(ctx, "user", userIDStr).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis ZREM error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to remove user from index", + }) + } + + utils.Logger.Infof("✅ User removed from ZSET — key=user, member=%s, removed=%d", userIDStr, remResult) + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "User deleted successfully", + }) +} + +// GetAllUsersRedis - GET /api/v1/utils/users/redis +// Retrieve all users from ZSET +func GetAllUsersRedis(c *fiber.Ctx) error { + ctx := context.Background() + + // Check Redis connection + _, err := db.Rdb.Ping(ctx).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis connection error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Redis connection error", + }) + } + + // Get all user IDs from ZSET + userIDs, err := db.Rdb.ZRange(ctx, "user", 0, -1).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis ZRANGE error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to fetch users", + }) + } + + utils.Logger.Infof("✅ Retrieved %d user IDs from ZSET", len(userIDs)) + + // Fetch each user's data + var users []map[string]interface{} + for _, userIDStr := range userIDs { + userKey := fmt.Sprintf("user:%s", userIDStr) + userData, err := db.Rdb.Get(ctx, userKey).Result() + if err != nil { + utils.Logger.Warnf("⚠️ Failed to get user data — key=%s", userKey) + continue + } + + var user map[string]interface{} + if err := json.Unmarshal([]byte(userData), &user); err != nil { + utils.Logger.Warnf("⚠️ Failed to unmarshal user data — key=%s", userKey) + continue + } + + users = append(users, user) + } + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "count": len(users), + "data": users, + }) +} + +// UserExistsRedis - GET /api/v1/utils/users/redis/:userid/exists +// Check if user exists in Redis +func UserExistsRedis(c *fiber.Ctx) error { + ctx := context.Background() + + userIDStr := c.Params("userid") + userID, err := strconv.Atoi(userIDStr) + if err != nil || userID == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": http.StatusBadRequest, + "message": "Valid userid parameter is required", + }) + } + + // Check Redis connection + _, err = db.Rdb.Ping(ctx).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis connection error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Redis connection error", + }) + } + + userKey := fmt.Sprintf("user:%d", userID) + exists, err := db.Rdb.Exists(ctx, userKey).Result() + if err != nil { + utils.Logger.Errorf("❌ Redis EXISTS error: %v", err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to check user existence", + }) + } + + userExists := exists > 0 + utils.Logger.Infof("✅ User existence check — userid=%d, exists=%v", userID, userExists) + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "exists": userExists, + }) +} + +func CreateRiderPeriodicLog(c *fiber.Ctx) error { + ctx := context.Background() + + var log models.RiderLog + if err := c.BodyParser(&log); err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "Invalid request body", + }) + } + + // Parse logdate + t, err := time.Parse("2006-01-02 15:04:05", log.LogDate) + if err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "Invalid logdate format", + }) + } + + timestamp := t.Unix() + + // Key + logKey := fmt.Sprintf("rider_periodic_log:%d:%d", log.UserID, timestamp) + + data, _ := json.Marshal(log) + + // Store STRING + err = db.Rdb.Set(ctx, logKey, data, 0).Err() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to store log", + }) + } + + // ✅ Per-user ZSET + userZsetKey := fmt.Sprintf("rider_periodic_logs:%d", log.UserID) + err = db.Rdb.ZAdd(ctx, userZsetKey, redis.Z{ + Score: float64(timestamp), + Member: logKey, + }).Err() + + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to index user log", + }) + } + + // 🔥 ADD THIS (GLOBAL INDEX) + err = db.Rdb.ZAdd(ctx, "rider_periodic_logs_all", redis.Z{ + Score: float64(timestamp), + Member: logKey, + }).Err() + + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to index global log", + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "message": "Rider periodic log stored", + }) +} + +func GetRiderPeriodicLogs(c *fiber.Ctx) error { + ctx := context.Background() + + userID := c.Query("userid") + + var keys []string + var err error + + if userID != "" { + // ✅ USER specific (latest only) + zsetKey := fmt.Sprintf("rider_periodic_logs:%s", userID) + keys, err = db.Rdb.ZRevRange(ctx, zsetKey, 0, 0).Result() + } else { + // ✅ GLOBAL (latest only) + keys, err = db.Rdb.ZRevRange(ctx, "rider_periodic_logs_all", 0, 0).Result() + } + + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to fetch logs", + }) + } + + if len(keys) == 0 { + return c.JSON(fiber.Map{ + "code": 200, + "data": []interface{}{}, + "message": "No logs found", + "status": true, + }) + } + + // 🔥 Get only one value + val, err := db.Rdb.Get(ctx, keys[0]).Result() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to fetch log data", + }) + } + + var log map[string]interface{} + json.Unmarshal([]byte(val), &log) + + return c.JSON(fiber.Map{ + "code": 200, + "data": log, + "message": "Success", + "status": true, + }) +} + +func CreateRiderStatus(c *fiber.Ctx) error { + ctx := context.Background() + + var status models.RiderStatus + if err := c.BodyParser(&status); err != nil { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "Invalid request body", + }) + } + + if status.UserID == 0 || status.Status == "" { + return c.Status(400).JSON(fiber.Map{ + "status": false, + "message": "userid and status required", + }) + } + + key := fmt.Sprintf("rider_status:%d", status.UserID) + + data, _ := json.Marshal(status) + + // ✅ Store latest status + err := db.Rdb.Set(ctx, key, data, 0).Err() + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to store status", + }) + } + + // 🔥 ADD THIS (MISSING PART) + err = db.Rdb.ZAdd(ctx, "rider_status_all", redis.Z{ + Score: float64(time.Now().Unix()), + Member: key, + }).Err() + + if err != nil { + return c.Status(500).JSON(fiber.Map{ + "status": false, + "message": "Failed to index status", + }) + } + + return c.JSON(fiber.Map{ + "status": true, + "message": "Rider status updated", + }) +} + +func GetRiderStatus(c *fiber.Ctx) error { + ctx := context.Background() + + userIDStr := c.Query("userid") + pageStr := c.Query("page") + pageSizeStr := c.Query("pagesize") + + // 🔹 CASE 1: Single user + if userIDStr != "" { + key := fmt.Sprintf("rider_status:%s", userIDStr) + + val, err := db.Rdb.Get(ctx, key).Result() + if err != nil { + return c.Status(http.StatusNotFound).JSON(fiber.Map{ + "status": false, + "code": http.StatusNotFound, + "message": "Status not found", + "data": []interface{}{}, + }) + } + + var data map[string]interface{} + json.Unmarshal([]byte(val), &data) + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "data": data, + }) + } + + // 🔹 CASE 2: ALL users (with optional pagination) + + page, _ := strconv.Atoi(pageStr) + pageSize, _ := strconv.Atoi(pageSizeStr) + + var start, end int64 + + if page > 0 && pageSize > 0 { + offset := (page - 1) * pageSize + start = int64(offset) + end = int64(offset + pageSize - 1) + } else { + start = 0 + end = -1 // return ALL + } + + // 🔥 Fetch all status keys from global ZSET + keys, err := db.Rdb.ZRevRange(ctx, "rider_status_all", start, end).Result() + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to fetch statuses", + "data": []interface{}{}, + }) + } + + // 🔥 Fast fetch using MGET + values, err := db.Rdb.MGet(ctx, keys...).Result() + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "status": false, + "code": http.StatusInternalServerError, + "message": "Failed to fetch data", + "data": []interface{}{}, + }) + } + + var result []map[string]interface{} + + for _, val := range values { + if val == nil { + continue + } + + var item map[string]interface{} + json.Unmarshal([]byte(val.(string)), &item) + + result = append(result, item) + } + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "status": true, + "code": http.StatusOK, + "message": "Success", + "data": result, + }) +} diff --git a/db/connect copy.go b/db/connect copy.go new file mode 100644 index 0000000..1dc3a1f --- /dev/null +++ b/db/connect copy.go @@ -0,0 +1,185 @@ +package db + +// import ( +// "fmt" +// "log" +// "nearle/utils" +// "time" + +// "gorm.io/driver/mysql" +// "gorm.io/gorm" +// ) + +// var DB *gorm.DB + +// var ( +// DB_DEV *gorm.DB +// DB_LIVE *gorm.DB +// ) + +// func DevConnect() { + +// var DBname string +// var Username string +// var Password string +// var Host string +// var Port string + +// var err error + +// Port, DBname, Password, Username, Host, _, _ = utils.DevConfig() + +// dsn := Username + ":" + Password + "@tcp" + "(" + Host + ":" + Port + ")/" + DBname + "?" + "parseTime=true&loc=Local" + +// DB_DEV, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// if err != nil { +// log.Fatal("❌ Could not connect to Dev database:", err) +// } +// setupDB(DB_DEV) +// fmt.Println("✅ Connected to Dev Database") + +// // var DBname string +// // var Username string +// // var Password string +// // var Host string +// // var Port string + +// // Port, DBname, Password, Username, Host, _, _ = utils.DevConfig() + +// // dsn := Username + ":" + Password + "@tcp" + "(" + Host + ":" + Port + ")/" + DBname + "?" + "parseTime=true&loc=Local" + +// // for i := 0; i < 5; i++ { +// // db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// // if err == nil { +// // sqlDB, _ := db.DB() +// // sqlDB.SetMaxIdleConns(100) +// // sqlDB.SetMaxOpenConns(1000) +// // sqlDB.SetConnMaxLifetime(time.Hour) +// // err = sqlDB.Ping() +// // if err == nil { +// // log.Println("Successfully connected to the database.") +// // DB = db +// // return +// // } +// // } +// // log.Printf("Failed to connect to the database, retrying... (%d/5)\n", i+1) +// // time.Sleep(2 * time.Second) +// // } +// // // log.Fatal("Could not connect to the database:", err) + +// // // database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// // // if err != nil { +// // // panic("Could not connect to the database") +// // // } + +// } + +// func setupDB(database *gorm.DB) { +// sqlDB, err := database.DB() +// if err != nil { +// log.Fatal("❌ Failed to get DB from GORM:", err) +// } +// sqlDB.SetMaxIdleConns(10) +// sqlDB.SetMaxOpenConns(100) +// sqlDB.SetConnMaxLifetime(30 * time.Minute) +// } + +// // func LiveConnect() { + +// // var DBname string +// // var Username string +// // var Password string +// // var Host string +// // var Port string + +// // Port, DBname, Password, Username, Host, _, _ = utils.DevConfig() + +// // dsn := Username + ":" + Password + "@tcp" + "(" + Host + ":" + Port + ")/" + DBname + "?" + "parseTime=true&loc=Local" + +// // for i := 0; i < 5; i++ { +// // db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// // if err != nil { +// // log.Printf("Failed to connect to the database, retrying... (%d/5)\n", i+1) +// // log.Println("Error:", err) +// // time.Sleep(2 * time.Second) +// // continue +// // } + +// // sqlDB, err := db.DB() +// // if err != nil { +// // log.Println("Failed to get DB from GORM:", err) +// // time.Sleep(2 * time.Second) +// // continue +// // } + +// // sqlDB.SetMaxIdleConns(10) +// // sqlDB.SetMaxOpenConns(100) +// // sqlDB.SetConnMaxLifetime(time.Hour) + +// // if err = sqlDB.Ping(); err != nil { +// // log.Printf("Failed to ping database, retrying... (%d/5)\n", i+1) +// // log.Println("Error:", err) +// // time.Sleep(2 * time.Second) +// // continue +// // } + +// // log.Println("Successfully connected to the database.") +// // DB = db +// // return +// // } + +// // log.Fatal("Could not connect to the database after multiple attempts.") + +// // } + +// func LiveConnect() { + +// var DBname string +// var Username string +// var Password string +// var Host string +// var Port string + +// Port, DBname, Password, Username, Host, _, _ = utils.LiveConfig() + +// // dsn := "zqbuvyrhhf:Package%40%123#@tcp(165.232.178.78:3306)/dbname?parseTime=true" + +// dsn := Username + ":" + Password + "@tcp" + "(" + Host + ":" + Port + ")/" + DBname + "?" + "parseTime=true&loc=Local" + +// //database, err := sql.Open("mysql", dsn) + +// database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// if err != nil { +// panic("Could not connect to the database") +// } + +// sqlDB, err := database.DB() +// if err != nil { +// log.Println("Failed to get DB from GORM:", err) +// panic("Could not connect to the database") + +// } +// sqlDB.SetMaxIdleConns(100) +// sqlDB.SetMaxOpenConns(1000) +// sqlDB.SetConnMaxLifetime(time.Hour) + +// if err = sqlDB.Ping(); err != nil { +// log.Println("Error:", err) +// panic("Could not connect to the database") +// } + +// fmt.Println("Live Database Connected Successfully") +// DB = database + +// } + +// func CloseDB() { +// sqlDB, err := DB.DB() +// if err != nil { +// log.Println("Error retrieving sql.DB from GORM:", err) +// return +// } +// fmt.Println("Connection closed Successfully") +// sqlDB.Close() // This will close the connection pool + +// } diff --git a/db/connect.go b/db/connect.go new file mode 100644 index 0000000..2101a4e --- /dev/null +++ b/db/connect.go @@ -0,0 +1,221 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "nearle/utils" + + "os" + "time" + + "github.com/redis/go-redis/v9" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var ( + DB *gorm.DB +) + +// -------------------- +// DATABASE CONNECTION +// -------------------- + +func Connect() { + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata", + mustEnv("DB_HOST"), + mustEnv("DB_USER"), + mustEnv("DB_PASSWORD"), + mustEnv("DB_NAME"), + getEnv("DB_PORT", "5432"), + ) + + var err error + maxRetries := 10 + backoff := 2 * time.Second + + for i := 1; i <= maxRetries; i++ { + utils.Logger.Infow("Connecting to database", "attempt", i) + + DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Error), + }) + + if err == nil { + sqlDB, dbErr := DB.DB() + if dbErr == nil { + + // 🔥 Ping with timeout (important) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if pingErr := sqlDB.PingContext(ctx); pingErr == nil { + setupDB(DB) + + utils.Logger.Infow("✅ Database connected successfully") + + // Start background health check + startDBHealthCheck(sqlDB) + + return + } else { + err = pingErr + } + } else { + err = dbErr + } + } + + utils.Logger.Errorw("❌ DB connection failed", "attempt", i, "error", err) + time.Sleep(backoff) + backoff *= 2 + } + + utils.Logger.Fatalw("☢️ CRITICAL: DB connection failed after retries") +} + +// -------------------- +// CONNECTION POOLING +// -------------------- + +func setupDB(database *gorm.DB) { + sqlDB, err := database.DB() + if err != nil { + utils.Logger.Errorw("Failed to get sql.DB", "error", err) + return + } + + // 🔥 K8s SAFE CONFIG + sqlDB.SetMaxOpenConns(30) // total open connections + sqlDB.SetMaxIdleConns(5) // idle connections + sqlDB.SetConnMaxLifetime(5 * time.Minute) // avoid stale connections + sqlDB.SetConnMaxIdleTime(2 * time.Minute) + + stats := sqlDB.Stats() + utils.Logger.Infow("DB Pool configured", + "MaxOpen", stats.MaxOpenConnections, + ) +} + +// -------------------- +// DB HEALTH CHECK +// -------------------- + +func startDBHealthCheck(sqlDB *sql.DB) { + go func() { + for { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + + err := sqlDB.PingContext(ctx) + cancel() + + if err != nil { + utils.Logger.Errorw("❌ DB connection lost", "error", err) + } + + time.Sleep(30 * time.Second) + } + }() +} + +// -------------------- +// CLOSE DB +// -------------------- + +func CloseDB() { + if DB == nil { + return + } + + sqlDB, err := DB.DB() + if err != nil { + utils.Logger.Errorw("Error retrieving sql.DB", "error", err) + return + } + + utils.Logger.Infow("Closing DB connection") + sqlDB.Close() +} + +// -------------------- +// ENV HELPERS +// -------------------- + +func mustEnv(key string) string { + val := os.Getenv(key) + if val == "" { + utils.Logger.Warnw("Missing env variable", "key", key) + } + return val +} + +func getEnv(key, fallback string) string { + if val := os.Getenv(key); val != "" { + return val + } + return fallback +} + +// -------------------- +// REDIS CONNECTION +// -------------------- + +var Rdb *redis.Client +var Ctx = context.Background() + +func InitRedis() { + redisHost := getEnv("REDIS_HOST", "66.116.226.255") // ✅ FIXED IP + redisPort := getEnv("REDIS_PORT", "6379") + + addr := fmt.Sprintf("%s:%s", redisHost, redisPort) + + Rdb = redis.NewClient(&redis.Options{ + Addr: addr, + Username: "default", + Password: "Package@324969#", + DB: 0, + + // ✅ TIMEOUTS (VERY IMPORTANT) + DialTimeout: 10 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + + // ✅ POOL + PoolSize: 50, + MinIdleConns: 10, + + // ✅ RETRIES + MaxRetries: 3, + MinRetryBackoff: 500 * time.Millisecond, + MaxRetryBackoff: 2 * time.Second, + }) + + maxRetries := 5 + backoff := 1 * time.Second + + for i := 1; i <= maxRetries; i++ { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + + _, err := Rdb.Ping(ctx).Result() + cancel() + + if err == nil { + utils.Logger.Infow("✅ Redis connected successfully", "addr", addr) + return + } + + utils.Logger.Errorw("❌ Redis connection failed", + "attempt", i, + "addr", addr, + "error", err, + ) + + time.Sleep(backoff) + backoff *= 2 + } + + utils.Logger.Fatalw("☢️ CRITICAL: Redis connection failed after retries", "addr", addr) +} diff --git a/domain/customerDomain.go b/domain/customerDomain.go new file mode 100644 index 0000000..706a356 --- /dev/null +++ b/domain/customerDomain.go @@ -0,0 +1,668 @@ +package domain + +import ( + "nearle/db" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + "strings" +) + +func CreateCustomer(input models.Customers) int { + + var custloc models.Customerlocations + var tcust models.Tenantcustomers + + tx := db.DB.Begin() + + t1 := tx.Create(&input) + if t1.Error != nil { + + utils.Error("CreateCustomer t1 error", "error", t1.Error) + tx.Rollback() + } + + custloc.Customerid = input.Customerid + custloc.Address = input.Address + custloc.Suburb = input.Suburb + custloc.City = input.City + custloc.State = input.State + custloc.Postcode = input.Postcode + custloc.Latitude = input.Latitude + custloc.Longitude = input.Longitude + custloc.Doorno = input.Doorno + custloc.Landmark = input.Landmark + custloc.Primaryaddress = 1 + + t2 := tx.Table("customerlocations").Create(&custloc) + if t2.Error != nil { + + utils.Error("CreateCustomer t2 error", "error", t2.Error) + tx.Rollback() + } + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("CreateCustomer t3 error", "error", t3.Error) + tx.Rollback() + } + + tx.Commit() + + return input.Customerid +} + +func CreateCustomerv1(input models.Customers) (int, error) { + + var tcust models.Tenantcustomers + + tx := db.DB.Begin() + + t1 := tx.Create(&input) + if t1.Error != nil { + + utils.Error("CreateCustomerv1 t1 error", "error", t1.Error) + tx.Rollback() + return 0, t1.Error + } + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("CreateCustomerv1 t3 error", "error", t3.Error) + tx.Rollback() + return 0, t3.Error + } + + err := tx.Commit().Error + if err != nil { + + return 0, err + } + + return input.Customerid, nil +} + +func CreateCustomerv2(input models.Customers) (*models.CustomersId, error) { + + var custloc models.Customerlocations + var tcust models.Tenantcustomers + var cid models.CustomersId + + tx := db.DB.Begin() + + t1 := tx.Create(&input) + if t1.Error != nil { + + utils.Error("CreateCustomerv2 t1 error", "error", t1.Error) + tx.Rollback() + return nil, t1.Error + } + + custloc.Customerid = input.Customerid + custloc.Address = input.Address + custloc.Suburb = input.Suburb + custloc.City = input.City + custloc.State = input.State + custloc.Postcode = input.Postcode + custloc.Latitude = input.Latitude + custloc.Longitude = input.Longitude + custloc.Doorno = input.Doorno + custloc.Landmark = input.Landmark + custloc.Primaryaddress = 1 + + t2 := tx.Table("customerlocations").Create(&custloc) + if t2.Error != nil { + + utils.Error("CreateCustomerv2 t2 error", "error", t2.Error) + tx.Rollback() + return nil, t2.Error + } + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("CreateCustomerv2 t3 error", "error", t3.Error) + tx.Rollback() + return nil, t3.Error + } + + cid.Customerid = input.Customerid + cid.Locationid = custloc.Locationid + + err := tx.Commit().Error + if err != nil { + + return nil, err + } + + return &cid, nil +} + +func UpdateCustomerv2(input models.Customers) (int, error) { + + var custloc models.Customerlocations + var tcust models.Tenantcustomers + var data models.CustomersId + + tx := db.DB.Begin() + + t1 := tx.Where("customerid=?", input.Customerid).Updates(&input) + if t1.Error != nil { + + utils.Error("UpdateCustomerv2 t1 error", "error", t1.Error) + tx.Rollback() + return 0, t1.Error + } + + custloc.Customerid = input.Customerid + custloc.Address = input.Address + custloc.Suburb = input.Suburb + custloc.City = input.City + custloc.State = input.State + custloc.Postcode = input.Postcode + custloc.Latitude = input.Latitude + custloc.Longitude = input.Longitude + custloc.Doorno = input.Doorno + custloc.Landmark = input.Landmark + custloc.Primaryaddress = 1 + + custlocid, err := CheckLocation(input.Customerid) + if err != nil { + + tx.Rollback() + return 0, err + } + + if custlocid != 0 { + + utils.Logger.Infof("entering location update: %d", custlocid) + + custloc.Locationid = custlocid + + t2 := tx.Table("customerlocations").Where("locationid=?", custlocid).Updates(&custloc) + if t2.Error != nil { + + utils.Error("UpdateCustomerv2 t2 update error", "error", t2.Error) + tx.Rollback() + return 0, t2.Error + } + + } else { + + utils.Logger.Infof("entering location create for customer: %d", input.Customerid) + + t2 := tx.Table("customerlocations").Create(&custloc) + if t2.Error != nil { + + utils.Error("UpdateCustomerv2 t2 create error", "error", t2.Error) + tx.Rollback() + return 0, t2.Error + } + + } + + cid := CheckTenantCustomer(input.Customerid, input.Tenantid) + + if cid == 0 { + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("UpdateCustomerv2 t3 error", "error", t3.Error) + tx.Rollback() + return 0, t3.Error + } + + } + + data.Locationid = custloc.Locationid + + utils.Logger.Infof("customerid for the customer is %d", input.Customerid) + utils.Logger.Infof("The created Location for the customer is %d", data.Locationid) + utils.Logger.Infof("the Location for the customer is %d", custloc.Locationid) + + err = tx.Commit().Error + if err != nil { + + return 0, err + } + + return data.Locationid, nil +} + +func UpdateCustomerv1(input models.Customers) error { + + var tcust models.Tenantcustomers + + tx := db.DB.Begin() + + t1 := tx.Where("customerid=?", input.Customerid).Updates(&input) + if t1.Error != nil { + + utils.Error("UpdateCustomerv1 t1 error", "error", t1.Error) + tx.Rollback() + return t1.Error + } + + cid := CheckTenantCustomer(input.Customerid, input.Tenantid) + if cid == 0 { + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("UpdateCustomerv1 t3 error", "error", t3.Error) + tx.Rollback() + return t3.Error + } + + } + + err := tx.Commit().Error + if err != nil { + + return err + } + + return nil +} + +func CreateTenantCustomer(input models.Customers) int { + var tcust models.Tenantcustomers + + tcust.Customerid = input.Customerid + tcust.Tenantid = input.Tenantid + + tx := db.DB.Begin() + + t3 := tx.Table("tenantcustomers").Create(&tcust) + if t3.Error != nil { + + utils.Error("CreateTenantCustomer t3 error", "error", t3.Error) + tx.Rollback() + } + + tx.Commit() + + return input.Customerid + +} + +func UpdateCustomer(input models.Customers) models.Result { + + var custloc models.Customerlocations + + var result models.Result + + tx := db.DB.Begin() + + t1 := tx.Where("customerid=?", input.Customerid).Updates(&input) + if t1.Error != nil { + + utils.Error("UpdateCustomer t1 error", "error", t1.Error) + tx.Rollback() + } + + custloc.Customerid = input.Customerid + custloc.Address = input.Address + custloc.Suburb = input.Suburb + custloc.City = input.City + custloc.State = input.State + custloc.Postcode = input.Postcode + custloc.Latitude = input.Latitude + custloc.Longitude = input.Longitude + custloc.Doorno = input.Doorno + custloc.Landmark = input.Landmark + + t2 := tx.Table("customerlocations").Where("customerid=? and primaryaddress=?", input.Customerid, 1).Updates(&custloc) + if t2.Error != nil { + + utils.Error("UpdateCustomer t2 error", "error", t2.Error) + tx.Rollback() + } + + err := tx.Commit().Error + if err != nil { + result.Code = http.StatusConflict + result.Status = false + result.Message = "Update Customer Failed" + return result + } + + result.Code = http.StatusAccepted + result.Status = true + result.Message = "Customer customer successful" + + return result +} + +func GetCustomer(cid int, cno string) models.CustomerInfo { + + var data models.CustomerInfo + + var q1 string + if cid != 0 { + + // q1 = `select a.customerid,a.authmode,a.configid,a.deviceid,a.devicetype,a.customertoken,a.firstname,a.lastname, + // a.contactno,a.email,a.profileimage,a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode, + // a.latitude,a.longitude,a.applocationid,a.status from customers a where a.customerid= ` + strconv.Itoa(cid) + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, a.profileimage, a.dialcode, a.deviceid, a.devicetype, a.authmode, a.configid, a.customertoken, a.intro, a.gender, a.dob, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,c.locationid as tenantlocationid, c.tenantid, a.status,d.qrmode,d.latitude as selectedlatitude, d.longitude as selectedlongitude, d.radius, d.applocationid, e.allocationid + from customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + LEFT JOIN app_location d ON a.applocationid=d.applocationid + LEFT JOIN tenants e ON e.tenantid=c.tenantid + WHERE b.primaryaddress=1 and a.customerid= ` + strconv.Itoa(cid) + ` order by customerid desc` + + } else { + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email,a.profileimage, a.dialcode, a.deviceid, a.devicetype, a.authmode, a.configid, a.customertoken, a.intro,a.gender, a.dob, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,c.locationid as tenantlocationid, c.tenantid, a.status, d.qrmode, d.latitude as selectedlatitude, d.longitude as selectedlongitude,d.radius, d.applocationid, e.allocationid + from customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + LEFT JOIN app_location d ON a.applocationid=d.applocationid + LEFT JOIN tenants e ON e.tenantid=c.tenantid + WHERE b.primaryaddress=1 and a.contactno= '` + cno + `' order by customerid desc` + + } + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1).Find(&data) + + return data +} + +func GetTenantCustomers(tid, lid, aid, pageno, pagesize int, keyword string) []models.CustomerInfo { + + var data []models.CustomerInfo + offset := (pageno - 1) * pagesize + + var q1 string + var args []interface{} + + + if lid != 0 { + + q1 = ` + SELECT a.customerid,a.firstname,a.lastname,a.contactno, + a.email,b.locationid AS deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode,b.latitude,b.longitude,a.applocationid,c.tenantid, + c.locationid AS tenantlocationid,a.status,1 AS quantity,0.0 AS collectionamt + FROM customers a + LEFT JOIN customerlocations b ON a.customerid = b.customerid + INNER JOIN tenantcustomers c ON a.customerid = c.customerid + WHERE c.locationid = ? AND c.tenantid = ? + ` + + args = append(args, lid, tid) + + if aid != 0 { + q1 += ` AND a.applocationid = ?` + args = append(args, aid) + } + + if keyword != "" { + search := "%" + keyword + "%" + q1 += ` AND (a.firstname LIKE ? OR a.contactno LIKE ? OR b.suburb LIKE ?)` + args = append(args, search, search, search) + } + + q1 += ` ORDER BY a.customerid DESC LIMIT ? OFFSET ?` + args = append(args, pagesize, offset) + + } else { + + + q1 = ` + SELECT a.customerid, a.firstname, a.lastname, a.contactno, a.email, a.address, a.suburb, a.city, a.state, a.landmark, a.doorno, a.postcode, a.latitude, + a.longitude, a.applocationid, c.tenantid, c.locationid AS tenantlocationid, a.status, 1 AS quantity, 0.0 AS collectionamt + FROM customers a + INNER JOIN tenantcustomers c ON a.customerid = c.customerid WHERE c.tenantid = ? ` + + + args = append(args, tid) + + if aid != 0 { + q1 += ` AND a.applocationid = ?` + args = append(args, aid) + } + + if keyword != "" { + search := "%" + strings.ToLower(keyword) + "%" + q1 += ` AND (LOWER(a.firstname) LIKE ? OR LOWER(a.contactno) LIKE ? OR LOWER(a.suburb) LIKE ?)` + args = append(args, search, search, search) + } + + q1 += ` ORDER BY a.customerid DESC LIMIT ? OFFSET ?` + args = append(args, pagesize, offset) + } + + // 🔹 Execute query + db.DB.Raw(q1, args...).Scan(&data) + + return data +} + + + + +func GetCustomerbytenant(tid, lid int) []models.CustomerInfo { + + var data []models.CustomerInfo + + var q1 string + + if lid != 0 { + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,c.locationid as tenantlocationid,a.status + from customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + WHERE c.locationid > 0 and c.tenantid=? order by customerid desc` + + } else { + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,c.locationid as tenantlocationid,a.status + from customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + WHERE c.tenantid=? and c.locationid = 0 order by customerid desc` + + //and c.locationid = 0 + + } + + db.DB.Raw(q1, tid).Find(&data) + + return data +} + +func GetallCustomers(aid, pageno, pagesize int, keyword string) []models.CustomerInfo { + var data []models.CustomerInfo + offset := (pageno - 1) * pagesize + + var q1 string + + baseQuery := `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,a.status,b.primaryaddress + FROM customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + WHERE b.primaryaddress=1` + + // filter by applocationid + if aid != 0 { + baseQuery += " AND a.applocationid=" + strconv.Itoa(aid) + } + + // keyword search filter (case-insensitive) + if keyword != "" { + like := "'%" + strings.ToLower(keyword) + "%'" + baseQuery += " AND (LOWER(a.firstname) LIKE " + like + + " OR LOWER(a.lastname) LIKE " + like + + " OR LOWER(a.contactno) LIKE " + like + ")" + } + + // order + pagination + q1 = baseQuery + " ORDER BY a.customerid DESC LIMIT " + strconv.Itoa(pagesize) + " OFFSET " + strconv.Itoa(offset) + + db.DB.Raw(q1).Find(&data) + + return data +} + +func SearchCustomer(str string, tid int) []models.CustomerInfo { + + var data []models.CustomerInfo + + var q1 string + + if tid != 0 { + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode, + a.latitude,a.longitude,a.applocationid,c.locationid as tenantlocationid,a.status + from customers a + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + WHERE c.tenantid=` + strconv.Itoa(tid) + ` and lower(a.firstname) like '` + strings.ToLower(str) + `%' or contactno like '` + strings.ToLower(str) + `%'` + + } else { + + // q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + // b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + // b.latitude,b.longitude,a.applocationid,a.status,c.locationid as tenantlocationid + // from customers a + // INNER JOIN customerlocations b ON a.customerid=b.customerid + // INNER JOIN tenantcustomers c ON a.customerid=c.customerid + // WHERE lower(a.firstname) like '` + strings.ToLower(str) + `%' or contactno like '` + strings.ToLower(str) + `%'` + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode, + a.latitude,a.longitude,a.applocationid,c.locationid as tenantlocationid,a.status + from customers a + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + WHERE lower(a.firstname) like '` + strings.ToLower(str) + `%' or contactno like '` + strings.ToLower(str) + `%'` + + } + + + db.DB.Raw(q1).Find(&data) + + return data +} + +func GetCustomerbyApplocation(aid int) []models.CustomerInfo { + + var data []models.CustomerInfo + + var q1 string + if aid != 0 { + + q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email, + b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode, + b.latitude,b.longitude,a.applocationid,a.status + from customers a + INNER JOIN customerlocations b ON a.customerid=b.customerid + INNER JOIN tenantcustomers c ON a.customerid=c.customerid + WHERE b.primaryaddress=1 and a.applocationid=? order by customerid desc` + + } + + db.DB.Raw(q1, aid).Find(&data) + + return data +} + +func CheckCustomer(contactno string) int { + + var data models.Customers + + q1 := `select a.customerid,a.authmode,a.configid,a.deviceid,a.devicetype,a.customertoken,a.firstname,a.lastname, + a.contactno,a.email,a.profileimage,a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode, + a.latitude,a.longitude,a.status from customers a where a.contactno= '` + contactno + `'` + + db.DB.Raw(q1).Find(&data) + + return data.Customerid +} + +func CheckLocation(cid int) (int, error) { + + var data int + + q1 := `select a.locationid from customerlocations a where a.customerid= ` + strconv.Itoa(cid) + + db.DB.Raw(q1).Find(&data) + + return data, nil +} + +func CheckTenantCustomer(cid, tid int) int { + + var data models.Customers + + q1 := `select a.customerid from tenantcustomers a where a.customerid=` + strconv.Itoa(cid) + ` and a.tenantid=` + strconv.Itoa(tid) + + db.DB.Raw(q1).Find(&data) + + return data.Customerid +} + +func CreateCustomerLocation(input models.Customerlocations) int { + + err := db.DB.Create(&input).Error + + if err != nil { + + return 0 + + } + + return input.Customerid +} + +func GetCustomerLocation(cid int) models.CustomerLocationResult { + var result models.CustomerLocationResult + var data []models.Customerlocations + var q1 string + + if cid != 0 { + q1 = `SELECT a.locationid, a.customerid, a.address, a.suburb, a.city, a.state, a.landmark, a.doorno, a.postcode, + a.latitude, a.longitude, a.defaultaddress, a.primaryaddress, a.status + FROM customerlocations a WHERE a.customerid = ` + strconv.Itoa(cid) + } + + db.DB.Raw(q1).Find(&data) + + result.Code = http.StatusOK + result.Status = true + result.Message = "Successful" + result.Details = data + return result +} diff --git a/domain/delivery.go b/domain/delivery.go new file mode 100644 index 0000000..64f4368 --- /dev/null +++ b/domain/delivery.go @@ -0,0 +1,1295 @@ +package domain + +import ( + "context" + "encoding/json" + "errors" + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" + "strings" + "time" + + "github.com/jinzhu/copier" + "github.com/redis/go-redis/v9" +) + +const ( + deliveries = `SELECT distinct a.deliveryid,a.applocationid,f.locationname AS applocation,a.orderheaderid,a.configid,a.tenantid,a.partnerid,a.locationid,a.userid,a.categoryid,a.subcategoryid,a.moduleid, + a.orderid,a.deliverydate,a.orderstatus,a.assigntime,a.starttime,a.arrivaltime,a.pickuptime,a.deliverytime,a.canceltime,a.acceptedtime, + a.customerid,a.pickupcustomer,a.pickupcontactno,a.pickuplocationid,a.pickupaddress,a.pickuplocation,a.pickuplat,a.pickuplon, + a.deliverycustomerid,a.deliverycustomer,a.deliverycontactno,a.deliverylocationid,a.deliveryaddress,a.deliverylocation, + a.droplat,a.droplon,a.deliverylat,a.deliverylong, + a.riderslat,a.riderslon,a.deliveryamt,a.kms,a.actualkms,a.riderkms,a.deliverycharges,a.deliverytype,a.paymenttype,a.smsdelivery,a.quantity,a.collectionamt,a.collectedamt,a.collectionstatus, + a.notes,a.ordernotes,a.step,a.eta,a.previouskms,a.cumulativekms,a.ridertime,b.tenantname,b.primarycontact as tenantcontactno,b.tenanttoken,b.suburb as tenantsuburb,b.city as tenantcity, + b.address AS tenantaddress, CONCAT(c.firstname, ' ', c.lastname) AS ridername,c.userfcmtoken,c.contactno as ridercontact,e.locationname,e.suburb AS locationsuburb,e.contactno AS locationcontactno,e.address AS locationaddress, + h.slab, h.pricingdate, h.baseprice, h.minkm, h.priceperkm, h.maxkm, h.orders, h.othercharges, h.surgecharges, + a.expecteddeliverytime, a.profit, a.transitminutes, a.calculationdistancekm + FROM deliveries a + INNER JOIN tenants b ON a.tenantid=b.tenantid + INNER JOIN app_users c ON a.userid=c.userid + INNER JOIN tenantlocations e ON a.locationid=e.locationid + INNER JOIN app_location f ON a.applocationid = f.applocationid + INNER JOIN app_locationconfig g ON f.applocationid = g.applocationid + LEFT JOIN tenantpricing h ON a.tenantid = h.tenantid` +) + +func PublishLog(input []models.Deliverylogs) error { + + logInfo("PublishLog", "records", len(input)) + + tx := db.DB.Begin() + + for i := range input { + + t1 := tx.Create(&input[i]) + if t1.Error != nil { + logError("PublishLog failed", "error", tx.Error) + tx.Rollback() + return t1.Error + } + + } + + err := tx.Commit().Error + if err != nil { + + return err + } + + return nil + +} + +func CreateDeliveries(data []models.Deliveries) error { + logInfo("CreateDeliveries", "records", len(data)) + + var que []models.Deliveryqueues + + var ord models.Updateorderstatus + + tx := db.DB.Begin() + + for i := range data { + + t1 := tx.Create(&data[i]) + if t1.Error != nil { + logError("CreateDeliveries failed", "error", tx.Error) + tx.Rollback() + return t1.Error + } + + if err := copier.Copy(&que, &data[i]); err != nil { + return err + } + + t2 := tx.Create(&que) + if t2.Error != nil { + logError("CreateDeliveries queue failed", "error", tx.Error) + tx.Rollback() + return t2.Error + } + ord.Orderstatus = data[i].Orderstatus + ord.Pending = data[i].Deliverydate + + t3 := tx.Table("orders").Where("orderheaderid=?", data[i].Orderheaderid).Updates(&ord) + if t3.Error != nil { + + logError("CreateDeliveries order update failed", "error", tx.Error) + tx.Rollback() + return t3.Error + + } + + } + + err := tx.Commit().Error + if err != nil { + + return err + } + + return nil + +} + +func CreateDeliveriesV2(data []models.Deliveries) ([]int64, error) { + + ctx := db.Ctx + var deliveryIDs []int64 + + logInfo("🔥 CreateDeliveriesV2 DOMAIN CALLED") + + for _, d := range data { + + // 🔒 Rider guard key + riderKey := "rider:" + strconv.Itoa(d.Userid) + ":active_delivery" + + // ⏱ delivery TTL (example: 2 hours) + ttl := 2 * time.Hour + + // 🔒 PREVENT DOUBLE ASSIGN (CREATE ONLY IF NOT EXISTS) + ok, err := db.Rdb.SetNX(ctx, riderKey, "LOCK", ttl).Result() + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("rider " + strconv.Itoa(d.Userid) + " already has active delivery") + } + + // 🔥 Generate delivery ID + deliveryID, err := db.Rdb.Incr(ctx, "counter:deliveryid").Result() + if err != nil { + db.Rdb.Del(ctx, riderKey) + return nil, err + } + + deliveryIDs = append(deliveryIDs, deliveryID) + + // 🔑 Redis keys + deliveryKey := "delivery:active:" + strconv.FormatInt(deliveryID, 10) + cityKey := "city:" + strconv.Itoa(d.Tenantid) + ":active_deliveries" + + pipe := db.Rdb.TxPipeline() + + // 🔥 ACTIVE DELIVERY HASH (AUTO-CREATES) + pipe.HSet(ctx, deliveryKey, map[string]interface{}{ + "delivery_id": deliveryID, + "orderheaderid": d.Orderheaderid, + "orderid": d.Orderid, + "tenantid": d.Tenantid, // city + "partnerid": d.Partnerid, + "rider_id": d.Userid, + "status": "ASSIGNED", + "pickupaddress": d.Pickupaddress, + "deliveryaddress": d.Deliveryaddress, + "pickuplat": d.Pickuplat, + "pickuplon": d.Pickuplon, + "deliverylat": d.Deliverylat, + "deliverylong": d.Deliverylong, + "eta": d.Eta, + "expecteddeliverytime": d.Expecteddeliverytime, + "profit": d.Profit, + "transitminutes": d.Transitminutes, + "calculationdistancekm": d.Calculationdistancekm, + "created_at": time.Now().Unix(), + "last_updated": time.Now().Unix(), + }) + + // 🔥 CITY → ACTIVE DELIVERIES INDEX (AUTO-CREATES) + pipe.SAdd(ctx, cityKey, deliveryID) + + // 🔥 SET RIDER VALUE (REPLACE LOCK) + pipe.Set(ctx, riderKey, deliveryID, ttl) + + // 🔥 TTL ONLY ON DELIVERY & RIDER (NEVER CITY) + pipe.Expire(ctx, deliveryKey, ttl) + + cmds, err := pipe.Exec(ctx) + if err != nil { + db.Rdb.Del(ctx, riderKey) + return nil, err + } + + // 🔍 Per-command safety check + for _, cmd := range cmds { + if cmd.Err() != nil { + db.Rdb.Del(ctx, riderKey) + return nil, cmd.Err() + } + } + + logInfo("✅ Delivery created", "deliveryID", deliveryID) + } + + logInfo("✅ CreateDeliveriesV2 COMPLETED") + return deliveryIDs, nil +} + +func CreateDelivery(data models.Deliveries) error { + logInfo("CreateDelivery", "orderheaderid", data.Orderheaderid) + + var que models.Deliveryqueues + var ord models.Updateorderstatus + + tx := db.DB.Begin() + + t1 := tx.Table("deliveries").Create(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + if err := copier.Copy(&que, &data); err != nil { + + return err + } + + t2 := tx.Table("deliveryqueues").Create(&que) + if t2.Error != nil { + + logError("CreateDelivery queue failed", "error", tx.Error) + tx.Rollback() + return t2.Error + } + + ord.Orderstatus = data.Orderstatus + ord.Pending = data.Assigntime + + t3 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord) + if t3.Error != nil { + + logError("CreateDelivery order update failed", "error", tx.Error) + tx.Rollback() + return t3.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func UpdateDelivery(data models.UpdateDeliveryStatus) error { + logInfo("UpdateDelivery", "deliveryid", data.Deliveryid, "status", data.Orderstatus) + + var ord models.Updateorderstatus + var cloc models.Customerlocations + + tx := db.DB.Begin() + + t1 := tx.Table("deliveries").Where("deliveryid=?", data.Deliveryid).Updates(&data) + if t1.Error != nil { + + logError("UpdateDelivery failed", "error", tx.Error) + tx.Rollback() + } + + if data.Orderstatus == "pending" { + + ord.Orderstatus = data.Orderstatus + ord.Pending = data.Assigntime + + t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord) + if t2.Error != nil { + + logError("UpdateDelivery pending update failed", "error", tx.Error) + tx.Rollback() + } + + } + + if data.Orderstatus == "picked" { + + if data.Pickuplocationid != 0 && data.Deliverytype != "B" { + + cloc.Latitude = data.Riderslat + cloc.Longitude = data.Riderslon + cloc.Address = data.Address + cloc.Suburb = data.Suburb + cloc.City = data.City + cloc.State = data.State + cloc.Postcode = data.Postcode + + t5 := tx.Table("customerlocations").Where("locationid=?", data.Pickuplocationid).Updates(&cloc) + if t5.Error != nil { + + logError("UpdateDelivery customer location update failed", "error", tx.Error) + tx.Rollback() + } + + } + + } + + if data.Orderstatus == "delivered" { + + ord.Orderstatus = data.Orderstatus + ord.Delivered = data.Deliverytime + + t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord) + if t2.Error != nil { + + logError("UpdateDelivery delivered update failed", "error", tx.Error) + tx.Rollback() + } + + if data.Deliverylocationid != 0 && data.Deliverytype != "B" { + + cloc.Latitude = data.Deliverylat + cloc.Longitude = data.Deliverylong + cloc.Address = data.Address + cloc.Suburb = data.Suburb + cloc.City = data.City + cloc.State = data.State + cloc.Postcode = data.Postcode + + t5 := tx.Table("customerlocations").Where("locationid=?", data.Deliverylocationid).Updates(&cloc) + if t5.Error != nil { + + logError("UpdateDelivery delivery location update failed", "error", tx.Error) + tx.Rollback() + } + + } + + } + + if data.Orderstatus == "cancelled" { + + ord.Orderstatus = data.Orderstatus + ord.Cancelled = data.Canceltime + + t2 := tx.Table("orders").Where("orderheaderid=?", data.Orderheaderid).Updates(&ord) + if t2.Error != nil { + + logError("UpdateDelivery cancelled update failed", "error", tx.Error) + tx.Rollback() + } + + } + + err := tx.Commit().Error + + if err != nil { + + return err + + } + + return err + +} + +func GetDeliveryQueues(uid int, fdate, tdate string) []models.Deliveryinfo { + logInfo("GetDeliveryQueues", "userid", uid) + + var data []models.Deliveryinfo + + var q1 string + + if fdate != "" && tdate != "" { + + q1 = deliveries + " where a.orderstatus='pending' and a.userid= ? and a.deliverydate::date between ? and ? order by a.deliveryid asc" + db.DB.Raw(q1, uid, fdate, tdate).Find(&data) + + } else { + + q1 = deliveries + " where a.orderstatus='pending' and a.userid= ? and a.deliverydate::date = CURRENT_DATE order by a.deliveryid asc" + db.DB.Raw(q1, uid).Find(&data) + } + + logInfo("GetDeliveryQueues Query executed", "userid", uid) + + return data + +} + +func GetDeliveryQueuesV1(uid int, fdate, tdate string) []models.Deliveryinfo { + logInfo("GetDeliveryQueuesV1", "userid", uid) + var data []models.Deliveryinfo + var q1 string + + statusFilter := `a.orderstatus IN ('pending', 'accepted', 'arrived')` + + if fdate != "" && tdate != "" { + q1 = deliveries + " WHERE " + statusFilter + " AND a.userid = ? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid ASC" + db.DB.Raw(q1, uid, fdate, tdate).Find(&data) + } else { + q1 = deliveries + " WHERE " + statusFilter + " AND a.userid = ? AND a.deliverydate::date = CURRENT_DATE ORDER BY a.deliveryid ASC" + db.DB.Raw(q1, uid).Find(&data) + } + + return data +} + +func GetDeliveryQueuesPicked(uid int, fdate, tdate string) []models.Deliveryinfo { + logInfo("GetDeliveryQueuesPicked", "userid", uid) + + var data []models.Deliveryinfo + + var q1 string + + if fdate != "" && tdate != "" { + + q1 = deliveries + " where a.orderstatus='picked' and a.userid=? and a.deliverydate::date between ? and ? order by a.deliveryid asc" + db.DB.Raw(q1, uid, fdate, tdate).Find(&data) + + } else { + + q1 = deliveries + " where a.orderstatus='picked' and a.userid=? and a.deliverydate::date = CURRENT_DATE order by a.deliveryid asc" + db.DB.Raw(q1, uid).Find(&data) + } + + return data + +} + +func GetDeliverylogs(did int) []models.Deliverylogs { + logInfo("GetDeliverylogs", "deliveryid", did) + + var data []models.Deliverylogs + + q1 := "select * from deliverylogs where deliveryid=? order by logid asc" + db.DB.Raw(q1, did).Find(&data) + + return data + +} + +func GetTenantDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + logInfo("🔍 Fetching Tenant Deliveries", "tenantid", input.Tenantid) + + var data []models.Deliveryinfo + var queryBuilder strings.Builder + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + baseQuery := ` + SELECT a.*, b.tenantname, + CONCAT(c.firstname, ' ', c.lastname) AS ridername, + c.contactno AS ridercontact, c.userfcmtoken, + d.locationname, d.contactno AS locationcontactno, + d.suburb AS locationsuburb, d.address AS locationaddress, + e.slab, e.pricingdate, e.baseprice, e.minkm, + e.priceperkm, e.maxkm, e.orders, + e.othercharges, e.surgecharges + FROM deliveries a + JOIN tenants b ON a.tenantid = b.tenantid + JOIN app_users c ON a.userid = c.userid + JOIN tenantlocations d ON a.locationid = d.locationid + JOIN tenantpricing e ON a.tenantid = e.tenantid + WHERE b.moduleid = 6 + AND a.tenantid = ? + ` + queryBuilder.WriteString(baseQuery) + params = append(params, input.Tenantid) + + // ✅ USERID FILTER (FIX) + if input.UserID > 0 { + queryBuilder.WriteString(" AND a.userid = ?") + params = append(params, input.UserID) + } + + // ✅ STATUS FILTER (ignore "all") + if input.Status != "" && strings.ToLower(input.Status) != "all" { + queryBuilder.WriteString(" AND LOWER(a.orderstatus) = LOWER(?)") + params = append(params, input.Status) + } + + // ✅ DATE FILTER + if input.Fromdate != "" && input.ToDate != "" { + queryBuilder.WriteString(" AND a.deliverydate::date BETWEEN ? AND ?") + params = append(params, input.Fromdate, input.ToDate) + } + + // ✅ KEYWORD FILTER + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + queryBuilder.WriteString(` + AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(CAST(a.orderid AS CHAR)) LIKE ? OR + LOWER(c.firstname) LIKE ? OR + LOWER(c.lastname) LIKE ? OR + LOWER(c.contactno) LIKE ? + ) + `) + + for i := 0; i < 8; i++ { + params = append(params, k) + } + } + + queryBuilder.WriteString(" ORDER BY a.deliveryid DESC") + + if input.Pagesize > 0 { + queryBuilder.WriteString(" LIMIT ? OFFSET ?") + params = append(params, input.Pagesize, offset) + } + + query := queryBuilder.String() + // print("Final Query:", query) + db.DB.Raw(query, params...).Scan(&data) + return data +} + +func GetPartnerDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + + logInfo("Partner Deliveries", "partnerid", input.Partnerid) + + var data []models.Deliveryinfo + var qb strings.Builder + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + // 🔹 Base query + qb.WriteString(deliveries) + qb.WriteString(` WHERE b.moduleid = 6 AND a.partnerid = ?`) + params = append(params, input.Partnerid) + + // 🔹 STATUS FILTER (ignore "all") + if input.Status != "" && strings.ToLower(input.Status) != "all" { + qb.WriteString(` AND LOWER(a.orderstatus) = LOWER(?)`) + params = append(params, input.Status) + } + + // 🔹 DATE FILTER + if input.Fromdate != "" && input.ToDate != "" { + qb.WriteString(` AND a.deliverydate::date BETWEEN ? AND ?`) + params = append(params, input.Fromdate, input.ToDate) + } + + // 🔹 ORDER + qb.WriteString(` ORDER BY a.deliveryid DESC`) + + // 🔹 PAGINATION + if input.Pagesize > 0 { + qb.WriteString(` LIMIT ? OFFSET ?`) + params = append(params, input.Pagesize, offset) + } + + finalQuery := qb.String() + logInfo("Final Query", "query", finalQuery) + logInfo("Params", "params", params) + + db.DB.Raw(finalQuery, params...).Scan(&data) + return data +} + +func GetCustomerDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + + logInfo("Customer Deliveries", "partnerid", input.Partnerid) + + var q1 string + offset := (input.Pageno - 1) * input.Pagesize + var data []models.Deliveryinfo + + // ✅ Apply status filter ONLY if status != "" AND status != "all" + if input.Status != "" && strings.ToLower(input.Status) != "all" { + + if input.Fromdate != "" && input.ToDate != "" { + + if input.Pagesize != 0 { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Partnerid, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc" + db.DB.Raw(q1, input.Partnerid, input.Status, input.Fromdate, input.ToDate).Find(&data) + } + + } else { + + if input.Pagesize != 0 { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? order by deliveryid desc LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Partnerid, input.Status, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.orderstatus=? order by deliveryid desc" + db.DB.Raw(q1, input.Partnerid, input.Status).Find(&data) + } + } + + } else { + // ✅ status = "" OR status = "all" → NO STATUS FILTER + + if input.Pagesize != 0 { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Partnerid, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " where b.moduleid=6 and a.partnerid=? and a.deliverydate::date between ? and ? order by deliveryid desc" + db.DB.Raw(q1, input.Partnerid, input.Fromdate, input.ToDate).Find(&data) + } + } + return data +} + +func GetAdminDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + logInfo("Admin Deliveries", "applocationid", input.Applocationid) + + var data []models.Deliveryinfo + var qb strings.Builder + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + qb.WriteString(` + SELECT a.*, + b.tenantname,b.primarycontact as tenantcontactno,b.tenanttoken,b.suburb as tenantsuburb,b.city as tenantcity, b.address AS tenantaddress, + CONCAT(c.firstname, ' ', c.lastname) AS ridername,c.userfcmtoken,c.contactno as ridercontact, + d.locationname,d.suburb AS locationsuburb,d.contactno AS locationcontactno,d.address AS locationaddress, + e.locationname AS applocation, f.slab, f.pricingdate, f.baseprice, f.minkm, f.priceperkm, f.maxkm, f.orders, f.othercharges, f.surgecharges + FROM deliveries a + JOIN tenants b ON a.tenantid = b.tenantid + JOIN app_users c ON a.userid = c.userid + JOIN tenantlocations d ON a.locationid = d.locationid + JOIN app_location e ON a.applocationid = e.applocationid + JOIN tenantpricing f ON a.tenantid = f.tenantid + WHERE b.moduleid = 6 + AND a.applocationid = ? + `) + params = append(params, input.Applocationid) + + // ✅ TENANT FILTER + if input.Tenantid != 0 { + qb.WriteString(" AND a.tenantid = ?") + params = append(params, input.Tenantid) + } + + // ✅ LOCATION FILTER + if input.Locationid != 0 { + qb.WriteString(" AND a.locationid = ?") + params = append(params, input.Locationid) + } + + // ✅ USER / RIDER FILTER + if input.UserID != 0 { + qb.WriteString(" AND a.userid = ?") + params = append(params, input.UserID) + } + + // ✅ STATUS FILTER + if input.Status != "" && strings.ToLower(input.Status) != "all" { + qb.WriteString(" AND LOWER(a.orderstatus) = LOWER(?)") + params = append(params, input.Status) + } + + // ✅ DATE RANGE FILTER + if input.Fromdate != "" && input.ToDate != "" { + qb.WriteString(` + AND a.deliverydate >= (CONCAT(?::text, ' 00:00:00'))::timestamp + AND a.deliverydate <= (CONCAT(?::text, ' 23:59:59'))::timestamp + `) + params = append(params, input.Fromdate, input.ToDate) + } + + qb.WriteString(" ORDER BY a.deliveryid DESC") + + // ✅ PAGINATION + if input.Pagesize > 0 { + qb.WriteString(" LIMIT ? OFFSET ?") + params = append(params, input.Pagesize, offset) + } + + finalQuery := qb.String() + + db.DB.Raw(finalQuery, params...).Scan(&data) + return data +} + +func GetUserDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + + var q1 string + logInfo("User Deliveries", "userid", input.UserID) + + offset := (input.Pageno - 1) * input.Pagesize + var data []models.Deliveryinfo + var params []interface{} + + q1 = deliveries + " where b.moduleid=6 and a.userid=?" + params = append(params, input.UserID) + + // CASE 1: status = all → show all statuses + if input.Status == "all" { + + q1 += " and a.deliverydate::date between ? and ? order by deliveryid desc" + params = append(params, input.Fromdate, input.ToDate) + + // CASE 2: Specific status (picked, active, delivered…) + } else if input.Status != "" { + + q1 += " and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc" + params = append(params, input.Status, input.Fromdate, input.ToDate) + + // CASE 3: No status passed → default statuses + } else { + + q1 += " and a.orderstatus in ('picked','active','skipped') and a.deliverydate::date between ? and ? order by deliveryid desc" + params = append(params, input.Fromdate, input.ToDate) + } + + if input.Pagesize != 0 { + q1 += " LIMIT ? OFFSET ?" + params = append(params, input.Pagesize, offset) + } + + db.DB.Raw(q1, params...).Find(&data) + + return data +} + +func GetUserDeliveriesv1(input models.DeliveryQuery) []models.Deliveryinfo { + + var q1 string + + logInfo("User Deliveriesv1", "userid", input.UserID) + + offset := (input.Pageno - 1) * input.Pagesize + + var data []models.Deliveryinfo + + if input.Status != "" { + + if input.Pagesize != 0 { + + logInfo("Rider Deliveriesv2") + + q1 = deliveries + " where a.userid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.UserID, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + logInfo("Rider Deliveriesv1") + + q1 = deliveries + " where a.userid=? and a.orderstatus=? and a.deliverydate::date between ? and ? order by deliveryid asc" + db.DB.Raw(q1, input.UserID, input.Status, input.Fromdate, input.ToDate).Find(&data) + + } + + } else { + + if input.Pagesize != 0 { + logInfo("Rider Deliveriesv2") + + q1 = deliveries + " where a.orderstatus in ('picked') and a.userid=? and a.deliverydate::date between ? and ? order by deliveryid desc LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.UserID, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + logInfo("Rider Deliveriesv1") + + q1 = deliveries + " where a.orderstatus in ('picked') and a.userid=? and a.deliverydate::date between ? and ? order by deliveryid desc" + db.DB.Raw(q1, input.UserID, input.Fromdate, input.ToDate).Find(&data) + + } + + } + + return data + +} + +func GetAppUserDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + var data []models.Deliveryinfo + + logInfo("App User Deliveries", "appuserid", input.UserID) + + var ( + q1 strings.Builder + params []interface{} + ) + + offset := (input.Pageno - 1) * input.Pagesize + + // 🔹 Base query + q1.WriteString(deliveries) + q1.WriteString(" WHERE b.moduleid = 6 AND g.status = 'Active' ") + + // 🔹 Apply userid filter ONLY if userid > 0 + if input.UserID > 0 { + q1.WriteString(" AND g.userid = ? ") + params = append(params, input.UserID) + } + + // 🔹 Status filter (IGNORE if status = all) + if input.Status != "" && strings.ToLower(input.Status) != "all" { + q1.WriteString(" AND a.orderstatus = ? ") + params = append(params, input.Status) + } + + // 🔹 Date filter + if input.Fromdate != "" && input.ToDate != "" { + q1.WriteString(" AND a.deliverydate::date BETWEEN ? AND ? ") + params = append(params, input.Fromdate, input.ToDate) + } + + // 🔹 Keyword filter (case-insensitive) + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + q1.WriteString(` + AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(b.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(CAST(a.orderid AS CHAR)) LIKE ? OR + LOWER(CAST(a.deliveryid AS CHAR)) LIKE ? OR + LOWER(c.firstname) LIKE ? OR + LOWER(c.contactno) LIKE ? + ) + `) + + for i := 0; i < 9; i++ { + params = append(params, k) + } + } + + q1.WriteString(" ORDER BY a.deliveryid DESC ") + + if input.Pagesize > 0 { + q1.WriteString(" LIMIT ? OFFSET ? ") + params = append(params, input.Pagesize, offset) + } + + db.DB.Raw(q1.String(), params...).Scan(&data) + + return data +} + +func GetDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + var data []models.Deliveryinfo + var queryBuilder strings.Builder + var params []interface{} + + logInfo("Get All Deliveries -new") + + offset := (input.Pageno - 1) * input.Pagesize + + baseQuery := ` + SELECT a.*, b.tenantname, c.userid, + CONCAT(c.firstname, ' ', c.lastname) AS ridername, + c.contactno AS ridercontact, + d.slab, d.pricingdate, d.baseprice, d.minkm, + d.priceperkm, d.maxkm, d.orders, + d.othercharges, d.surgecharges + FROM deliveries a + JOIN tenants b ON a.tenantid = b.tenantid + JOIN app_users c ON a.userid = c.userid + JOIN tenantpricing d ON a.tenantid = d.tenantid + WHERE 1=1 + ` + + queryBuilder.WriteString(baseQuery) + + // ✅ DATE FILTER + if input.Fromdate != "" && input.ToDate != "" { + queryBuilder.WriteString(" AND b.moduleid=6 AND a.deliverydate::date BETWEEN ? AND ?") + params = append(params, input.Fromdate, input.ToDate) + } + + // ✅ STATUS FILTER (ignore "all") + if input.Status != "" && strings.ToLower(input.Status) != "all" { + queryBuilder.WriteString(" AND b.moduleid=6 AND LOWER(a.orderstatus) = LOWER(?)") + params = append(params, input.Status) + } + + // ✅ KEYWORD FILTER + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + queryBuilder.WriteString(` + AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(b.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(CAST(a.orderid AS CHAR)) LIKE ? + ) + `) + + for i := 0; i < 6; i++ { + params = append(params, k) + } + } + + queryBuilder.WriteString(" ORDER BY a.deliveryid DESC") + + if input.Pagesize > 0 { + queryBuilder.WriteString(" LIMIT ? OFFSET ?") + params = append(params, input.Pagesize, offset) + } + + query := queryBuilder.String() + + db.DB.Raw(query, params...).Scan(&data) + return data +} + +func GetTenantLocationDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + + logInfo("Tenant Location Deliveries", "tenantid", input.Tenantid, "locationid", input.Locationid) + + var data []models.Deliveryinfo + var queryBuilder strings.Builder + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + // 🔹 Base query + queryBuilder.WriteString(deliveries) + queryBuilder.WriteString(` WHERE b.moduleid = 6 AND a.tenantid = ? AND a.locationid = ?`) + params = append(params, input.Tenantid, input.Locationid) + + // 🔹 USER FILTER (apply only if userid > 0) + if input.UserID > 0 { + queryBuilder.WriteString(` AND a.userid = ?`) + params = append(params, input.UserID) + } + + // 🔹 STATUS FILTER (ignore "all", case-insensitive) + if input.Status != "" && strings.ToLower(input.Status) != "all" { + queryBuilder.WriteString(` AND LOWER(a.orderstatus) = LOWER(?)`) + params = append(params, input.Status) + } + + // 🔹 DATE FILTER + if input.Fromdate != "" && input.ToDate != "" { + queryBuilder.WriteString(` AND a.deliverydate::date BETWEEN ? AND ?`) + params = append(params, input.Fromdate, input.ToDate) + } + + // 🔹 KEYWORD FILTER (case-insensitive) + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + queryBuilder.WriteString(` + AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(b.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(CAST(a.orderid AS CHAR)) LIKE ? + ) + `) + + // ✔ exactly 6 placeholders + for i := 0; i < 6; i++ { + params = append(params, k) + } + } + + // 🔹 ORDER + PAGINATION + queryBuilder.WriteString(` ORDER BY a.deliveryid DESC`) + + if input.Pagesize > 0 { + queryBuilder.WriteString(` LIMIT ? OFFSET ?`) + params = append(params, input.Pagesize, offset) + } + + finalQuery := queryBuilder.String() + logInfo("Final Query", "query", finalQuery) + logInfo("Params", "params", params) + + db.DB.Raw(finalQuery, params...).Scan(&data) + return data +} + +func GetLocationDeliveries(input models.DeliveryQuery) []models.Deliveryinfo { + + logInfo("Location Deliveries", "locationid", input.Locationid) + + var q1 string + var data []models.Deliveryinfo + offset := (input.Pageno - 1) * input.Pagesize + + if input.Status != "" { + + if input.Fromdate != "" && input.ToDate != "" { + + if input.Pagesize != 0 { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Locationid, input.Status, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC" + db.DB.Raw(q1, input.Locationid, input.Status, input.Fromdate, input.ToDate).Find(&data) + } + + } else { + + if input.Pagesize != 0 { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Locationid, input.Status, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.orderstatus=? ORDER BY a.deliveryid DESC" + db.DB.Raw(q1, input.Locationid, input.Status).Find(&data) + } + } + + } else { + + if input.Pagesize != 0 { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC LIMIT ? OFFSET ?" + db.DB.Raw(q1, input.Locationid, input.Fromdate, input.ToDate, input.Pagesize, offset).Find(&data) + + } else { + + q1 = deliveries + " WHERE b.moduleid=6 and a.locationid=? AND a.deliverydate::date BETWEEN ? AND ? ORDER BY a.deliveryid DESC" + db.DB.Raw(q1, input.Locationid, input.Fromdate, input.ToDate).Find(&data) + } + } + return data +} + +// func PublishLogv1(input []models.Deliverylogs) error { +// key := "Deliverylogs" + +// for _, item := range input { +// // Convert each item to JSON +// jsonData, err := json.Marshal(item) +// if err != nil { +// return fmt.Errorf("failed to marshal delivery log: %v", err) +// } + +// // Push each record into Redis list +// if err := db.Rdb.RPush(db.Ctx, key, jsonData).Err(); err != nil { +// return fmt.Errorf("failed to push to Redis: %v", err) +// } +// } + +// return nil +// } + +// func PublishLogv1(input []models.Deliverylogs) error { + +// for _, item := range input { + +// // key per delivery +// key := fmt.Sprintf("Deliverylogs:%d", item.Deliveryid) + +// // Convert to JSON +// jsonData, err := json.Marshal(item) +// if err != nil { +// return fmt.Errorf("marshal error: %v", err) +// } + +// // Write to delivery-specific list +// if err := db.Rdb.RPush(db.Ctx, key, jsonData).Err(); err != nil { +// return fmt.Errorf("redis push error: %v", err) +// } +// } + +// return nil +// } + +func PublishLogv1(input []models.Deliverylogs) error { + logInfo("PublishLogv1", "records", len(input)) + + pipe := db.Rdb.TxPipeline() + + for _, item := range input { + + logInfo("DELIVERY LOG RECEIVED", + "deliveryid", item.Deliveryid, + "userid", item.Userid, + "logdate", item.Logdate, + ) + + logTime, err := time.Parse("2006-01-02 15:04:05", item.Logdate) + if err != nil { + logError("❌ TIME PARSE FAILED", "error", err) + continue + } + + ts := logTime.Unix() + + logKey := "Deliverylogs:" + strconv.Itoa(item.Deliveryid) + userIndexKey := "user:deliverylogs:" + strconv.Itoa(item.Userid) + + jsonData, _ := json.Marshal(item) + + pipe.RPush(db.Ctx, logKey, jsonData) + pipe.ZAdd(db.Ctx, userIndexKey, redis.Z{ + Score: float64(ts), + Member: item.Deliveryid, + }) + + logInfo("✅ ZADD PREPARED", "key", userIndexKey, "deliveryid", item.Deliveryid) + } + + _, err := pipe.Exec(db.Ctx) + if err != nil { + logError("❌ PIPELINE EXEC FAILED", "error", err) + } + + return err +} + +func GetDeliverylogsv1(did int) ([]models.Deliverylogs, error) { + logInfo("GetDeliverylogsv1", "deliveryid", did) + var result []models.Deliverylogs + + key := "Deliverylogs" + + // Get all elements from the Redis list + values, err := db.Rdb.LRange(db.Ctx, key, 0, -1).Result() + if err != nil { + return nil, errors.New("failed to read from Redis: " + err.Error()) + } + + for _, v := range values { + var log models.Deliverylogs + if err := json.Unmarshal([]byte(v), &log); err != nil { + continue // skip invalid JSON entries + } + + // filter only the deliveryid we need + if log.Deliveryid == did { + result = append(result, log) + } + } + + return result, nil +} + +func GetDeliveryLogsv1(deliveryID string) ([]models.Deliverylogs, error) { + logInfo("GetDeliveryLogsv1", "deliveryid", deliveryID) + ctx := context.Background() + var results []models.Deliverylogs + + key := "Deliverylogs:" + deliveryID + + // Fetch logs ONLY for this delivery + listItems, err := db.Rdb.LRange(ctx, key, 0, -1).Result() + if err != nil { + return nil, errors.New("redis lrange error: " + err.Error()) + } + + for _, item := range listItems { + + var log models.Deliverylogs + if err := json.Unmarshal([]byte(item), &log); err != nil { + continue + } + + // Fetch username using redis cached function + if log.Userid != 0 { + user, err := GetUserCached(log.Userid) + if err == nil { + log.Firstname = user.Firstname + log.Lastname = user.Lastname + } + } + + results = append(results, log) + } + + return results, nil +} + +// func GetDeliveryCached(deliveryID int) (models.Delivery, error) { +// ctx := context.Background() +// var data models.Delivery + +// key := fmt.Sprintf("delivery:info:%d", deliveryID) + +// // Check in Redis +// cached, err := db.Rdb.Get(ctx, key).Result() +// if err == nil { +// json.Unmarshal([]byte(cached), &data) +// return data, nil +// } + +// // Fetch from DB if not cached +// q := ` +// SELECT deliveryid, pickupcustomer, deliverycustomer, +// pickupcontactno, deliverycontactno +// FROM deliveries +// WHERE deliveryid=? LIMIT 1 +// ` +// if err := db.DB.Raw(q, deliveryID).Scan(&data).Error; err != nil { +// return data, err +// } + +// // Store back to Redis for 6 hours +// b, _ := json.Marshal(data) +// db.Rdb.Set(ctx, key, b, 6*time.Hour) + +// return data, nil +// } + +func GetUserDeliveryLogs(userid int, fromdate, todate string) ([]models.Deliverylogs, error) { + logInfo("GetUserDeliveryLogs", "userid", userid) + + var fromTS int64 = 0 + var toTS int64 = time.Now().Unix() + + if fromdate != "" { + t, err := time.Parse("2006-01-02", fromdate) + if err != nil { + return nil, errors.New("invalid fromdate") + } + fromTS = t.Unix() + } + + if todate != "" { + t, err := time.Parse("2006-01-02", todate) + if err != nil { + return nil, errors.New("invalid todate") + } + toTS = t.Add(23*time.Hour + 59*time.Minute + 59*time.Second).Unix() + } + + // 1️⃣ Get delivery IDs for user (O(logN)) + deliveryIDs, err := db.Rdb.ZRangeByScore( + db.Ctx, + "user:deliverylogs:"+strconv.Itoa(userid), + &redis.ZRangeBy{ + Min: strconv.FormatInt(fromTS, 10), + Max: strconv.FormatInt(toTS, 10), + }, + ).Result() + + if err != nil || len(deliveryIDs) == 0 { + return []models.Deliverylogs{}, nil + } + + // 2️⃣ Batch fetch logs + var result []models.Deliverylogs + + for _, id := range deliveryIDs { + key := "Deliverylogs:" + id + + values, err := db.Rdb.LRange(db.Ctx, key, 0, -1).Result() + if err != nil { + continue + } + + for _, v := range values { + var log models.Deliverylogs + if err := json.Unmarshal([]byte(v), &log); err == nil { + result = append(result, log) + } + } + } + + return result, nil +} + +func logInfo(msg string, props ...interface{}) { + utils.Logger.Infow(msg, props...) +} + +func logError(msg string, props ...interface{}) { + utils.Logger.Errorw(msg, props...) +} diff --git a/domain/invoice.go b/domain/invoice.go new file mode 100644 index 0000000..717d5b6 --- /dev/null +++ b/domain/invoice.go @@ -0,0 +1,207 @@ +package domain + +import ( + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" +) + +func GetAllInvoice(stat, tid int) []models.Tenantsales { + + var q1 string + + var data []models.Tenantsales + + const ( + base = `select a.* from tenantsales a` + ) + + if stat == 1 { + + if tid != 0 { + + q1 = base + ` where a.billstatus=0 and a.tenantid=` + strconv.Itoa(tid) + ` and a.duedate::date > CURRENT_DATE order by salesid desc ` + + } else { + + q1 = base + ` where a.billstatus=0 and a.duedate::date > CURRENT_DATE order by salesid desc ` + } + + } else if stat == 2 { + + if tid != 0 { + + q1 = base + ` where a.billstatus=0 and a.tenantid=` + strconv.Itoa(tid) + ` and a.duedate::date < CURRENT_DATE order by salesid desc ` + + } else { + + q1 = base + ` where a.billstatus=0 and a.duedate::date < CURRENT_DATE order by salesid desc ` + + } + + } else if stat == 3 { + + if tid != 0 { + + q1 = base + ` where a.billstatus=2 and a.tenantid=` + strconv.Itoa(tid) + ` order by salesid desc ` + + } else { + + q1 = base + ` where a.billstatus=2 order by salesid desc ` + } + + } else { + + if tid != 0 { + + q1 = base + ` where a.tenantid=` + strconv.Itoa(tid) + ` order by salesid desc ` + + } else { + + q1 = base + ` order by salesid desc ` + } + + } + // print(q1) + db.DB.Raw(q1).Preload("Tenantsalesdetails").Find(&data) + + return data + +} + +func GetInvoiceOrders(tid int, fdate, tdate string) models.InvoiceOrders { + + var q1 string + + var data models.InvoiceOrders + + q1 = `SELECT round(SUM(actualkms),0) AS actualkms,round(SUM(kms),0) AS kms,count(*) as deliveries FROM deliveries + WHERE orderstatus='delivered' and tenantid=` + strconv.Itoa(tid) + ` and date(deliverydate) between '` + fdate + `' and '` + tdate + `'` + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetInvoiceInsight(tid int) models.InvoiceInsight { + + var q1 string + + var data models.InvoiceInsight + + if tid != 0 { + + q1 = `Select COUNT(*) AS totalcount, + SUM(totalamount) AS total, + SUM(CASE WHEN billstatus=0 and status='pending' AND duedate > CURRENT_DATE THEN 1 ELSE 0 END) AS pendingcount, + SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate > CURRENT_DATE THEN totalamount ELSE 0 END) AS pending, + (SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate > CURRENT_DATE THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS pendingpercent, + SUM(CASE WHEN billstatus = 1 THEN 1 ELSE 0 END) AS confirmedcount, + SUM(CASE WHEN billstatus = 1 THEN totalamount ELSE 0 END) AS confirmed, + (SUM(CASE WHEN billstatus = 1 THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS confrimedpercent, + SUM(CASE WHEN billstatus = 2 THEN 1 ELSE 0 END) AS paidcount, + SUM(CASE WHEN billstatus = 2 THEN totalamount ELSE 0 END) AS paid, + (SUM(CASE WHEN billstatus = 2 THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS paidpercent, + SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate <= CURRENT_DATE THEN 1 ELSE 0 END) AS overduecount, + SUM(CASE WHEN billstatus IN(0) and status='pending' AND duedate <= CURRENT_DATE THEN totalamount ELSE 0 END) AS overdue, + (SUM(CASE WHEN billstatus IN(0) and status='pending' AND duedate <= CURRENT_DATE THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS overduepercent + FROM tenantsales where tenantid=` + strconv.Itoa(tid) + + } else { + + q1 = `Select COUNT(*) AS totalcount, + SUM(totalamount) AS total, + SUM(CASE WHEN billstatus=0 and status='pending' AND duedate > CURRENT_DATE THEN 1 ELSE 0 END) AS pendingcount, + SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate > CURRENT_DATE THEN totalamount ELSE 0 END) AS pending, + (SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate > CURRENT_DATE THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS pendingpercent, + SUM(CASE WHEN billstatus = 1 THEN 1 ELSE 0 END) AS confirmedcount, + SUM(CASE WHEN billstatus = 1 THEN totalamount ELSE 0 END) AS confirmed, + (SUM(CASE WHEN billstatus = 1 THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS confrimedpercent, + SUM(CASE WHEN billstatus = 2 THEN 1 ELSE 0 END) AS paidcount, + SUM(CASE WHEN billstatus = 2 THEN totalamount ELSE 0 END) AS paid, + (SUM(CASE WHEN billstatus = 2 THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS paidpercent, + SUM(CASE WHEN billstatus IN (0) and status='pending' AND duedate <= CURRENT_DATE THEN 1 ELSE 0 END) AS overduecount, + SUM(CASE WHEN billstatus IN(0) and status='pending' AND duedate <= CURRENT_DATE THEN totalamount ELSE 0 END) AS overdue, + (SUM(CASE WHEN billstatus IN(0) and status='pending' AND duedate <= CURRENT_DATE THEN totalamount ELSE 0 END) / SUM(totalamount) * 100) AS overduepercent + FROM tenantsales` + + } + + // print(q1) + db.DB.Raw(q1).Find(&data) + + return data + +} + +func CreateInvoice(data models.Tenantsales) error { + + tx := db.DB.Begin() + + t1 := tx.Create(&data) + if t1.Error != nil { + + utils.Error("CreateInvoice error", "error", t1.Error) + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("deliveries").Where("deliverydate>=? and deliverydate<=?", data.Tenantsalesdetails[0].Fromdate, data.Tenantsalesdetails[0].Todate).Update("settlementid", data.Salesid) + if t2.Error != nil { + + utils.Error("CreateInvoice deliveries update error", "error", t2.Error) + tx.Rollback() + return t2.Error + } + + UpdateSeqno(data.Tenantid, "INV") + + err := tx.Commit().Error + + if err != nil { + + return err + + } + return nil +} + +func UpdateInvoice(data models.Tenantsales) error { + + tx := db.DB.Begin() + + t1 := tx.Where("salesid=?", data.Salesid).Updates(&data) + if t1.Error != nil { + + utils.Error("UpdateInvoice error", "error", t1.Error) + tx.Rollback() + } + err := tx.Commit().Error + + if err != nil { + return err + } + return nil +} + +func UpdateInvoiceStatus(data models.InvoiceStatus) error { + + tx := db.DB.Begin() + + t1 := tx.Table("tenantsales").Where("salesid=?", data.Salesid).Updates(&data) + if t1.Error != nil { + + utils.Error("UpdateInvoiceStatus error", "error", t1.Error) + tx.Rollback() + } + err := tx.Commit().Error + + if err != nil { + return err + } + return nil +} diff --git a/domain/order.go b/domain/order.go new file mode 100644 index 0000000..8976f8a --- /dev/null +++ b/domain/order.go @@ -0,0 +1,1072 @@ +package domain + +import ( + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" + "strings" +) + +// base = `SELECT DISTINCT a.orderheaderid, a.applocationid, h.locationname AS applocation, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, +// a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, +// a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, f.orderstatus AS deliverystatus, +// f.assigntime, f.starttime, f.arrivaltime, f.pickuptime, f.deliverytime, f.canceltime, +// a.deliverycharge, a.kms, f.actualkms, f.deliveryamt, +// a.customerid, a.pickuplocationid, a.pickupaddress, a.pickuplat, a.pickuplong, +// a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, +// a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, +// a.deliverycustomer,a.deliverycontactno,a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, b.customertoken, +// c.tenantname, c.tenanttoken, c.primarycontact AS tenantcontactno, c.postcode AS tenantpostcode, c.suburb AS tenantsuburb, c.city AS tenantcity, +// d.locationname, d.contactno AS locationcontactno, d.postcode AS locationpostcode, d.suburb AS locationsuburb, d.city AS locationcity, g.firstname AS rider, g.contactno AS ridercontactno, +// g.userfcmtoken as ridertoken +// FROM orders a +// INNER JOIN customers b ON a.customerid = b.customerid +// INNER JOIN tenants c ON a.tenantid = c.tenantid +// INNER JOIN tenantlocations d ON a.locationid = d.locationid +// LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid +// LEFT JOIN app_users g ON f.userid = g.userid +// INNER JOIN app_location h ON a.applocationid = h.applocationid +// INNER JOIN app_locationconfig i ON a.applocationid = i.applocationid` + +const ( + base = `SELECT DISTINCT a.orderheaderid, a.applocationid, h.locationname AS applocation, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, + a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, + a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, + a.deliverycharge, a.kms, + a.customerid, a.pickuplocationid, a.pickupaddress, a.pickuplat, a.pickuplong, a.pickupslot, + a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, + a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, + a.deliverycustomer,a.deliverycontactno,a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, a.quantity, a.collectionamt, b.customertoken, + c.tenantname, c.tenanttoken, c.primarycontact AS tenantcontactno, c.postcode AS tenantpostcode, c.suburb AS tenantsuburb, c.city AS tenantcity, + d.locationname, d.contactno AS locationcontactno, d.postcode AS locationpostcode, d.suburb AS locationsuburb, d.city AS locationcity, + f.slab, f.pricingdate, f.baseprice, f.minkm, f.priceperkm, f.maxkm, f.orders, f.othercharges, f.surgecharges + FROM orders a + LEFT JOIN customers b ON a.customerid = b.customerid + INNER JOIN tenants c ON a.tenantid = c.tenantid + INNER JOIN tenantlocations d ON a.locationid = d.locationid + INNER JOIN app_location h ON a.applocationid = h.applocationid + INNER JOIN app_locationconfig i ON a.applocationid = i.applocationid + LEFT JOIN tenantpricing f ON a.tenantid = f.tenantid` + + orderdetails = `SELECT DISTINCT a.orderheaderid, a.applocationid, + a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, + a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, + a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, + a.deliverycharge, a.kms, + a.customerid, a.pickupaddress, a.pickuplat, a.pickuplong, a.pickupslot, + a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, + a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, + a.deliverycustomer,a.deliverycontactno, a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, a.orderamount, a.quantity, a.collectionamt, + b.tenantname, b.tenanttoken, b.primarycontact AS tenantcontactno, b.postcode AS tenantpostcode, b.suburb AS tenantsuburb,b.city AS tenantcity, b.address AS tenantaddress, + c.locationname, c.contactno AS locationcontactno, c.postcode AS locationpostcode, c.suburb AS locationsuburb, c.city AS locationcity, c.address AS locationaddress, + d.locationname AS applocation, f.slab, f.pricingdate, f.baseprice, f.minkm, f.priceperkm, f.maxkm, f.orders, f.othercharges, f.surgecharges + FROM orders a + INNER JOIN tenants b ON a.tenantid = b.tenantid + INNER JOIN tenantlocations c ON a.locationid = c.locationid + INNER JOIN app_location d ON a.applocationid = d.applocationid + INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid + LEFT JOIN tenantpricing f ON a.tenantid = f.tenantid` +) + +func GetTenantLocationOrders(input models.DeliveryQuery) []models.OrderInfo { + var data []models.OrderInfo + var query string + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + baseQuery := base + ` WHERE c.moduleid = 6 and a.tenantid = ? and a.locationid = ?` + params = append(params, input.Tenantid, input.Locationid) + + utils.Info("Fetching Tenant Location Orders") + // Order status filtering + if input.Status == "ongoing" { + query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` + } else { + query = baseQuery + ` AND a.orderstatus = ?` + params = append(params, input.Status) + } + + // Date filtering + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, input.Fromdate, input.ToDate) + + // 🔍 Case-insensitive keyword filter + if input.Keyword != "" { + + k := "%" + strings.ToLower(input.Keyword) + "%" + + query += ` AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(c.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + + params = append(params, k, k, k, k, k, k) + } + + // 🔧 Config ID filter + if input.Configid != 0 { + query += ` AND a.configid = ?` + params = append(params, input.Configid) + } + + // Final ordering & paging + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, input.Pagesize, offset) + + // Debug log + utils.Logger.Debugf("Executing: %s", query) + + db.DB.Raw(query, params...).Find(&data) + return data +} +func GetTenantLocationAppOrders(input models.DeliveryQuery) []models.OrderInfo { + var data []models.OrderInfo + var query string + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + baseQuery := base + ` WHERE c.moduleid = 6 + AND a.tenantid = ? + AND a.locationid = ? + AND a.applocationid = ?` + + params = append(params, input.Tenantid, input.Locationid, input.Applocationid) + + // status + if input.Status == "ongoing" { + query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` + } else { + query = baseQuery + ` AND a.orderstatus = ?` + params = append(params, input.Status) + } + + // date filter + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, input.Fromdate, input.ToDate) + + // keyword filter + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + query += ` AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(c.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + params = append(params, k, k, k, k, k, k) + } + + // configid filter + if input.Configid != 0 { + query += ` AND a.configid = ?` + params = append(params, input.Configid) + } + + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, input.Pagesize, offset) + + db.DB.Raw(query, params...).Find(&data) + return data +} + +func GetTenantAppOrders(input models.DeliveryQuery) []models.OrderInfo { + var data []models.OrderInfo + var query string + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + // Base query for Tenant + AppLocation + baseQuery := base + ` WHERE c.moduleid = 6 + AND a.tenantid = ? + AND a.applocationid = ?` + + params = append(params, input.Tenantid, input.Applocationid) + + // 🔥 Status filter + if input.Status == "ongoing" { + query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` + } else { + query = baseQuery + ` AND a.orderstatus = ?` + params = append(params, input.Status) + } + + // 📅 Date filter + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, input.Fromdate, input.ToDate) + + // 🔍 Keyword (case-insensitive) + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + query += ` AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(c.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + params = append(params, k, k, k, k, k, k) + } + + // ⚙️ Config ID filter + if input.Configid != 0 { + query += ` AND a.configid = ?` + params = append(params, input.Configid) + } + + // 📦 Order & Pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, input.Pagesize, offset) + + // Execute + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetAppOrders(input models.DeliveryQuery) []models.OrderInfo { + var data []models.OrderInfo + var query string + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + + // Base query for only AppLocation + baseQuery := base + ` WHERE c.moduleid = 6 + AND a.applocationid = ?` + + params = append(params, input.Applocationid) + + // 🔥 Status filter + if input.Status == "ongoing" { + query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` + } else { + query = baseQuery + ` AND a.orderstatus = ?` + params = append(params, input.Status) + } + + // 📅 Date filter + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, input.Fromdate, input.ToDate) + + // 🔍 Keyword (case-insensitive) + if input.Keyword != "" { + k := "%" + strings.ToLower(input.Keyword) + "%" + + query += ` AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(c.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + params = append(params, k, k, k, k, k, k) + } + + // ⚙️ Config ID filter + if input.Configid != 0 { + query += ` AND a.configid = ?` + params = append(params, input.Configid) + } + + // 📦 Order & Pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, input.Pagesize, offset) + + // Execute + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetTenantOrders(input models.DeliveryQuery) []models.OrderInfo { + var data []models.OrderInfo + var query string + var params []interface{} + + offset := (input.Pageno - 1) * input.Pagesize + baseQuery := base + ` WHERE c.moduleid = 6 and a.tenantid = ?` + params = append(params, input.Tenantid) + + utils.Info("Fetching Tenant Orders") + // Order status filtering + if input.Status == "ongoing" { + query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` + } else { + query = baseQuery + ` AND a.orderstatus = ?` + params = append(params, input.Status) + } + + // Date filtering + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, input.Fromdate, input.ToDate) + + // 🔍 Keyword filter (case-insensitive) + if input.Keyword != "" { + + keyword := strings.ToLower(input.Keyword) + like := "%" + keyword + "%" + + query += ` AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(c.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + + params = append(params, like, like, like, like, like, like) + } + + // 🔧 Config ID filter + if input.Configid != 0 { + query += ` AND a.configid = ?` + params = append(params, input.Configid) + } + + // Final ordering & paging + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, input.Pagesize, offset) + + // Debug log + utils.Logger.Debugf("Executing: %s", query) + + db.DB.Raw(query, params...).Find(&data) + return data +} + +func GetTenantOrdersv2(stat, fdate, tdate string, tid, pageno, pagesize int) []models.OrderInfo { + + var q1 string + + var data []models.OrderInfo + offset := (pageno - 1) * pagesize + + if stat == "ongoing" { + + q1 = orderdetails + ` where a.moduleid=6 and a.tenantid=? and a.orderstatus in ('pending','processing','ready') and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.tenantid=? and a.orderstatus= '` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.deliverytime LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + // print(q1) + db.DB.Raw(q1, tid).Find(&data) + + return data + +} + +func GetPartnerOrders(stat, fdate, tdate string, pid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + + if pageno <= 0 { + pageno = 1 + } + if pagesize <= 0 { + pagesize = 10 + } + offset := (pageno - 1) * pagesize + + utils.Info("Getting partner order details") + + // Base SELECT query (with joins) + query := orderdetails + ` WHERE b.moduleid = 6 and a.partnerid = ?` + params := []interface{}{pid} + + // Filter conditions + if fdate != "" && tdate != "" { + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, fdate, tdate) + } + if stat != "" { + query += ` AND a.orderstatus = ?` + params = append(params, stat) + } + if keyword != "" { + query += ` AND ( + a.pickupcustomer LIKE ? OR + b.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + like := "%" + keyword + "%" + params = append(params, like, like, like, like, like, like) + } + + // ✅ Count query for pagination + var total int64 + countQuery := ` + SELECT COUNT(DISTINCT a.orderheaderid) + FROM orders a + INNER JOIN tenants b ON a.tenantid = b.tenantid + INNER JOIN tenantlocations c ON a.locationid = c.locationid + INNER JOIN app_location d ON a.applocationid = d.applocationid + INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid + WHERE a.partnerid = ?` + countParams := []interface{}{pid} + + // Reapply filters to countQuery + if fdate != "" && tdate != "" { + countQuery += ` AND a.deliverytime::date BETWEEN ? AND ?` + countParams = append(countParams, fdate, tdate) + } + if stat != "" { + countQuery += ` AND a.orderstatus = ?` + countParams = append(countParams, stat) + } + if keyword != "" { + countQuery += ` AND ( + a.pickupcustomer LIKE ? OR + b.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + like := "%" + keyword + "%" + countParams = append(countParams, like, like, like, like, like, like) + } + + db.DB.Raw(countQuery, countParams...).Scan(&total) + + // ✅ Offset boundary fix + if int64(offset) >= total { + offset = 0 + pageno = 1 + } + + // Append pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + utils.Logger.Debugf("QUERY: %s", query) + utils.Logger.Debugf("PARAMS: %v", params) + + db.DB.Raw(query, params...).Find(&data) + utils.Logger.Infof("RESULT COUNT: %d", len(data)) + + return data +} + +func GetPartnerOrdersv2(stat, fdate, tdate string, pid, pageno, pagesize int) []models.OrderInfo { + + var data []models.OrderInfo + var q1 string + + offset := (pageno - 1) * pagesize + + if stat != "" { + + if fdate != "" && tdate != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.partnerid=` + strconv.Itoa(pid) + ` and a.orderstatus='` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.partnerid=` + strconv.Itoa(pid) + ` and a.orderstatus='` + stat + `' + order by a.deliverytime LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.partnerid=` + strconv.Itoa(pid) + ` and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + // print(q1) + + db.DB.Raw(q1).Find(&data) + return data +} + +func GetAllOrders(stat, fdate, tdate string, aid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + offset := (pageno - 1) * pagesize + utils.Info("All orders") + + // Base query + query := orderdetails + ` + WHERE b.moduleid = 6 + AND a.deliverytime::date BETWEEN ? AND ? + ` + params := []interface{}{fdate, tdate} + + // 🔹 applocationid filter + if aid != 0 { + query += ` AND a.applocationid = ?` + params = append(params, aid) + } + + // 🔹 Status filter + if stat != "" { + query += ` AND a.orderstatus = ?` + params = append(params, stat) + } + + // 🔹 Keyword filter (case-insensitive) + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + + query += ` + AND ( + LOWER(a.pickupcustomer) LIKE ? OR + LOWER(b.tenantname) LIKE ? OR + LOWER(a.deliverycustomer) LIKE ? OR + LOWER(a.pickupcontactno) LIKE ? OR + LOWER(a.deliverycontactno) LIKE ? OR + LOWER(a.orderid) LIKE ? + )` + + params = append(params, k, k, k, k, k, k) + } + + // Sorting + pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + // Debug + utils.Logger.Debugf("Executing query: %s", query) + print(query) + // DB execution + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetAllOrdersv2(stat, fdate, tdate string, pageno, pagesize int) []models.OrderInfo { + + var data []models.OrderInfo + var q1 string + offset := (pageno - 1) * pagesize + + if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.orderstatus='` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1).Find(&data) + return data +} + +func GetUserOrdersv2(stat, fdate, tdate string, uid, pageno, pagesize int) []models.OrderInfo { + + var data []models.OrderInfo + var q1 string + offset := (pageno - 1) * pagesize + + if uid != 0 { + + if fdate != "" && tdate != "" { + + if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and e.status = 'Active' and e.userid=` + strconv.Itoa(uid) + ` and a.orderstatus='` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and e.status = 'Active' and e.userid=` + strconv.Itoa(uid) + ` and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + } else if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and e.status = 'Active' and e.userid=` + strconv.Itoa(uid) + ` and a.orderstatus='` + stat + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + } + + // print(q1) + + db.DB.Raw(q1).Find(&data) + return data +} + +func GetLocationOrders(stat, fdate, tdate string, lid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + offset := (pageno - 1) * pagesize + + utils.Info("Getting location order details") + + // Start query and params + query := orderdetails + ` + WHERE b.moduleid = 6 and e.status = 'Active' AND a.locationid = ? + ` + params := []interface{}{lid} + + // Add date filter + if fdate != "" && tdate != "" { + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, fdate, tdate) + } + + // Add status filter + if stat != "" { + query += ` AND a.orderstatus = ?` + params = append(params, stat) + } + + // Add keyword filter + if keyword != "" { + query += ` + AND ( + a.pickupcustomer LIKE ? OR + a.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + likeKeyword := "%" + keyword + "%" + params = append(params, likeKeyword, likeKeyword, likeKeyword, likeKeyword, likeKeyword, likeKeyword) + } + + // Add sorting and pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + // Debugging + utils.Logger.Debugf("Executing query: %s", query) + + // Execute + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetUserOrders(stat, fdate, tdate string, uid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + offset := (pageno - 1) * pagesize + + utils.Info("Getting user order details") + + // Start query and params + query := orderdetails + ` + WHERE b.moduleid = 6 and e.status = 'Active' AND e.userid = ? + ` + params := []interface{}{uid} + + // Add date filter + if fdate != "" && tdate != "" { + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, fdate, tdate) + } + + // Add status filter + if stat != "" { + query += ` AND a.orderstatus = ?` + params = append(params, stat) + } + + // Add keyword filter + if keyword != "" { + query += ` + AND ( + a.pickupcustomer LIKE ? OR + a.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + likeKeyword := "%" + keyword + "%" + params = append(params, likeKeyword, likeKeyword, likeKeyword, likeKeyword, likeKeyword, likeKeyword) + } + + // Add sorting and pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + // Debugging + utils.Logger.Debugf("Executing query: %s", query) + + // Execute + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetAdminOrders(stat, fdate, tdate string, aid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + offset := (pageno - 1) * pagesize + + utils.Info("Getting admin order details") + + // Start query and params + query := orderdetails + ` WHERE 1=1` + var params []interface{} + + // applocationid filter (aid) + if aid != 0 { + query += ` AND a.applocationid = ?` + params = append(params, aid) + } + + // date filter + if fdate != "" && tdate != "" { + query += ` AND a.deliverytime::date BETWEEN ? AND ?` + params = append(params, fdate, tdate) + } + + // status filter + if stat != "" { + query += ` AND a.orderstatus = ?` + params = append(params, stat) + } + + // keyword search + if keyword != "" { + query += ` + AND ( + a.pickupcustomer LIKE ? OR + a.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + likeKeyword := "%" + keyword + "%" + params = append(params, + likeKeyword, likeKeyword, likeKeyword, + likeKeyword, likeKeyword, likeKeyword, + ) + } + + // Sorting and pagination + query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + // Debugging + utils.Logger.Debugf("Executing query: %s", query) + + // Execute the query + db.DB.Raw(query, params...).Find(&data) + + return data +} + +func GetAdminOrdersv2(stat, fdate, tdate string, aid, pageno, pagesize int) []models.OrderInfo { + + var data []models.OrderInfo + var q1 string + + utils.Info("Getting admin order details") + offset := (pageno - 1) * pagesize + + if aid != 0 { + + if fdate != "" && tdate != "" { + + if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.applocationid=` + strconv.Itoa(aid) + ` and a.orderstatus='` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.applocationid=` + strconv.Itoa(aid) + ` and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + } else if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.applocationid=` + strconv.Itoa(aid) + ` and a.orderstatus='` + stat + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + } else { + + if stat != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.orderstatus='` + stat + `' and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.deliverytime::date between '` + fdate + `' and '` + tdate + `' + order by a.orderheaderid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + } + + } + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1).Find(&data) + return data +} + +func GetCustomerOrders(stat, fdate, tdate string, cid, mid, pageno, pagesize int, keyword string) []models.OrderInfo { + var data []models.OrderInfo + utils.Info("Getting customer order details") + // Safety defaults + if pageno <= 0 { + pageno = 1 + } + if pagesize <= 0 { + pagesize = 10 + } + offset := (pageno - 1) * pagesize + + // Prepare base query + baseQuery := orderdetails + ` WHERE b.moduleid = 6 and a.customerid = ?` + params := []interface{}{cid} + + // Filters + if mid != 0 { + baseQuery += ` AND a.moduleid = ?` + params = append(params, mid) + } + if fdate != "" && tdate != "" { + baseQuery += ` AND a.orderdate::date BETWEEN ? AND ?` + params = append(params, fdate, tdate) + } + if stat != "" { + baseQuery += ` AND a.orderstatus = ?` + params = append(params, stat) + } + if keyword != "" { + baseQuery += ` AND ( + a.pickupcustomer LIKE ? OR + b.tenantname LIKE ? OR + a.deliverycustomer LIKE ? OR + a.pickupcontactno LIKE ? OR + a.deliverycontactno LIKE ? OR + a.orderid LIKE ? + )` + like := "%" + keyword + "%" + params = append(params, like, like, like, like, like, like) + } + + // Count total + var total int64 + countQuery := strings.Replace(baseQuery, "SELECT DISTINCT a.orderheaderid, ", "SELECT COUNT(DISTINCT a.orderheaderid) ", 1) + db.DB.Raw(countQuery, params...).Scan(&total) + + // Prevent out-of-bound offset + if int64(offset) >= total { + offset = 0 + pageno = 1 + } + + // Final paginated query + finalQuery := baseQuery + ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` + params = append(params, pagesize, offset) + + utils.Logger.Debugf("QUERY: %s", finalQuery) + utils.Logger.Debugf("PARAMS: %v", params) + + res := db.DB.Raw(finalQuery, params...).Find(&data) + if res.Error != nil { + utils.Error("ERROR querying customer orders", "error", res.Error) + } + utils.Logger.Infof("RESULT COUNT: %d", len(data)) + + print(finalQuery) + + return data +} + +func GetCustomerOrdersv2(stat, fdate, tdate string, cid, mid, pageno, pagesize int) []models.OrderInfo { + + var data []models.OrderInfo + var q1 string + + if stat != "" { + + if fdate != "" && tdate != "" { + + q1 = orderdetails + ` where a.moduleid=6 and a.customerid=` + strconv.Itoa(cid) + ` and a.orderstatus='` + stat + `' and a.orderdate::date between '` + fdate + `' and '` + tdate + `' order by a.orderheaderid desc` + + } else { + + q1 = orderdetails + ` where a.moduleid=6 and a.customerid=` + strconv.Itoa(cid) + ` and a.orderstatus='` + stat + `' order by a.orderheaderid desc` + + } + + } else { + + if mid != 0 { + + q1 = orderdetails + ` where a.moduleid=` + strconv.Itoa(mid) + ` and a.customerid=` + strconv.Itoa(cid) + ` and a.orderdate::date between '` + fdate + `' and '` + tdate + `' order by a.orderheaderid desc` + + } else { + + q1 = orderdetails + ` where a.customerid=` + strconv.Itoa(cid) + ` and a.orderdate::date between '` + fdate + `' and '` + tdate + `' order by a.orderheaderid desc` + + } + + } + + // print(q1) + + db.DB.Raw(q1).Find(&data) + return data +} + +func GetOrderbyid(oid int) models.OrderInfo { + + var data models.OrderInfo + + q1 := orderdetails + ` where a.orderheaderid=` + strconv.Itoa(oid) + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetOrderbyStatus(uid int, stat string) models.OrderInfo { + + var data models.OrderInfo + + q1 := orderdetails + ` where a.orderheaderid=` + strconv.Itoa(uid) + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetOrderbyidV2(oid int) models.OrderInfo { + + var data models.OrderInfo + + q1 := orderdetails + ` where a.orderheaderid=` + strconv.Itoa(oid) + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetOrderDetailsByHeaderID(orderHeaderID int) ([]models.OrderDetails, float64, float64, error) { + var details []models.OrderDetails + var orderAmount float64 + var totalTaxAmount float64 + + query := ` + SELECT + a.orderdetailid, + a.orderheaderid, + a.tenantid, + a.locationid, + a.productid, + a.productname, + a.productdescription, + a.supplyqty, + a.balanceqty, + a.orderqty, + a.price, + a.unitid, + a.unitname, + a.productaddonid, + a.addontypeid, + a.productmapid, + a.productvariantid, + a.productaddondescription, + a.discountid, + a.discountname, + a.discountcode, + a.discountterms, + a.discountpercentage, + a.discountamount, + a.landingamount, + a.taxpercentage, + a.taxamount, + a.productsumprice, + a.itemstatus, + a.delivered, + a.taxamount, + COALESCE(b.orderamount, 0) as orderamount, + COALESCE(b.taxamount, 0) as totaltaxamount, + c.productimage + FROM orderdetails a + LEFT JOIN orders b ON b.orderheaderid = a.orderheaderid + LEFT JOIN products c ON a.productid = c.productid + WHERE a.orderheaderid = ? + ` + + err := db.DB.Raw(query, orderHeaderID).Scan(&details).Error + if err != nil { + return nil, 0, 0, err + } + + if len(details) > 0 { + orderAmount = details[0].Orderamount + } + + if len(details) > 0 { + totalTaxAmount = details[0].Totaltaxamount + } + + return details, orderAmount, totalTaxAmount, nil +} + +func GetCustomerOrderByLocation(customerid int) ([]models.CustomerOrderLocation, error) { + query := ` + SELECT + c.locationname AS locationname, + COUNT(a.orderid) AS ordercount, + c.contactno AS contactno, + c.address AS address, + c.suburb AS suburb, + c.city AS city, + c.state AS state, + c.postcode AS postcode, + c.latitude AS latitude, + c.longitude AS longitude + FROM orders a + LEFT JOIN tenantlocations c ON a.locationid = c.locationid + WHERE a.customerid = ? + GROUP BY + c.locationname + ` + + var result []models.CustomerOrderLocation + err := db.DB.Raw(query, customerid).Scan(&result).Error + + // Debug actual data + utils.Logger.Debugf("result: %+v", result) + + // return [] instead of null + if result == nil { + result = []models.CustomerOrderLocation{} + } + + return result, err +} diff --git a/domain/partner.go b/domain/partner.go new file mode 100644 index 0000000..05a7783 --- /dev/null +++ b/domain/partner.go @@ -0,0 +1,1388 @@ +package domain + +import ( + "context" + "encoding/json" + "errors" + "nearle/db" + "nearle/models" + "nearle/utils" + "reflect" + "strconv" + "strings" + "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func GetActiveRiders(partnerid, aid, uid int, keyword string, pageno, pagesize int) []models.RiderInfo { + + data := make([]models.RiderInfo, 0) // ✅ IMPORTANT + var qb strings.Builder + var params []interface{} + + offset := 0 + if pageno > 0 && pagesize > 0 { + offset = (pageno - 1) * pagesize + } + + qb.WriteString(` + WITH latest_logs AS ( + SELECT *, + ROW_NUMBER() OVER (PARTITION BY userid ORDER BY logdate DESC) AS rn + FROM riderlogs + WHERE logdate >= CURRENT_DATE + AND logdate < CURRENT_DATE + INTERVAL '1 day' + ) + SELECT DISTINCT + a.userid,a.firstname,a.lastname,CONCAT(a.firstname,' ',a.lastname) AS fullname,a.contactno,a.userfcmtoken,a.partnerid,a.applocationid,c.identificationno, + c.registrationno,c.licenseno,c.vehiclename,c.vehicleno,d.shiftid,d.starttime,d.endtime,d.shifthours,d.basefare,d.fuelcharge,e.logdate,e.login,e.logout, + e.workhours,e.shorthours,e.logstatus,e.latitude,e.longitude,a.status,f.locationname AS applocation + FROM app_users a + JOIN ridersettings c ON c.userid = a.userid + JOIN ridershifts d ON d.shiftid = c.shiftid + JOIN latest_logs e ON e.userid = a.userid AND e.rn = 1 + JOIN app_location f ON f.applocationid = a.applocationid + + WHERE a.configid = 6 + AND a.status = 'Active' + AND e.logstatus = 0 + AND EXISTS ( + SELECT 1 + FROM app_userpools p + WHERE p.userid = a.userid + AND p.onduty = 1 + ) + `) + + if aid != 0 { + qb.WriteString(" AND a.applocationid = ?") + params = append(params, aid) + } + if partnerid != 0 { + qb.WriteString(" AND a.partnerid = ?") + params = append(params, partnerid) + } + if uid != 0 { + qb.WriteString(" AND a.userid = ?") + params = append(params, uid) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + qb.WriteString(` + AND ( + LOWER(a.firstname) LIKE ? OR + LOWER(a.lastname) LIKE ? OR + LOWER(a.contactno) LIKE ? OR + LOWER(a.userid::text) LIKE ? + ) + `) + for i := 0; i < 4; i++ { + params = append(params, k) + } + } + + qb.WriteString(" ORDER BY a.userid DESC") + + if pageno > 0 && pagesize > 0 { + qb.WriteString(" LIMIT ? OFFSET ?") + params = append(params, pagesize, offset) + } + + utils.Logger.Debugf("ActiveRiders SQL: %s", qb.String()) + utils.Logger.Debugf("Params: %v", params) + + print(qb.String()) + print(params) + + db.DB.Raw(qb.String(), params...).Scan(&data) + return data +} + +func GetActiveRidersv2(partnerid, aid, uid, tenantid int) []models.RiderInfo { + var data []models.RiderInfo + var q1 string + + const riders = `SELECT DISTINCT a.*,a.userid, a.firstname, a.lastname, + CONCAT(a.firstname, ' ', a.lastname) AS fullname, + a.contactno, a.userfcmtoken, a.partnerid, a.applocationid, + c.identificationno, c.registrationno, c.licenseno, c.vehiclename, c.vehicleno, + d.shiftid, d.starttime, d.endtime, d.shifthours, d.basefare, d.fuelcharge, + e.logdate, e.login, e.logout, e.workhours, e.shorthours, e.logstatus, a.status, + f.locationname AS applocation + FROM + app_users a + LEFT JOIN ridersettings c ON a.userid = c.userid + INNER JOIN ridershifts d ON a.shiftid = d.shiftid + INNER JOIN ( + SELECT r1.* + FROM riderlogs r1 + INNER JOIN ( + SELECT userid, MAX(logdate) AS max_logdate + FROM riderlogs + WHERE logdate::date = CURRENT_DATE + GROUP BY userid + ) r2 ON r1.userid = r2.userid AND r1.logdate = r2.max_logdate + WHERE r1.logstatus = 0 + ) e ON a.userid = e.userid + INNER JOIN app_location f ON a.applocationid = f.applocationid + INNER JOIN app_locationconfig g ON g.applocationid = f.applocationid` + + if aid != 0 { + q1 = riders + " WHERE a.configid = 6 AND a.status = 'Active' AND e.logdate::date = CURRENT_DATE AND e.logstatus = 0 AND a.applocationid = ?" + db.DB.Raw(q1, aid).Find(&data) + } else if partnerid != 0 { + q1 = riders + " WHERE a.configid = 6 AND a.status = 'Active' AND e.logdate::date = CURRENT_DATE AND e.logstatus = 0 AND a.partnerid = ?" + db.DB.Raw(q1, partnerid).Find(&data) + } else if tenantid != 0 { + q1 = riders + " WHERE a.status = 'Active' AND a.configid = 6 AND a.status = 'Active' AND a.tenantid = ?" + db.DB.Raw(q1, tenantid).Find(&data) + } else { + q1 = riders + " WHERE g.status = 'Active' AND a.configid = 6 AND a.status = 'Active' AND e.logdate::date = CURRENT_DATE AND e.logstatus = 0 AND g.userid = ?" + db.DB.Raw(q1, uid).Find(&data) + } + + utils.Logger.Debugf("Query executed for riders") + + return data +} + +func GetRiderShifts(aid int) []models.Ridershifts { + + var data []models.Ridershifts + + q1 := "Select a.*,concat(a.starttime,'-',a.endtime) as shiftname from ridershifts a where a.applocationid=?" + + db.DB.Raw(q1, aid).Find(&data) + + return data +} + +func GetRiderPricing(aid int) []models.RiderPricing { + + var data []models.RiderPricing + + q1 := `SELECT a.shiftid, a.basefare, a.additionalcharges, a.fuelcharge, a.applocationid, b.locationname from ridershifts a + LEFT JOIN app_location b ON a.applocationid = b.applocationid` + + if aid > 0 { + q1 += ` WHERE a.applocationid=` + strconv.Itoa(aid) + } + + db.DB.Raw(q1).Find(&data) + + return data +} + +func GetRiderPool(partnerid int) []models.RiderPool { + + var data []models.RiderPool + + q1 := `SELECT b.poolid,a.userid,a.firstname,a.lastname,CONCAT(a.firstname,' ',a.lastname) AS fullname, + a.contactno,a.userfcmtoken,a.partnerid,a.applocationid, + c.identificationno,c.registrationno,c.licenseno,c.vehiclename,c.vehicleno, + d.shiftid,d.starttime,d.endtime,d.shifthours,d.basefare,d.fuelcharge,a.status + FROM + app_users a + INNER JOIN app_userpools b ON a.userid=b.userid + INNER JOIN ridersettings c ON a.userid=c.userid + INNER JOIN ridershifts d ON c.shiftid=d.shiftid + WHERE a.configid=6 and a.status='Active' and a.partnerid=?` + + db.DB.Raw(q1, partnerid).Find(&data) + + return data +} + +func GetRiderInfo(userid int) models.RiderInfo { + + var data models.RiderInfo + + q1 := `SELECT a.userid,a.firstname,a.lastname, CONCAT(a.firstname,' ',a.lastname) as fullname, + a.partnerid,a.configid,a.contactno, + a.address,a.suburb,a.city,a.state,a.postcode,a.latitude,a.longitude, + b.identificationno,b.vehicleno,b.vehiclename,b.licenseno,b.insuranceno,b.insurancedate, + c.shiftid,c.starttime,c.endtime,c.shifthours,c.basefare,c.additionalcharges,c.orders,c.fuelcharge,a.status, + a.applocationid,d.locationname as applocation,d.logseconds + FROM app_users a + INNER JOIN ridersettings b ON a.userid=b.userid + INNER JOIN ridershifts c on b.shiftid=c.shiftid + INNER JOIN app_location d on a.applocationid=d.applocationid + WHERE a.userid=?` + + utils.Logger.Debugf("Query executed for userid: %d", userid) + + db.DB.Raw(q1, userid).Find(&data) + + return data +} + +func GetRiderInfov2(userid int) models.RiderInfo { + + var data models.RiderInfo + + q1 := `SELECT a.userid,a.firstname,a.lastname, CONCAT(a.firstname,' ',a.lastname) as fullname, + a.partnerid,a.configid,a.contactno, + a.address,a.suburb,a.city,a.state,a.postcode,a.latitude,a.longitude,a.tenantid, + b.identificationno,b.vehicleno,b.vehiclename,b.licenseno,b.insuranceno,b.insurancedate, + c.shiftid,c.starttime,c.endtime,c.shifthours,c.basefare,c.additionalcharges,c.orders,c.fuelcharge,a.status, + a.applocationid,d.locationname as applocation,d.logseconds + FROM app_users a + LEFT JOIN ridersettings b ON a.userid=b.userid + INNER JOIN ridershifts c ON a.shiftid=c.shiftid + INNER JOIN app_location d on a.applocationid=d.applocationid + WHERE a.userid=?` + + utils.Logger.Debugf("Query executed for userid: %d", userid) + + db.DB.Raw(q1, userid).Find(&data) + + return data +} + +func GetRiderDetails(userid int) models.RiderDetails { + + var data models.RiderDetails + + q1 := `SELECT a.*,b.*,c.* from app_users a + INNER JOIN ridersettings b ON a.userid=b.userid + LEFT JOIN ridershifts c ON b.shiftid=c.shiftid + WHERE a.userid=?` + + db.DB.Raw(q1, userid).Find(&data) + + return data +} + +func GetAllRiders(aid, pid, uid int, status, keyword string, pageno, pagesize int) []models.RiderInfo { + var data []models.RiderInfo + var q1 string + var params []interface{} + + const riders = `SELECT DISTINCT a.userid,a.firstname,a.lastname, + CONCAT(a.firstname,' ',a.lastname) AS username, + a.partnerid,a.configid,a.contactno, + a.address,a.suburb,a.city,a.state,a.postcode,a.latitude,a.longitude,a.name,a.capacity,a.rangekm,a.batterypercentage,a.type, + b.identificationno,b.vehicleno,b.vehiclename,b.licenseno,b.insuranceno,b.insurancedate, + c.shiftid,c.starttime,c.endtime,c.shifthours,c.basefare,c.additionalcharges,c.orders, + c.fuelcharge,a.status,a.applocationid,d.locationname AS applocation + FROM app_users a + INNER JOIN ridersettings b ON a.userid=b.userid + INNER JOIN ridershifts c ON b.shiftid=c.shiftid + INNER JOIN app_location d ON a.applocationid=d.applocationid + INNER JOIN app_locationconfig e ON a.applocationid=e.applocationid` + + conditions := []string{"e.status='Active'"} + + if aid != 0 { + conditions = append(conditions, "a.applocationid=?") + params = append(params, aid) + } + + if pid != 0 { + conditions = append(conditions, "a.partnerid=?") + params = append(params, pid) + } + + if uid != 0 { + conditions = append(conditions, "a.userid=?") + params = append(params, uid) + } + + if status != "" { + conditions = append(conditions, "a.status=?") + params = append(params, status) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + conditions = append(conditions, + `(LOWER(a.firstname) LIKE ? + OR LOWER(a.lastname) LIKE ? + OR LOWER(a.contactno) LIKE ? + OR LOWER(a.userid::text) LIKE ?)`) + params = append(params, k, k, k, k) + } + + q1 = riders + " WHERE " + strings.Join(conditions, " AND ") + " ORDER BY a.userid DESC" + + // Apply pagination only if pagesize > 0 + if pagesize > 0 { + offset := (pageno - 1) * pagesize + q1 += " LIMIT ? OFFSET ?" + params = append(params, pagesize, offset) + } + + db.DB.Raw(q1, params...).Find(&data) + + return data +} + +func GetPartners(aid, pid int) []models.Partnerinfo { + + // onduty := c.Query("onduty") + + var data []models.Partnerinfo + var q1 string + + if pid != 0 { + + q1 = `select partnerid,applocationid,partnertypeid,partnername,primarycontact,primaryemail, + contactno,address,suburb,state,city,partnerimage from partnerinfo where status='Active' and partnerid=?` + db.DB.Raw(q1, pid).Find(&data) + + } else if aid != 0 { + + q1 = `select partnerid,applocationid,partnertypeid,partnername,primarycontact,primaryemail, + contactno,address,suburb,state,city,partnerimage from partnerinfo where status='Active' and applocationid=?` + db.DB.Raw(q1, aid).Find(&data) + + } else { + + q1 = `select partnerid,applocationid,partnertypeid,partnername,primarycontact,primaryemail, + contactno,address,suburb,state,city,partnerimage from partnerinfo where status='Active'` + db.DB.Raw(q1).Find(&data) + + } + + return data + +} + +func GetLocationConfig(uid, cid int) []models.Locationconfigs { + + var data []models.Locationconfigs + q1 := `SELECT Distinct a.applocationid,a.locationname FROM app_location a + inner join app_locationconfig b ON a.applocationid=b.applocationid + INNER JOIN app_users c ON a.applocationid=c.applocationid + INNER JOIN app_roles d ON d.roleid=c.roleid + WHERE b.status='Active' and b.userid=? ORDER BY applocationid ASC` + db.DB.Raw(q1, uid).Find(&data) + return data +} + +func GetPartnerUsers(aid int) []models.UserInfo { + + var data []models.UserInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname, + a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.applocationid,a.partnerid + from app_users a where configid=5 and a.applocationid=?` + + db.DB.Raw(q1, aid).Find(&data) + + return data + +} + +func GetAdmintoken(pid, uid, tid, aid int) models.AdminToken { + + var data models.AdminToken + var q1 string + + if tid != 0 && pid != 0 { + + q1 = `SELECT a.userid,a.userfcmtoken, b.closetime,b.opentime,c.tenantid + FROM app_users a + INNER JOIN partnerinfo b ON a.partnerid=b.partnerid + INNER JOIN tenants c ON a.userid=c.partneruserid + WHERE b.partnerid=? AND c.tenantid=?` + db.DB.Raw(q1, pid, tid).Find(&data) + + } else if aid != 0 { + + q1 = `SELECT distinct a.userid,a.userfcmtoken,b.notify,b.applocationid,c.opentime,c.closetime FROM app_users a + INNER JOIN app_locationconfig b ON a.userid=b.userid + INNER JOiN app_location c on a.applocationid= c.applocationid + WHERE b.notify='true' AND b.applocationid=?` + db.DB.Raw(q1, aid).Find(&data) + + } + + return data + +} + +func CreateRider(data models.Rider) error { + + var userpools models.Userpools + + tx := db.DB.Begin() + + // ✅ FIX 1: Proper upsert for app_users + t1 := tx.Table("app_users").Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "email"}}, // REQUIRED + DoUpdates: clause.AssignmentColumns([]string{ + "firstname", + "lastname", + "contactno", + "address", + "city", + "state", + }), + }).Create(&data) + + if t1.Error != nil { + tx.Rollback() + return t1.Error + } + + // ✅ FIX 2: Manually insert ridersettings (prevents ON CONFLICT error) + data.Ridersettings.Userid = data.Userid + data.Ridersettings.Partnerid = data.Partnerid + + tRs := tx.Table("ridersettings"). + Omit("riderid"). // 🚀 THIS FIXES YOUR ISSUE + Create(&data.Ridersettings) + if tRs.Error != nil { + tx.Rollback() + return tRs.Error + } + + // ✅ Existing logic (unchanged) + userpools.Userid = data.Userid + userpools.Partnerid = data.Partnerid + userpools.Vehicleid = data.Ridersettings.Vehicleid + + t2 := tx.Table("app_userpools").Create(&userpools) + if t2.Error != nil { + tx.Rollback() + return t2.Error + } + + return tx.Commit().Error +} + +func UpdateRiderSettings(data models.Rider) error { + + tx := db.DB.Begin() + + t1 := tx.Table("app_users").Where("userid=?", data.Userid).Updates(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("ridersettings").Where("userid=?", data.Userid).Updates(&data.Ridersettings) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +// func UpdateRiderInfo(data models.Rider) error { + +// tx := db.DB.Begin() + +// t1 := tx.Table("app_users").Where("userid=?", data.Userid).Updates(&data) +// if t1.Error != nil { + +// tx.Rollback() +// return t1.Error +// } + +// err := tx.Commit().Error +// if err != nil { + +// return err + +// } +// return nil + +// } + +func UpdateRiderInfo(data models.Rider) error { + + tx := db.DB.Begin() + + t1 := tx.Table("app_users").Where("userid=?", data.Userid).Updates(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("ridersettings").Where("userid=?", data.Userid).Updates(&data.Ridersettings) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func CreateRiderShift(data models.Ridershifts) error { + + tx := db.DB.Begin() + + t1 := tx.Table("ridershifts").Create(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func CreateRiderLog(data models.Riderlogs) error { + + tx := db.DB.Begin() + + t1 := tx.Table("riderlogs").Create(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("app_userpools").Where("userid=?", data.Userid).UpdateColumn("onduty", 1) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func GetBreaklog(bid int) models.Riderbreaks { + + var data models.Riderbreaks + + q1 := "select * from riderbreaks where breakid=?" + + db.DB.Raw(q1, bid).Find(&data) + + return data + +} + +func CreateBreakLog(data models.Riderbreaks) (int, error) { + + tx := db.DB.Begin() + + t1 := tx.Table("riderbreaks").Create(&data) + if t1.Error != nil { + + tx.Rollback() + return 0, t1.Error + } + + t2 := tx.Table("app_userpools").Where("userid=?", data.Userid).UpdateColumn("onduty", 0) + if t2.Error != nil { + + tx.Rollback() + return 0, t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return 0, err + + } + return data.Breakid, nil + +} + +func UpdateBreakLog(data models.Riderbreaks) error { + + tx := db.DB.Begin() + + t1 := tx.Table("riderbreaks").Where("breakid=?", data.Breakid).Updates(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("app_userpools").Where("userid=?", data.Userid).UpdateColumn("onduty", 1) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func UpdateRiderLog(data models.Riderlogs) error { + + tx := db.DB.Begin() + + t1 := tx.Table("riderlogs").Where("userid=?", data.Userid).Updates(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + t2 := tx.Table("app_userpools").Where("userid=?", data.Userid).UpdateColumn("onduty", 0) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + return nil + +} + +func GetRiderLog(uid int) (models.Riderlogs, error) { + + var data models.Riderlogs + + q1 := `SELECT a.*,b.onduty from riderlogs a + INNER JOIN app_userpools b ON a.userid=b.userid WHERE a.userid=? and a.logdate::date=CURRENT_DATE` + + err := db.DB.Raw(q1, uid).Find(&data).Error + if err != nil { + + return data, err + + } + + return data, nil + +} + +func GetRiderLogs(pid, aid int, fdate, tdate, keyword string) []models.RiderlogDetails { + + var data []models.RiderlogDetails + var q1 string + var params []interface{} + + if pid != 0 { + if fdate != "" && tdate != "" { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE a.partnerid = ? + AND a.logdate::date between ? and ?` + params = append(params, pid, fdate, tdate) + } else { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE a.partnerid = ? + AND a.logdate::date=CURRENT_DATE` + params = append(params, pid) + } + + } else if aid != 0 { + + if fdate != "" && tdate != "" { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE b.applocationid = ? + AND a.logdate::date between ? and ?` + params = append(params, aid, fdate, tdate) + } else { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE b.applocationid = ? + AND a.logdate::date=CURRENT_DATE` + params = append(params, aid) + } + + } else { + + if fdate != "" && tdate != "" { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE a.logdate::date between ? and ?` + params = append(params, fdate, tdate) + } else { + q1 = `SELECT a.*,b.*,CONCAT(b.firstname,' ',b.lastname) AS username, + (SELECT SUM(breakhours) FROM riderbreaks WHERE logid = a.logid AND userid = a.userid) as breakhours + FROM riderlogs a + INNER JOIN app_users b ON a.userid=b.userid + WHERE a.logdate::date=CURRENT_DATE` + } + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + q1 += ` + AND ( + LOWER(b.firstname) LIKE ? OR + LOWER(b.lastname) LIKE ? OR + LOWER(CONCAT(b.firstname,' ',b.lastname)) LIKE ? OR + b.userid LIKE ? + )` + params = append(params, k, k, k, keyword) + } + + // Finish query + q1 += " ORDER BY logid ASC" + + utils.Logger.Debugf("Query executed for RiderLogs") + + db.DB.Raw(q1, params...).Find(&data) + return data +} + +func GetRiderOrderCount(uid int) (int, error) { + + var dc int + + // q1 = `SELECT COUNT(*) as deliverycount FROM deliveryqueues WHERE queuestatus=0 and userid=` + strconv.Itoa(uid) + q1 := `SELECT COUNT(*) as deliverycount FROM deliveries + WHERE orderstatus IN('pending','accepted','arrived','picked') and userid=?` + + db.DB.Raw(q1, uid).Find(&dc) + + return dc, nil + +} + +func CreateBreakLogv1(data models.Riderbreaks) (int, error) { + if db.Rdb == nil { + return 0, errors.New("Redis client not initialized") + } + + id, err := db.Rdb.Incr(db.Ctx, "riderbreaks:counter").Result() + if err != nil { + utils.Logger.Errorf("Redis INCR error: %v", err) + return 0, errors.New("failed to generate break ID") + } + data.Breakid = int(id) + + jsonData, err := json.Marshal(data) + if err != nil { + utils.Logger.Errorf("JSON marshal error: %v", err) + return 0, errors.New("failed to serialize break data") + } + + if err := db.Rdb.RPush(db.Ctx, "riderbreaks", jsonData).Err(); err != nil { + utils.Logger.Errorf("Redis RPush error: %v", err) + return 0, errors.New("failed to store break in Redis") + } + + tx := db.DB.Begin() + if err := updateOnduty(tx, data.Userid); err != nil { + tx.Rollback() + return 0, err + } + if err := tx.Commit().Error; err != nil { + utils.Logger.Errorf("MySQL commit error: %v", err) + return 0, errors.New("failed to update onduty") + } + + return data.Breakid, nil +} + +func updateOnduty(tx *gorm.DB, userid int) error { + // Try update + result := tx.Table("app_userpools"). + Where("userid = ?", userid). + UpdateColumn("onduty", 0) + + if result.Error != nil { + return result.Error + } + + // if result.RowsAffected == 0 { + // // INSERT instead of update + // err := tx.Table("app_userpools").Create(map[string]interface{}{ + // "userid": userid, + // "onduty": 0, + // }).Error + + // if err != nil { + // return errors.New("failed to insert onduty row: " + err.Error()) + // } + // } + + return nil +} + +func GetBreaklogv1(breakID int) models.Riderbreaks { + if db.Rdb == nil { + return models.Riderbreaks{} + } + + vals, err := db.Rdb.LRange(db.Ctx, "riderbreaks", 0, -1).Result() + if err != nil { + utils.Logger.Errorf("Redis LRange error: %v", err) + return models.Riderbreaks{} + } + + var data models.Riderbreaks + for _, v := range vals { + var item models.Riderbreaks + if err := json.Unmarshal([]byte(v), &item); err != nil { + continue + } + if item.Breakid == breakID { + data = item + break + } + } + + return data +} + +func UpdateBreakLogv1(data models.Riderbreaks) error { + key := "riderbreaks" + + values, err := db.Rdb.LRange(db.Ctx, key, 0, -1).Result() + if err != nil { + return errors.New("failed to read Redis list: " + err.Error()) + } + if len(values) == 0 { + return errors.New("no data found in Redis list " + key) + } + + var foundIndex int = -1 + for i, val := range values { + var rb models.Riderbreaks + if err := json.Unmarshal([]byte(val), &rb); err != nil { + continue + } + if rb.Breakid == data.Breakid { + foundIndex = i + break + } + } + + if foundIndex == -1 { + return errors.New("Break ID " + strconv.Itoa(data.Breakid) + " not found in Redis") + } + + jsonData, err := json.Marshal(data) + if err != nil { + return errors.New("Redis marshal failed: " + err.Error()) + } + + if err := db.Rdb.LSet(db.Ctx, key, int64(foundIndex), jsonData).Err(); err != nil { + return errors.New("Redis LSET failed: " + err.Error()) + } + + tx := db.DB.Begin() + t2 := tx.Table("app_userpools"). + Where("userid = ?", data.Userid). + UpdateColumn("onduty", 1) + if t2.Error != nil { + tx.Rollback() + return t2.Error + } + + if err := tx.Commit().Error; err != nil { + return err + } + + return nil +} + +func GetRiderWeeklyKms(uid int) (*models.RiderWeeklyKms, error) { + var result []models.RiderKms + var overallKms float64 + + // 🔥 SAFE + POSTGRES COMPATIBLE QUERY + q1 := ` + SELECT + TRIM(TO_CHAR(deliverydate, 'Dy')) AS day, + ROUND( + COALESCE(SUM(NULLIF(riderkms, '')::numeric), 0), + 2) AS kms + FROM deliveries + WHERE userid = ? + AND deliverydate >= CURRENT_DATE - INTERVAL '7 days' + GROUP BY EXTRACT(DOW FROM deliverydate), TRIM(TO_CHAR(deliverydate, 'Dy')) + ORDER BY EXTRACT(DOW FROM deliverydate); + ` + + q2 := ` + SELECT + ROUND( + COALESCE(SUM(NULLIF(riderkms, '')::numeric), 0), + 2) AS overallkms + FROM deliveries + WHERE userid = ?; + ` + + // Execute queries + if err := db.DB.Raw(q1, uid).Scan(&result).Error; err != nil { + return nil, err + } + + if err := db.DB.Raw(q2, uid).Scan(&overallKms).Error; err != nil { + return nil, err + } + + // Map results + dayMap := make(map[string]float64) + for _, r := range result { + dayMap[r.Day] = r.Kms + } + + // Ensure all 7 days + allDays := []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} + + var final []models.RiderKms + var totalKms float64 + + for _, d := range allDays { + kms := dayMap[d] + final = append(final, models.RiderKms{ + Day: d, + Kms: kms, + }) + totalKms += kms + } + + return &models.RiderWeeklyKms{ + Details: final, + TotalKms: totalKms, + OverallKms: overallKms, + }, nil +} + +func CreateRiderLogv1(data models.Riderlogs) error { + ctx := context.Background() + + pong, err := db.Rdb.Ping(ctx).Result() + utils.Logger.Infof("[Redis] Ping: %s Error: %v", pong, err) + if err != nil { + return errors.New("redis connection error: " + err.Error()) + } + + tx := db.DB.Begin() + + if err := tx.Table("riderlogs").Create(&data).Error; err != nil { + tx.Rollback() + return errors.New("mysql insert error (riderlogs): " + err.Error()) + } + + utils.Logger.Infof("[MySQL] Generated logid: %d", data.Logid) + + if err := tx.Table("app_userpools"). + Where("userid = ?", data.Userid). + UpdateColumn("onduty", 1).Error; err != nil { + tx.Rollback() + return errors.New("mysql update error (onduty): " + err.Error()) + } + + if err := tx.Commit().Error; err != nil { + return errors.New("mysql commit error: " + err.Error()) + } + + jsonData, err := json.Marshal(data) + if err != nil { + return errors.New("failed to marshal redis data: " + err.Error()) + } + + key := "riderlogs" + pushResult := db.Rdb.RPush(ctx, key, jsonData) + if pushResult.Err() != nil { + return errors.New("failed to push rider log to redis: " + pushResult.Err().Error()) + } + + insertedCount, _ := pushResult.Result() + utils.Logger.Infof("[Redis] RPush OK — key=%s, list length now=%d", key, insertedCount) + + val, err := db.Rdb.LRange(ctx, key, -1, -1).Result() + if err != nil { + utils.Logger.Errorf("[Redis] LRange check error: %v", err) + } else { + utils.Logger.Infof("[Redis] Last inserted value: %v", val) + } + + return nil +} + +func UpdateRiderLogv1(data models.Riderlogs) error { + ctx := context.Background() + key := "riderlogs" + + logs, err := db.Rdb.LRange(ctx, key, 0, -1).Result() + if err != nil { + return errors.New("failed to fetch logs from redis: " + err.Error()) + } + + found := false + + for i, item := range logs { + var existing models.Riderlogs + if err := json.Unmarshal([]byte(item), &existing); err != nil { + continue + } + + if existing.Userid == data.Userid { + found = true + + existingVal := reflect.ValueOf(&existing).Elem() + dataVal := reflect.ValueOf(&data).Elem() + + for i := 0; i < dataVal.NumField(); i++ { + field := dataVal.Field(i) + if !isZero(field) { // only update if non-zero + existingVal.Field(i).Set(field) + } + } + + updatedJSON, _ := json.Marshal(existing) + if err := db.Rdb.LSet(ctx, key, int64(i), updatedJSON).Err(); err != nil { + return errors.New("failed to update redis: " + err.Error()) + } + break + } + } + + if !found { + return errors.New("rider log for userid " + strconv.Itoa(data.Userid) + " not found in redis") + } + + tx := db.DB.Begin() + t1 := tx.Table("app_userpools"). + Where("userid = ?", data.Userid). + UpdateColumn("onduty", 0) + if t1.Error != nil { + tx.Rollback() + return errors.New("mysql update error: " + t1.Error.Error()) + } + + if err := tx.Commit().Error; err != nil { + return errors.New("mysql commit error: " + err.Error()) + } + + return nil +} + +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.String: + return v.Len() == 0 + case reflect.Int, reflect.Int64, reflect.Float64: + return v.Int() == 0 + case reflect.Ptr, reflect.Interface: + return v.IsNil() + default: + return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) + } +} + +func CreateRiderSupport(data models.RiderSupport) error { + + tx := db.DB.Begin() + + t1 := tx.Table("ridersupport").Create(&data) + if t1.Error != nil { + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + return err + } + + return nil +} + +func GetRiderSupport(uid int) ([]models.RiderSupport, error) { + + var data []models.RiderSupport + + q1 := "SELECT * FROM ridersupport WHERE userid=? ORDER BY ridersupportid DESC" + + err := db.DB.Raw(q1, uid).Scan(&data).Error + if err != nil { + return data, err + } + + return data, nil +} + +func GetRiderLogsv1(fromdate, todate, keyword string, partnerid int) ([]models.Riderlogsv1, error) { + ctx := context.Background() + results := []models.Riderlogsv1{} + + redisList, err := db.Rdb.LRange(ctx, "riderlogs", 0, -1).Result() + if err != nil { + return nil, errors.New("redis lrange error: " + err.Error()) + } + + if len(redisList) == 0 { + return results, nil + } + + from, _ := time.Parse("2006-01-02", fromdate) + to, _ := time.Parse("2006-01-02", todate) + to = to.Add(24 * time.Hour) + + keyword = strings.ToLower(keyword) + + last := make(map[int]models.Riderlogsv1) + + for _, item := range redisList { + + idx := strings.Index(item, `"logdate":"`) + if idx == -1 { + continue + } + + start := idx + len(`"logdate":"`) + end := start + 19 + if end > len(item) { + continue + } + + logDateStr := item[start:end] + logTime, err := time.Parse("2006-01-02 15:04:05", logDateStr) + if err != nil { + continue + } + + if logTime.Before(from) || logTime.After(to) { + continue + } + + var log models.Riderlogsv1 + if err := json.Unmarshal([]byte(item), &log); err != nil { + continue + } + + if partnerid > 0 && log.Partnerid != partnerid { + continue + } + + if keyword != "" { + if !strings.Contains(strings.ToLower(log.Firstname), keyword) && + !strings.Contains(strings.ToLower(log.Lastname), keyword) && + !strings.Contains(strings.ToLower(log.Username), keyword) { + continue + } + } + + if prev, ok := last[log.Userid]; ok { + prevTime, _ := time.Parse("2006-01-02 15:04:05", prev.Logdate) + if logTime.After(prevTime) { + last[log.Userid] = log + } + } else { + last[log.Userid] = log + } + } + + for _, v := range last { + results = append(results, v) + } + + return results, nil +} + +func GetUserByID(userid int) (models.User, error) { + + var user models.User + + q := `SELECT userid, firstname, lastname FROM app_users WHERE userid=? LIMIT 1` + + err := db.DB.Raw(q, userid).Scan(&user).Error + return user, err +} + +func GetUserCached(userid int) (models.User, error) { + ctx := context.Background() + var user models.User + + key := "user:info:" + strconv.Itoa(userid) + + val, err := db.Rdb.Get(ctx, key).Result() + if err == nil { + json.Unmarshal([]byte(val), &user) + return user, nil + } + + q := `SELECT * + FROM app_users + WHERE userid=? LIMIT 1` + + if err := db.DB.Raw(q, userid).Scan(&user).Error; err != nil { + return user, err + } + + b, _ := json.Marshal(user) + db.Rdb.Set(ctx, key, b, 6*time.Hour) + + return user, nil +} + +func GetAllRidersSummary(aid, pid, uid int, keyword string) models.RidersSummary { + var summary models.RidersSummary + var conditions []string + + const baseQry = ` + SELECT + COUNT(DISTINCT CASE WHEN a.status='Active' THEN a.userid END) AS active, + COUNT(DISTINCT CASE WHEN a.status='Inactive' THEN a.userid END) AS inactive, + COUNT(DISTINCT a.userid) AS total + FROM app_users a + INNER JOIN ridersettings b ON a.userid=b.userid + INNER JOIN ridershifts c ON b.shiftid=c.shiftid + INNER JOIN app_location d ON a.applocationid=d.applocationid + INNER JOIN app_locationconfig e + ON a.applocationid=e.applocationid AND e.status='Active' + WHERE 1=1 + ` + + var params []interface{} + + if aid != 0 { + conditions = append(conditions, "a.applocationid=?") + params = append(params, aid) + } + + if pid != 0 { + conditions = append(conditions, "a.partnerid=?") + params = append(params, pid) + } + + if uid != 0 { + conditions = append(conditions, "a.userid=?") + params = append(params, uid) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + conditions = append(conditions, "(LOWER(a.firstname) LIKE ? OR LOWER(a.lastname) LIKE ? OR LOWER(a.contactno) LIKE ? OR LOWER(CAST(a.userid AS CHAR)) LIKE ?)") + params = append(params, k, k, k, k) + } + + q := baseQry + if len(conditions) > 0 { + q += " AND " + strings.Join(conditions, " AND ") + } + + utils.Logger.Debugf("SUMMARY QUERY Executed") + + db.DB.Raw(q, params...).Scan(&summary) + + return summary +} + +type ActiveRidersSummary struct { + Total int `json:"total"` +} + +func GetActiveRidersSummary(partnerid, aid, uid int, keyword string) ActiveRidersSummary { + var summary ActiveRidersSummary + var conditions []string + + const baseQry = ` + SELECT + COUNT(DISTINCT a.userid) AS total + FROM app_users a + INNER JOIN app_userpools b ON a.userid = b.userid + INNER JOIN ridersettings c ON a.userid = c.userid + INNER JOIN ridershifts d ON c.shiftid = d.shiftid + INNER JOIN ( + SELECT r1.* + FROM riderlogs r1 + INNER JOIN ( + SELECT userid, MAX(logdate) AS max_logdate + FROM riderlogs + WHERE logdate::date = CURRENT_DATE + GROUP BY userid + ) r2 + ON r1.userid = r2.userid AND r1.logdate = r2.max_logdate + ) e ON a.userid = e.userid + INNER JOIN app_location f ON a.applocationid = f.applocationid + INNER JOIN app_locationconfig g ON g.applocationid = f.applocationid + WHERE 1=1 + ` + + conditions = append(conditions, + "a.configid = 6", + "a.status = 'Active'", + "b.onduty = 1", + "e.logdate::date = CURRENT_DATE", + "e.logstatus = 0", + ) + + var params []interface{} + + if aid != 0 { + conditions = append(conditions, "a.applocationid = ?") + params = append(params, aid) + } + if partnerid != 0 { + conditions = append(conditions, "a.partnerid = ?") + params = append(params, partnerid) + } + if uid != 0 { + conditions = append(conditions, "a.userid = ?") + params = append(params, uid) + } + + if keyword != "" { + k := "%" + strings.ToLower(keyword) + "%" + conditions = append(conditions, "(LOWER(a.firstname) LIKE ? OR LOWER(a.lastname) LIKE ? OR LOWER(a.contactno) LIKE ? OR LOWER(CAST(a.userid AS CHAR)) LIKE ?)") + params = append(params, k, k, k, k) + } + + q := baseQry + " AND " + strings.Join(conditions, " AND ") + + utils.Logger.Debugf("ACTIVE SUMMARY QUERY Executed") + + db.DB.Raw(q, params...).Scan(&summary) + return summary +} diff --git a/domain/platform.go b/domain/platform.go new file mode 100644 index 0000000..bd3f7ea --- /dev/null +++ b/domain/platform.go @@ -0,0 +1,34 @@ +package domain + +import ( + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" +) + +func GetModules(mid int) []models.AppModules { + + var data []models.AppModules + + q1 := `SELECT a.moduleid,a.modulename,a.logourl, + (SELECT COUNT(*) FROM tenants WHERE moduleid=a.moduleid) AS business, + (SELECT COUNT(*) FROM deliveries WHERE moduleid=a.moduleid) as orders from app_module a where a.status='Active' order by sortorder asc` + + db.DB.Raw(q1).Find(&data) + + return data +} + +func GetSmsprovider(tid int) models.Smsproviders { + + var data models.Smsproviders + + q1 := `select * from smsproviders where status='Active' and templatetypeid=` + strconv.Itoa(tid) + + db.DB.Raw(q1).Find(&data) + + utils.Logger.Debugf("Query: %s", q1) + + return data +} diff --git a/domain/product.go b/domain/product.go new file mode 100644 index 0000000..de7eea8 --- /dev/null +++ b/domain/product.go @@ -0,0 +1,847 @@ +package domain + +import ( + "errors" + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" + "strings" + "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func GetProductCategory() []models.ProductCategory { + var data []models.ProductCategory + + q1 := `SELECT * FROM productcategories WHERE moduleid = 2 and status = "Active"` + db.DB.Raw(q1).Scan(&data) + + return data +} + +func GetProductSubCategory(categoryID, tenantID int) []models.ProductSubCategory { + var data []models.ProductSubCategory + var query strings.Builder + var args []interface{} + + query.WriteString("SELECT * FROM productsubcategories WHERE 1=1") + + if tenantID != 0 { + query.WriteString(" AND tenantid = ?") + args = append(args, tenantID) + } + if categoryID != 0 { + query.WriteString(" AND categoryid = ?") + args = append(args, categoryID) + } + + db.DB.Raw(query.String(), args...).Scan(&data) + return data +} + +func CreateProduct(product *models.Products) error { + + tx := db.DB.Begin() + + t1 := tx.Create(&product) + if t1.Error != nil { + utils.Error("CreateProduct error", "error", t1.Error) + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func CreateProductVariant(input models.Productvariant) error { + + tx := db.DB.Begin() + + t1 := tx.Create(&input) + if t1.Error != nil { + utils.Error("CreateProductVariant error", "error", t1.Error) + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus, approve string, pageno, pagesize int) ([]models.Tenantproducts, error) { + offset := (pageno - 1) * pagesize + results := make([]models.Tenantproducts, 0) + + if tenantID == 0 { + return results, nil + } + + var tenant models.TenantInfo + err := db.DB.Table("tenants").Where("tenantid = ?", tenantID).First(&tenant).Error + if err != nil { + return nil, err + } + + var products []models.Products + query := db.DB. + Debug(). + Table("products a"). + Select(` + a.*, + c.categoryname, + d.subcatname AS subcategoryname, + e.locationid, + COALESCE( + SUM(CASE WHEN e.stocktype = 'in' THEN e.quantity ELSE 0 END) - + SUM(CASE WHEN e.stocktype = 'out' THEN e.quantity ELSE 0 END), + 0 + ) AS quantity + `). + Joins("LEFT JOIN productcategories c ON a.categoryid = c.categoryid"). + Joins("LEFT JOIN productsubcategories d ON a.subcategoryid = d.subcatid"). + Joins("LEFT JOIN productstocks e ON e.productid = a.productid"). + Where("a.tenantid = ?", tenantID). + Group("a.productid"). // Important for SUM + Order("a.productid DESC") + + if categoryID != 0 { + query = query.Where("a.categoryid = ?", categoryID) + } + if subcategoryID != 0 { + query = query.Where("a.subcategoryid = ?", subcategoryID) + } + if productID != 0 { + query = query.Where("a.productid = ?", productID) + } + if productStatus != "" { + query = query.Where("a.productstatus = ?", productStatus) + } + if locationID != 0 { + query = query.Where("e.locationid = ?", locationID) + } + if approve != "" { + query = query.Where("a.approve = ?", approve) + } + + if keyword != "" { + like := "%" + strings.ToLower(keyword) + "%" + query = query.Where( + db.DB.Where("LOWER(a.productname) LIKE ?", like). + Or("LOWER(a.unitvalue) LIKE ?", like). + Or("LOWER(CAST(a.productcost AS CHAR)) LIKE ?", like), + ) + } + + if pagesize > 0 && offset >= 0 { + query = query.Limit(pagesize).Offset(offset) + } + + err = query.Scan(&products).Error + if err != nil { + return nil, err + } + + if products == nil { + products = []models.Products{} + } + + results = append(results, models.Tenantproducts{ + Tenant: tenant, + Products: products, + }) + + return results, nil +} + +func UpdateProduct(input models.Products) error { + + tx := db.DB.Begin() + + t1 := tx.Where("productid=?", input.Productid).Updates(&input) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func Getproductcount(tenantid, categoryid, subcategory int, approve string) ([]models.Productcount, error) { + var data []models.Productcount + + baseQuery := ` + SELECT + COUNT(*) AS total, + SUM(CASE WHEN a.productstatus = 'available' THEN 1 ELSE 0 END) AS available, + SUM(CASE WHEN a.productstatus = 'outofstock' THEN 1 ELSE 0 END) AS outofstock + FROM products a + WHERE 1 = 1 + ` + + var conditions []string + var params []interface{} + + if tenantid != 0 { + conditions = append(conditions, "a.tenantid = ?") + params = append(params, tenantid) + } + if categoryid != 0 { + conditions = append(conditions, "a.categoryid = ?") + params = append(params, categoryid) + } + if subcategory != 0 { + conditions = append(conditions, "a.subcategoryid = ?") + params = append(params, subcategory) + } + if approve != "" { + conditions = append(conditions, "a.approve = ?") + params = append(params, approve) + } + + if len(conditions) > 0 { + baseQuery += " AND " + strings.Join(conditions, " AND ") + } + + if err := db.DB.Raw(baseQuery, params...).Scan(&data).Error; err != nil { + return nil, err + } + + + return data, nil +} + +func GetproductbyVariant(tenantid, variantid int) ([]models.Products, error) { + + var data []models.Products + + err := db.DB. + Table("products p"). + Select("p.*, c.categoryname, d.subcatname as subcategoryname"). + Joins("LEFT JOIN productcategories c ON p.categoryid = c.categoryid"). + Joins("LEFT JOIN productsubcategories d ON p.subcategoryid = d.subcatid"). + Where("p.tenantid = ? and p.variants = ?", tenantid, variantid). + Order("p.productid desc").Scan(&data).Error + + + if err != nil { + return nil, err + } + + return data, nil + +} + +func GetProductVariants(tenantId int, subcategoryId int) []models.Productvariant { + var data []models.Productvariant + + var query string + var params []interface{} + + query = ` + SELECT a.*, b.categoryname + FROM productvariants a + JOIN app_category b ON a.categoryid = b.categoryid + WHERE a.tenantid = ? + ` + params = append(params, tenantId) + + if subcategoryId != 0 { + query += " AND a.subcategoryid = ?" + params = append(params, subcategoryId) + } + + db.DB.Raw(query, params...).Scan(&data) + + + return data +} + +func GetCatalougeProducts(tenantId, locationid, subcategoryid, pageno, pagesize int, keyword string) []models.Products { + var data []models.Products + + var query string + + if pageno < 1 { + pageno = 1 + } + if pagesize < 1 { + pagesize = 10 + } + + offset := (pageno - 1) * pagesize + + params := []interface{}{locationid, tenantId} + + query = `SELECT a.* FROM products a + LEFT JOIN productlocations b ON a.productid = b.productid + AND b.locationid =? AND b.tenantid = a.tenantid + WHERE a.approve=1 and a.tenantid = ? AND b.productid IS NULL order by a.productid desc LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + if subcategoryid != 0 { + query += " AND a.subcategoryid = ?" + params = append(params, subcategoryid) + } + + if keyword != "" { + query += " AND LOWER(a.productname) LIKE ?" + params = append(params, "%"+strings.ToLower(keyword)+"%") + } + + db.DB.Raw(query, params...).Scan(&data) + + utils.Logger.Debugf("Query: %s", query) + + return data +} + +func GetLocationProducts(tenantid, locationid, subcategoryid, pageno, pagesize int, keyword string) []models.Products { + var data []models.Products + + if pageno < 1 { + pageno = 1 + } + if pagesize < 1 { + pagesize = 10 + } + + offset := (pageno - 1) * pagesize + params := []interface{}{tenantid, locationid} + + query := `SELECT a.*, + COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' THEN c.quantity ELSE 0 END), 0) AS total_in, + COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' THEN c.quantity ELSE 0 END), 0) AS total_out, + COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' THEN c.quantity ELSE 0 END) - + SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' THEN c.quantity ELSE 0 END), 0) AS productstock + FROM products a + INNER JOIN productlocations b + ON a.productid = b.productid AND a.tenantid = b.tenantid + LEFT JOIN productstocks c + ON a.productid = c.productid + AND b.locationid = c.locationid + AND a.tenantid = c.tenantid + WHERE a.approve=1 AND a.tenantid = ? + AND b.locationid = ?` + + if subcategoryid != 0 { + query += " AND a.subcategoryid = ?" + params = append(params, subcategoryid) + } + + if keyword != "" { + query += " AND LOWER(a.productname) LIKE ?" + params = append(params, "%"+strings.ToLower(keyword)+"%") + } + + query += ` GROUP BY a.productid, a.productname, a.productimage, a.categoryid, a.subcategoryid, + a.productunit, a.productcost, a.taxpercent, a.taxamount, a.retailprice, + b.tenantid, b.locationid` + + query += " ORDER BY a.productid DESC LIMIT ? OFFSET ?" + params = append(params, pagesize, offset) + + db.DB.Raw(query, params...).Scan(&data) + + + return data +} + +func GetStockStatement(tenantid, locationid, subcategoryid, pageno, pagesize int, keyword string) []models.Productstockstatement { + data := make([]models.Productstockstatement, 0) + + if pageno < 1 { + pageno = 1 + } + if pagesize < 1 { + pagesize = 10 + } + + offset := (pageno - 1) * pagesize + + params := []interface{}{tenantid, locationid} + + query := `SELECT a.productid,a.productname,a.productimage,a.categoryid,a.subcategoryid,a.productunit,a.unitvalue,a.productcost,a.taxpercent,a.taxamount,a.retailprice,b.tenantid,b.locationid, + COALESCE( SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date <= CURRENT_DATE THEN c.quantity ELSE 0 END) - + SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date <= CURRENT_DATE THEN c.quantity ELSE 0 END),0 ) + AS opening, + COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END), 0) AS credit, + COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date= CURRENT_DATE THEN c.quantity ELSE 0 END), 0) AS debit, + COALESCE( + ( SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END) - + SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END) + ) + + SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END) - + SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END), + 0 + ) AS closing + FROM products a + JOIN productlocations b ON a.productid = b.productid AND a.tenantid = b.tenantid + LEFT JOIN productstocks c ON a.productid = c.productid AND b.locationid = c.locationid AND b.tenantid = c.tenantid + WHERE b.tenantid = ? AND b.locationid = ?` + + if subcategoryid != 0 { + query += " AND a.subcategoryid = ?" + params = append(params, subcategoryid) + } + + if keyword != "" { + query += " AND (CAST(a.productid AS CHAR) LIKE ? OR LOWER(a.productname) LIKE ?)" + likeParam := "%" + strings.ToLower(keyword) + "%" + params = append(params, likeParam, likeParam) + } + + query += ` + GROUP BY + a.productid, a.productname, a.productimage, + a.categoryid, a.subcategoryid, a.productunit, + a.productcost, a.taxpercent, a.taxamount, + a.retailprice, b.tenantid, b.locationid + ORDER BY a.productid DESC LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) + + db.DB.Raw(query, params...).Scan(&data) + + utils.Logger.Debugf("Query: %s", query) + + return data +} + +func CreateProductLocation(input []models.Productlocations) error { + + var stk []models.Productstock + + tx := db.DB.Begin() + + if err := tx.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "tenantid"}, {Name: "locationid"}, {Name: "productid"}}, + DoUpdates: clause.AssignmentColumns([]string{"price", "minquantity", "maxquantity"}), + }).Create(&input).Error; err != nil { + tx.Rollback() + return err + } + + for _, loc := range input { + + if loc.Quantity > 0 { + stk = append(stk, models.Productstock{ + Tenantid: loc.Tenantid, + Stockdate: time.Now(), + Locationid: loc.Locationid, + Productid: loc.Productid, + Quantity: loc.Quantity, + Stocktype: loc.Stocktype, + }) + + } + + } + + if len(stk) > 0 { + t2 := tx.Create(&stk) + if t2.Error != nil { + tx.Rollback() + return t2.Error + } + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func UpdateProductLocation(input models.Productlocations) error { + + tx := db.DB.Begin() + + t1 := tx.Where("productlocationid=?", input.Productlocationid).Updates(&input) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func FetchProductsBySubcategory(categoryID, tenantID, applocationID, locationID, productID int, keyword string) (map[string]interface{}, error) { + utils.Logger.Infof("Domain: Fetching products for categoryID=%d, tenantID=%d", categoryID, tenantID) + + // Step 1: Fetch subcategories + var subcategories []models.Subcategory + err := db.DB.Table("productsubcategories"). + Where("categoryid = ?", categoryID). + Find(&subcategories).Error + if err != nil { + utils.Error("Error fetching subcategories", "error", err) + return nil, err + } + + // Step 2: Build product query + var products []models.Products + query := db.DB.Table("products a"). + Joins("LEFT JOIN productlocations pl ON pl.productid = a.productid"). + Where("a.categoryid = ? AND a.tenantid = ?", categoryID, tenantID) + + if locationID > 0 { + query = query.Where("pl.locationid = ?", locationID) + } + if applocationID > 0 { + query = query.Where("a.applocationid = ?", applocationID) + } + if productID > 0 { + query = query.Where("a.productid = ?", productID) + } + if keyword != "" { + like := "%" + strings.ToLower(keyword) + "%" + query = query.Where( + db.DB.Where("LOWER(a.productname) LIKE ?", like). + Or("LOWER(a.unitvalue) LIKE ?", like). + Or("LOWER(CAST(a.productcost AS CHAR)) LIKE ?", like), + ) + } + + if err := query.Select("a.*").Find(&products).Error; err != nil { + utils.Error("Error fetching products", "error", err) + return nil, err + } + + // Step 3: Grouping + var grouped []models.SubcategoryProductResponse + var uncategorized []models.Products + + for _, subcat := range subcategories { + var matched []models.Products + for _, prod := range products { + if prod.Subcategoryid != nil && *prod.Subcategoryid == subcat.Subcategoryid { + matched = append(matched, prod) + } + } + if len(matched) > 0 { + grouped = append(grouped, models.SubcategoryProductResponse{ + SubcategoryID: subcat.Subcategoryid, + SubcategoryName: subcat.Subcategoryname, + Products: matched, + }) + } + } + + // Add uncategorized + for _, p := range products { + if p.Subcategoryid == nil { + uncategorized = append(uncategorized, p) + } + } + if len(uncategorized) > 0 { + grouped = append(grouped, models.SubcategoryProductResponse{ + SubcategoryID: 0, + SubcategoryName: "Uncategorized", + Products: uncategorized, + }) + } + + // Step 4: Tenant info + var tenantInfo map[string]interface{} + for _, p := range products { + if p.Tenantid != nil { + tenantInfo, err = fetchTenantDetails(*p.Tenantid, applocationID) + if err != nil { + utils.Error("Error fetching tenant details", "error", err) + } + break + } + } + + if tenantInfo == nil { + return map[string]interface{}{}, nil + } + + tenantInfo["details"] = grouped + return tenantInfo, nil +} + +func fetchTenantDetails(tenantID, applocationID int) (map[string]interface{}, error) { + var tenant struct { + Tenantname string + Address string + Licenseno string + Primaryemail string + Primarycontact string + Locationname string + Pickuplocationid int + Suburb string + City string + Latitude string + Longitude string + Postcode string + } + + err := db.DB.Raw(` + SELECT t.tenantname,t.address,t.licenseno,t.primaryemail,t.primarycontact,l.locationid AS pickuplocationid,l.suburb,l.city,l.latitude,l.longitude,l.postcode,a.locationname + FROM tenants t + LEFT JOIN tenantlocations l ON t.tenantid = l.tenantid + LEFT JOIN app_location a ON l.applocationid = a.applocationid + WHERE t.tenantid = ? AND t.applocationid = ? + LIMIT 1 + `, tenantID, applocationID).Scan(&tenant).Error + + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + + return map[string]interface{}{ + "tenantname": tenant.Tenantname, + "address": tenant.Address, + "licenseno": tenant.Licenseno, + "primaryemail": tenant.Primaryemail, + "primarycontact": tenant.Primarycontact, + "locationname": tenant.Locationname, + "pickuplocationid": tenant.Pickuplocationid, + "suburb": tenant.Suburb, + "city": tenant.City, + "pickuplat": tenant.Latitude, + "pickuplong": tenant.Longitude, + "postcode": tenant.Postcode, + }, nil +} + +func UpdateProductStock(stock *models.Productstock) error { + // Ensure the record exists + var existing models.Productstock + if err := db.DB.First(&existing, "productstockid = ?", stock.Productstockid).Error; err != nil { + return err + } + + // Update fields + return db.DB.Model(&existing).Updates(models.Productstock{ + Tenantid: stock.Tenantid, + Stockdate: stock.Stockdate, + Locationid: stock.Locationid, + Productid: stock.Productid, + Quantity: stock.Quantity, + Stocktype: stock.Stocktype, + Status: stock.Status, + }).Error +} + +func GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error) { + var stocks []models.Productstocks + var params []interface{} + var conditions []string + + query := ` + SELECT + a.productid, a.tenantid, MAX(a.stockdate) AS stockdate, a.locationid, a.stocktype, a.maxquantity, a.minquantity, a.status, + b.applocationid, b.categoryid, b.subcategoryid, b.catalogueid, b.addonid, b.discountid, b.pricingid, + b.productname, b.productimage, b.productdesc, b.productsku, b.brandid, b.productbrand, b.productunit, + b.unitvalue, b.toppicks, b.productcost, b.taxamount, b.taxpercent, b.producttax, b.productstock, + b.productcombo, b.variants, b.retailprice, b.diffprice, b.diffpercent, b.othercost, b.approve, + b.productstatus, b.created, b.updated, c.subcatname AS subcategoryname, + SUM(CASE WHEN a.stocktype = 'in' THEN a.quantity ELSE 0 END) - + SUM(CASE WHEN a.stocktype = 'out' THEN a.quantity ELSE 0 END) AS quantity + FROM productstocks a + JOIN products b ON a.productid = b.productid + INNER JOIN productsubcategories c ON c.subcatid = b.subcategoryid + ` + + if tenantID != "" { + conditions = append(conditions, "a.tenantid = ?") + params = append(params, tenantID) + } + + if locationID != "" { + conditions = append(conditions, "a.locationid = ?") + params = append(params, locationID) + } + + if len(conditions) > 0 { + query += " WHERE " + strings.Join(conditions, " AND ") + } + + query += " GROUP BY a.productid" + + if err := db.DB.Raw(query, params...).Scan(&stocks).Error; err != nil { + return nil, err + } + + return stocks, nil +} + +func GetSubCategoryWiseSummary(tenantId, locationid, subcategoryid int) ([]models.SubCategorySummary, error) { + var data []models.SubCategorySummary + + query := ` + SELECT a.subcategoryid AS subcategoryid, c.subcatname AS subcatname, COUNT(a.productid) AS productcount + FROM products a + LEFT JOIN productlocations b ON a.productid = b.productid AND b.locationid = ? AND b.tenantid = a.tenantid + LEFT JOIN productsubcategories c ON a.subcategoryid = c.subcatid + WHERE a.approve = 1 AND a.tenantid = ? AND b.productid IS NULL + ` + + var params []interface{} + params = append(params, locationid, tenantId) + + if subcategoryid > 0 { + query += " AND a.subcategoryid = ?" + params = append(params, subcategoryid) + } + + query += ` + GROUP BY a.subcategoryid, c.subcatname + ORDER BY a.subcategoryid ASC + ` + + if err := db.DB.Raw(query, params...).Scan(&data).Error; err != nil { + return nil, err + } + + return data, nil +} + +func GetLocationProductSummary(tenantid, locationid int) []models.ProductSummary { + data := make([]models.ProductSummary, 0) + + params := []interface{}{tenantid, locationid} + + query := ` + SELECT a.subcatid AS subcategoryid, a.subcatname AS subcategroyname, a.image, COUNT(DISTINCT b.productid) AS productcount + FROM productsubcategories a + LEFT JOIN products b ON a.subcatid = b.subcategoryid AND b.approve = 1 AND b.tenantid = ? + LEFT JOIN productlocations c ON b.productid = c.productid AND b.tenantid = c.tenantid AND c.locationid = ? + WHERE a.categoryid = 2 + GROUP BY a.subcatid, a.subcatname + ORDER BY a.subcatid; + ` + + db.DB.Raw(query, params...).Scan(&data) + + total := 0 + for _, d := range data { + total += d.Productcount + } + + all := models.ProductSummary{ + Subcategoryid: 0, + Subcategroyname: "All", + Productcount: total, + } + data = append([]models.ProductSummary{all}, data...) + + return data +} + +func GetStockStatementSummary(tenantid, locationid int) []models.ProductSummary { + data := make([]models.ProductSummary, 0) + + params := []interface{}{tenantid, locationid} + + query := ` + SELECT a.subcatid AS subcategoryid, a.subcatname AS subcategroyname, a.image, COUNT(DISTINCT b.productid) AS productcount + FROM productsubcategories a + LEFT JOIN products b ON a.subcatid = b.subcategoryid AND b.tenantid = ? + LEFT JOIN productlocations c ON b.productid = c.productid AND b.tenantid = c.tenantid AND c.locationid = ? + WHERE a.categoryid = 2 + GROUP BY a.subcatid, a.subcatname + ORDER BY a.subcatid; + ` + + db.DB.Raw(query, params...).Scan(&data) + + utils.Logger.Debugf("Query: %s", query) + + // Add "All" row + total := models.ProductSummary{ + Subcategoryid: 0, + Subcategroyname: "All", + } + for _, d := range data { + total.Productcount += d.Productcount + } + data = append([]models.ProductSummary{total}, data...) + + return data +} + +func CreateProductDiscount(discount *models.ProductDiscount) error { + + // convert []string to comma-separated string + if len(discount.Locationid) > 0 { + discount.LocationidStr = strings.Join(discount.Locationid, ",") + } + + tx := db.DB.Begin() + + if err := tx.Table("productdiscounts").Create(discount).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +func GetProductDiscounts(tid int, lid string) []models.ProductDiscount { + + var result []models.ProductDiscount + + query := ` + SELECT + discountid, discounttypeid, tenantid, productid, moduleid, + locationid, discountname, discountcode, + discountterms, discountvalue, startdate, enddate, status + FROM productdiscounts + WHERE tenantid = ? + AND moduleid = 2 + AND FIND_IN_SET(?, locationid) + ` + + db.DB.Raw(query, tid, lid).Scan(&result) + + // convert comma separated location ids to []string + for i := range result { + if result[i].LocationidStr != "" { + result[i].Locationid = strings.Split(result[i].LocationidStr, ",") + } else { + result[i].Locationid = []string{} + } + } + + return result +} diff --git a/domain/tenant.go b/domain/tenant.go new file mode 100644 index 0000000..e6c81a0 --- /dev/null +++ b/domain/tenant.go @@ -0,0 +1,591 @@ +package domain + +import ( + "errors" + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" + "strings" + + "github.com/jinzhu/copier" +) + +func CreateTenantUser(data models.Tenants) (bool, error) { + + var seq models.Ordersequences + var user models.User + var cust models.Customers + var custloc models.Customerlocations + var tcust models.Tenantcustomers + + tx := db.DB.Begin() + + t1 := tx.Create(&data) + if t1.Error != nil { + + tx.Rollback() + return false, errors.New("error in tenant creation") + } + + seq.Tenantid = data.Tenantid + + t2 := tx.Table("ordersequences").Create(&seq) + if t2.Error != nil { + + tx.Rollback() + return false, errors.New("error in sequence") + } + + if err := copier.Copy(&user, &data); err != nil { + return false, err + } + + user.Userfcmtoken = data.Tenanttoken + user.Contactno = data.Primarycontact + user.Email = data.Primaryemail + user.Authname = data.Primaryemail + user.Deviceid = data.Deviceid + user.Tenantid = data.Tenantid + user.Locationid = data.Tenantlocations.Locationid + user.Roleid = 1 + + t3 := tx.Table("app_users").Create(&user) + if t3.Error != nil { + + tx.Rollback() + return false, errors.New("error in user creation") + } + + cust.Configid = data.Configid + cust.Firstname = data.Tenantname + cust.Email = data.Primaryemail + cust.Contactno = data.Primarycontact + cust.Deviceid = data.Deviceid + cust.Devicetype = data.Devicetype + cust.Customertoken = data.Tenanttoken + cust.Profileimage = data.Tenantimage + cust.Address = data.Address + cust.Suburb = data.Suburb + cust.City = data.City + cust.State = data.State + cust.Postcode = data.Postcode + cust.Applocationid = data.Applocationid + cust.Latitude = data.Latitude + cust.Longitude = data.Longitude + cust.Primaryaddress = 1 + + cid := CheckCustomer(data.Primarycontact) + + if cid == 0 { + + t4 := tx.Table("customers").Create(&cust) + if t4.Error != nil { + + tx.Rollback() + return false, errors.New("error in customer creation") + } + + if err := copier.Copy(&custloc, &cust); err != nil { + return false, err + } + + t5 := tx.Table("customerlocations").Create(&custloc) + if t5.Error != nil { + + tx.Rollback() + return false, errors.New("error in customer location") + } + + } else { + + t1 := tx.Table("customers").Where("customerid=?", cid).Updates(&cust) + if t1.Error != nil { + + tx.Rollback() + return false, errors.New("error in customer creation") + } + + if err := copier.Copy(&custloc, &cust); err != nil { + return false, err + } + + t2 := tx.Table("customerlocations").Where("customerid=?", cid).Updates(&custloc) + if t2.Error != nil { + + tx.Rollback() + return false, errors.New("error in customer location") + } + + } + + tcust.Customerid = cust.Customerid + tcust.Tenantid = data.Tenantid + tcust.Locationid = data.Tenantlocations.Locationid + + t6 := tx.Table("tenantcustomers").Create(&tcust) + if t6.Error != nil { + + tx.Rollback() + return false, errors.New("error in tenant customer") + } + + err := tx.Commit().Error + + if err != nil { + + return false, errors.New("error in tenant creation") + + } + + return true, nil + +} + +func UpdateTenant(input models.Tenants) error { + + tx := db.DB.Begin() + + t1 := tx.Where("tenantid=?", input.Tenantid).Updates(&input) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func UpdateLocation(input models.Tenantlocations) error { + + tx := db.DB.Begin() + + t1 := tx.Where("locationid=?", input.Locationid).Updates(&input) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func CreateLocation(data models.Tenantlocations) error { + + var cust models.Customers + var tcust models.Tenantcustomers + var custloc models.Customerlocations + + tx := db.DB.Begin() + + t1 := tx.Create(&data) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + cust.Firstname = data.Locationname + cust.Email = data.Email + cust.Contactno = data.Contactno + cust.Address = data.Address + cust.Suburb = data.Suburb + cust.City = data.City + cust.State = data.State + cust.Postcode = data.Postcode + cust.Applocationid = data.Applocationid + cust.Latitude = data.Latitude + cust.Longitude = data.Longitude + cust.Primaryaddress = 0 + + t2 := tx.Table("customers").Create(&cust) + if t2.Error != nil { + + tx.Rollback() + return t2.Error + } + + if err := copier.Copy(&custloc, &cust); err != nil { + return err + } + + t3 := tx.Table("customerlocations").Create(&custloc) + if t3.Error != nil { + + tx.Rollback() + return t3.Error + } + + tcust.Customerid = cust.Customerid + tcust.Tenantid = data.Tenantid + tcust.Locationid = data.Locationid + tcust.Moduleid = data.Moduleid + + t4 := tx.Table("tenantcustomers").Create(&tcust) + if t4.Error != nil { + + tx.Rollback() + return t4.Error + } + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil + +} + +func Checktenantbyno(cno string) int { + + var id int + + q1 := "select tenantid from tenants where primarycontact='" + cno + `'` + + db.DB.Raw(q1).Find(&id) + return id + +} + +func GetTeanantById(tid int) models.Tenantinfo { + + var data models.Tenantinfo + + // q1 := `SELECT a.*,b.categoryid,b.subcategoryid,c.subcategoryname,b.moduleid,d.locationid,d.locationname FROM + // tenants a + // INNER JOIN tenantsubscriptions b ON a.tenantid=b.tenantid + // INNER JOIN app_subcategory c ON c.subcategoryid=b.subcategoryid + // INNER JOIN tenantlocations d ON d.tenantid=a.tenantid + // where a.tenantid=?` + + q1 := `SELECT a.*,b.categoryname,c.locationname as applocation,d.allocationid as allocationmode,e.typename AS allocationtype,e.mapid as allocationid,f.locationid, + f.locationname, g.slab, g.pricingdate, g.baseprice, g.minkm, g.priceperkm, g.maxkm, g.orders, g.othercharges, g.surgecharges + FROM tenants a + INNER JOIN app_category b ON a.categoryid=b.categoryid + INNER JOIN app_location c ON a.applocationid=c.applocationid + LEFT JOIN partnerinfo d ON a.partnerid=d.partnerid + LEFT JOIN app_types e ON d.allocationid=e.apptypeid + LEFT JOIN tenantlocations f ON a.tenantid=f.tenantid + LEFT JOIN tenantpricing g ON a.tenantid=g.tenantid + where a.tenantid=?` + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1, tid).Find(&data) + + return data + +} + +func GetTeanantPricing(tid, aid int) models.Tenantpricing { + + var data models.Tenantpricing + var q1 string + + if tid != 0 { + + q1 = `SELECT * FROM tenantpricing WHERE pricingdate = (SELECT MAX(pricingdate) FROM tenantpricing where tenantid=` + strconv.Itoa(tid) + `) AND tenantid=? order by tenantpricingid desc` + + } + + utils.Logger.Debugf("Query: %s", q1) + + db.DB.Raw(q1, tid).Find(&data) + + return data + +} + +func GetPricinglist(tid int) []models.Tenantpricing { + + var data []models.Tenantpricing + + q1 := `SELECT * + FROM tenantpricing + WHERE tenantid=? order by pricingdate desc` + + db.DB.Raw(q1, tid).Find(&data) + + return data + +} + +func Getalltenants(pageno, pagesize, aid, moduleid int, status, tenanttype, keyword string) []models.Tenantinfo { + + offset := (pageno - 1) * pagesize + + var data []models.Tenantinfo + + base := ` + SELECT a.*, b.categoryname, c.slab, c.pricingdate, c.baseprice, c.minkm, + c.priceperkm, c.maxkm, c.orders, + c.othercharges, c.surgecharges + FROM tenants a + LEFT JOIN app_category b ON a.categoryid = b.categoryid + LEFT JOIN ( + SELECT DISTINCT ON (tenantid) * + FROM tenantpricing + ORDER BY tenantid, pricingdate DESC + ) c ON a.tenantid = c.tenantid + WHERE 1 = 1 + ` + + var ( + conds []string + params []interface{} + ) + + switch strings.ToLower(status) { + case "active": + conds = append(conds, "a.approved = 1 AND a.status = 'Active'") + case "inactive": + conds = append(conds, "a.approved = 1 AND a.status = 'InActive'") + case "pending": + conds = append(conds, "a.approved = 0") + } + + if aid != 0 { + conds = append(conds, "a.applocationid = ?") + params = append(params, aid) + } + + if moduleid != 0 { + conds = append(conds, "a.moduleid = ?") + params = append(params, moduleid) + } + + if tenanttype != "" { + conds = append(conds, "a.tenanttype = ?") + params = append(params, tenanttype) + } + + if keyword != "" { + kw := "%" + strings.ToLower(keyword) + "%" + conds = append(conds, + "(LOWER(a.tenantname) LIKE ? OR LOWER(a.primarycontact) LIKE ?)") + params = append(params, kw, kw) + } + + if len(conds) > 0 { + base += " AND " + strings.Join(conds, " AND ") + } + + base += " ORDER BY a.tenantid DESC LIMIT ? OFFSET ?" + params = append(params, pagesize, offset) + + print(base, params) + db.DB.Raw(base, params...).Find(&data) + + return data +} + +func GetCustomerLocations(customerID int) models.CustomerTenantResponse { + var customer models.Customers + + // get customer + db.DB.Table("customers"). + Where("customerid = ?", customerID). + First(&customer) + + // get locations + locations := []models.LocationDetails{} + + query := ` + SELECT + c.locationid,c.locationname,c.tenantid,b.tenantname,c.address,c.email,c.contactno,c.applocationid,c.suburb,c.city,c.latitude,c.longitude,c.postcode + FROM tenantcustomers a + Left join tenants b ON a.tenantid = b.tenantid + LEFT JOIN tenantlocations c ON a.locationid = c.locationid + WHERE a.customerid = ? + ` + + db.DB.Raw(query, customerID).Scan(&locations) + + // print(customer, locations) + + return models.CustomerTenantResponse{ + Customer: customer, + Tenantlocations: locations, + } +} + +func CreateTenantLocation(data models.Tenantlocations) error { + var user models.Tenantuser + + tx := db.DB.Begin() + + // 🔧 Set status BEFORE insert + data.Status = "InActive" + + // Step 1: Insert into tenantlocations + if err := tx.Create(&data).Error; err != nil { + tx.Rollback() + return err + } + + // Step 2: Insert into app_users + user.Authname = data.Email + user.Firstname = data.Locationname + user.Email = data.Email + user.Contactno = data.Contactno + user.Address = data.Address + user.Suburb = data.Suburb + user.City = data.City + user.State = data.State + user.Postcode = data.Postcode + user.Partnerid = data.Partnerid + user.Tenantid = data.Tenantid + user.Locationid = data.Locationid + user.Applocationid = data.Applocationid + user.Configid = 1 + user.Status = "InActive" + user.Roleid = 0 + user.Authmode = 0 + user.Password = "" + user.Dialcode = "+91" + + if err := tx.Table("app_users").Create(&user).Error; err != nil { + tx.Rollback() + return err + } + + // Commit + if err := tx.Commit().Error; err != nil { + return err + } + + return nil +} + +func UpdateTenantLocation(input models.Tenantlocations) error { + tx := db.DB.Begin() + + t1 := tx.Where("locationid=?", input.Locationid).Updates(&input) + if t1.Error != nil { + + tx.Rollback() + return t1.Error + } + + err := tx.Commit().Error + if err != nil { + + return err + + } + + return nil +} + +func FetchLocationSummary(tenantID int) models.LocationSummary { + var summary models.LocationSummary + + db.DB.Raw(` + SELECT + SUM(CASE WHEN status = 'Active' THEN 1 ELSE 0 END) AS active_count, + SUM(CASE WHEN status = 'InActive' THEN 1 ELSE 0 END) AS inactive_count, + COUNT(*) AS total_count + FROM tenantlocations + WHERE tenantid = ? + `, tenantID).Scan(&summary) + + return summary +} + +func FetchTenantStaffSummary(tenantID int) models.TenantUserSummary { + var summary models.TenantUserSummary + + query := ` + SELECT + SUM(CASE WHEN a.status = 'Active' THEN 1 ELSE 0 END) AS active_count, + SUM(CASE WHEN b.status = 'InActive' THEN 1 ELSE 0 END) AS inactive_count, + COUNT(*) AS total_count + FROM tenantstaffs a + JOIN app_users b ON a.userid = b.userid + WHERE a.tenantid = ? AND b.roleid = 2 + ` + + db.DB.Raw(query, tenantID).Scan(&summary) + return summary +} + +func FetchTenantRiderSummary(tenantID int) models.TenantUserSummary { + var summary models.TenantUserSummary + + query := ` + SELECT + SUM(CASE WHEN a.status = 'Active' THEN 1 ELSE 0 END) AS active_count, + SUM(CASE WHEN a.status = 'InActive' THEN 1 ELSE 0 END) AS inactive_count, + COUNT(*) AS total_count + FROM tenantstaffs a + JOIN app_users b ON a.userid = b.userid + WHERE a.tenantid = ? AND b.roleid = 3 + ` + + db.DB.Raw(query, tenantID).Scan(&summary) + return summary +} + +func CreateTenantPromotion(promo *models.Tenantpromotions) error { + + // convert []string to comma-separated string + if len(promo.Locationid) > 0 { + promo.LocationidStr = strings.Join(promo.Locationid, ",") + } + + tx := db.DB.Begin() + + if err := tx.Table("tenantpromotions").Create(promo).Error; err != nil { + tx.Rollback() + return err + } + + return tx.Commit().Error +} + +func GetTenantPromotions(tid int, lid string) []models.Tenantpromotions { + + var result []models.Tenantpromotions + + query := ` + SELECT + promotionid, promotiontypeid, tenantid, + locationid, applocationid, moduleid, categoryid, + promoname, promocode, description, product, productimage, + promoamount, promovalue, purchasevalue, startdate, enddate + FROM tenantpromotions + WHERE tenantid = ? + AND moduleid = 2 + AND FIND_IN_SET(?, locationid) + ` + + db.DB.Raw(query, tid, lid).Scan(&result) + + // Convert location string → slice + for i := range result { + if result[i].LocationidStr != "" { + result[i].Locationid = strings.Split(result[i].LocationidStr, ",") + } else { + result[i].Locationid = []string{} + } + } + + return result +} diff --git a/domain/userDomain.go b/domain/userDomain.go new file mode 100644 index 0000000..181adc3 --- /dev/null +++ b/domain/userDomain.go @@ -0,0 +1,457 @@ +package domain + +import ( + "nearle/db" + "nearle/models" + "nearle/utils" + "strconv" + "strings" +) + +func Getuserbyid(uid int) models.UserInfo { + + var user models.UserInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno, + a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname,a.password,a.address,a.suburb,a.city,a.state,a.postcode, + a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.shiftid, + a.applocationid,b.locationname as applocation,b.latitude as applatitude,b.longitude as applongitude, b.radius as appradius , concat(c.starttime, ' - ', c.endtime) as shiftname + from app_users a + INNER JOIN app_location b on a.applocationid=b.applocationid + LEFT JOIN ridershifts c ON a.shiftid = c.shiftid + WHERE a.userid= ` + strconv.Itoa(uid) + + // print(q1) + + db.DB.Raw(q1).Find(&user) + + return user + +} + +func GetAllUsers( + roleID, configID, tenantID, pageno, pagesize int, + status, keyword string, +) []models.UserInfo { + + var users []models.UserInfo + var params []interface{} + var queryBuilder strings.Builder + + offset := (pageno - 1) * pagesize + + queryBuilder.WriteString(` + SELECT + a.userid, a.authname, a.email, a.configid, a.roleid, a.authmode, a.contactno, + a.firstname, a.lastname, CONCAT(a.firstname, ' ', a.lastname) AS fullname, + a.address, a.suburb, a.city, a.state, a.postcode, + a.userfcmtoken, a.pin, a.deviceid, a.devicetype, + a.tenantid, a.status, a.shiftid, a.latitude, a.longitude,d.rolename, + + STRING_AGG( + CONCAT( + ac.applocationid, '::', + al.locationname, '::', + ac.status + )::text, ',' + ) AS applocations_raw + + FROM app_users a + LEFT JOIN app_locationconfig ac ON ac.userid = a.userid + LEFT JOIN app_location al ON al.applocationid = ac.applocationid + LEFT JOIN app_roles d ON a.roleid = d.roleid + WHERE 1=1 + `) + + if roleID != 0 { + queryBuilder.WriteString(" AND a.roleid = ?") + params = append(params, roleID) + } + if configID != 0 { + queryBuilder.WriteString(" AND a.configid = ?") + params = append(params, configID) + } + if tenantID != 0 { + queryBuilder.WriteString(" AND a.tenantid = ?") + params = append(params, tenantID) + } + if status != "" { + queryBuilder.WriteString(" AND a.status = ?") + params = append(params, status) + } + if keyword != "" { + queryBuilder.WriteString(` + AND ( + LOWER(a.firstname) LIKE ? OR + LOWER(a.contactno) LIKE ? OR + LOWER(a.suburb) LIKE ? + ) + `) + search := "%" + strings.ToLower(keyword) + "%" + params = append(params, search, search, search) + } + + queryBuilder.WriteString(` + GROUP BY a.userid, d.rolename + ORDER BY a.userid DESC + LIMIT ? OFFSET ? + `) + + params = append(params, pagesize, offset) + + // ---------- TEMP STRUCT ---------- + type tempUser struct { + models.UserInfo + ApplocationsRaw string `gorm:"column:applocations_raw"` + } + + var temp []tempUser + db.DB.Raw(queryBuilder.String(), params...).Scan(&temp) + + // ---------- MAP RESULT ---------- + for _, t := range temp { + + t.UserInfo.Applocations = []models.AppLocation{} + + if t.ApplocationsRaw != "" { + items := strings.Split(t.ApplocationsRaw, ",") + for _, item := range items { + parts := strings.Split(item, "::") + if len(parts) == 3 { + id, err := strconv.Atoi(parts[0]) + if err == nil { + t.UserInfo.Applocations = append( + t.UserInfo.Applocations, + models.AppLocation{ + Applocationid: id, + Applocationname: parts[1], + Status: parts[2], + }, + ) + } + } + } + } + + users = append(users, t.UserInfo) + } + + return users +} + +func GetTenantUserbyId(uid int) models.TenantUserInfo { + + var user models.TenantUserInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno, + a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname, + a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.locationid,a.applocationid, + b.partnerid,b.moduleid,b.categoryid as categoryid,b.subcategoryid as subcategoryid, + b.applocationid,b.tenantname,b.address as tenantaddress,b.state as tenantstate,b.city as tenantcity, + b.postcode as tenantpostcode,b.latitude as tenantlat,b.longitude as tenantlong,c.locationname AS applocation, + c.latitude as applatitude,c.longitude as applongitude,c.radius as appradius, d.categoryname, e.locationname + from app_users a + LEFT JOIN tenants b ON a.tenantid=b.tenantid + INNER JOIN app_location c on c.applocationid=b.applocationid + LEFT JOIN app_category d ON b.categoryid=d.categoryid + LEFT JOIN tenantlocations e ON a.locationid=e.locationid + WHERE a.userid= ` + strconv.Itoa(uid) + + // print(q1) + + db.DB.Raw(q1).Find(&user) + + return user + +} + +func GetTenantId(uid int, contactno string) int { + + var tid int + var q1 string + + if contactno != "" { + + q1 = `select tenantid from tenants WHERE primarycontact='` + contactno + `'` + + } else { + + q1 = `select primarycontact from tenants WHERE primarycontact='` + contactno + `'` + + } + + db.DB.Raw(q1).Find(&tid) + + return tid + +} + +func Getuserbyno(cno string) models.UserInfo { + + var user models.UserInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno, + a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname, + a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.locationid, + b.partnerid,b.moduleid,b.categoryid as categoryid,b.subcategoryid as subcategoryid, + b.applocationid,b.tenantname,b.address as tenantaddress,b.state as tenantstate,b.city as tenantcity, + b.postcode as tenantpostcode,b.latitude as tenantlat,b.longitude as tenantlong + from app_users a + LEFT JOIN tenants b ON a.tenantid=b.tenantid + WHERE a.contactno= '` + cno + `'` + + db.DB.Raw(q1).Find(&user) + + return user + +} + +func GetPartnerUserbyid(uid int) models.UserInfo { + + var user models.UserInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname, + a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.applocationid,a.partnerid, + b.locationname AS applocation,b.latitude AS applatitude,b.longitude AS applongitude,b.radius AS appradius, b.deliveryradius, c.partnername + from app_users a + INNER JOIN app_location b ON a.applocationid=b.applocationid + LEFT JOIN partnerinfo c ON a.partnerid=c.partnerid + WHERE a.userid= ` + strconv.Itoa(uid) + + db.DB.Raw(q1).Find(&user) + + return user + +} + +func GetRiderUserbyid(uid int) models.RiderInfo { + + var user models.RiderInfo + + q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno, a.address,a.state,a.city,a.postcode,a.latitude,a.longitude, + a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as username,a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.partnerid,a.applocationid, a.tenantid, a.locationid, a.shiftid, + b.starttime, b.endtime, b.shifthours, b.firstmilecharge, b.fuelcharge, c.logid, d.onduty, e.logseconds, e.deliveryradius + from app_users a + LEFT JOIN ridershifts b ON a.shiftid=b.shiftid + LEFT JOIN riderlogs c ON a.userid=c.userid + LEFT JOIN app_userpools d on a.userid=d.userid + LEFT JOIN app_location e on a.applocationid=e.applocationid + WHERE a.userid= ` + strconv.Itoa(uid) + + db.DB.Raw(q1).Find(&user) + + utils.Logger.Debugf("Query: %s", q1) + + return user + +} + +func GetAdminUserbyid(uid int) models.User { + + var user models.User + + q1 := `SELECT a.*,b.rolename from app_users a LEFT JOIN app_roles b ON a.roleid=b.roleid where a.userid= ` + strconv.Itoa(uid) + + db.DB.Raw(q1).Find(&user) + + return user + +} + +func UpdatUser(user models.User) { + + db.DB.Table("app_users").Where("userid=?", user.Userid).Updates(&user) + + if user.Tenantid != 0 { + + db.DB.Table("tenants").Where("tenantid=?", user.Tenantid).Update("tenanttoken", user.Userfcmtoken) + + } + +} + +func GetUserIDByContact(contact string, configID int) int { + var uid int + query := `SELECT userid FROM app_users WHERE contactno = ? AND configid = ?` + db.DB.Raw(query, contact, configID).Scan(&uid) + return uid +} + +func GetUserIDByAuth(authname string, configID int) int { + var uid int + query := `SELECT userid FROM app_users WHERE authname = ? AND configid = ?` + db.DB.Raw(query, authname, configID).Scan(&uid) + return uid +} +func GetUserStatus(userid int) string { + var status string + db.DB.Raw("SELECT status FROM app_users WHERE userid = ?", userid).Scan(&status) + return status +} + +func GetStoredPin(uid int) int { + var pin int + db.DB.Raw(`SELECT COALESCE(pin, 0) FROM app_users WHERE userid = ?`, uid).Scan(&pin) + return pin +} + +func GetAuthMode(uid int) int { + var mode int + db.DB.Raw(`SELECT COALESCE(authmode, 1) FROM app_users WHERE userid = ?`, uid).Scan(&mode) + return mode +} + +func UpdateUserFcmToken(uid int, token string) error { + query := `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?` + return db.DB.Exec(query, token, uid).Error +} + +// Check if device already exists for user +func IsDeviceLinked(userid int, deviceid string) bool { + var count int64 + db.DB.Table("user_devices"). + Where("userid = ? AND deviceid = ?", userid, deviceid). + Count(&count) + return count > 0 +} + +func GetStoredDeviceID(uid int) (string, error) { + var deviceID string + query := `SELECT deviceid FROM app_users WHERE userid = ? LIMIT 1` + + result := db.DB.Raw(query, uid).Scan(&deviceID) + + if result.Error != nil { + utils.Error("Error fetching deviceid", "error", result.Error) + return "", result.Error + } + + // If nothing found + if result.RowsAffected == 0 { + return "", nil + } + + return deviceID, nil +} + +func CheckUserExists(contactno, email string, configid int) (bool, error) { + + var count int64 + + err := db.DB.Table("app_users"). + Where("configid = ?", configid). + Where( + db.DB. + Where("contactno = ? AND ? != ''", contactno, contactno). + Or("email = ? AND ? != ''", email, email), + ). + Count(&count).Error + + if err != nil { + return false, err + } + + return count > 0, nil +} + +func GetAllUsersv2(roleID, configID, tenantID, pageno, pagesize int, keyword string) []models.UserInfov2 { + + var users []models.UserInfov2 + var params []interface{} + var queryBuilder strings.Builder + + offset := (pageno - 1) * pagesize + + queryBuilder.WriteString(` + SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,a.firstname, + a.lastname,CONCAT(a.firstname, ' ', a.lastname) AS fullname,a.address,a.suburb, + a.city,a.state,a.postcode,a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid, + a.status,a.shiftid, + + STRING_AGG(DISTINCT alc.applocationid::text, ',') AS applocationids_raw, + + STRING_AGG(DISTINCT d.locationname, ',') AS applocationnames_raw, + + CONCAT(c.starttime, ' - ', c.endtime) AS shiftname + + FROM app_users a + LEFT JOIN app_locationconfig alc + ON alc.userid = a.userid + AND alc.configid = a.configid + AND alc.status = 'Active' + LEFT JOIN app_location d + ON d.applocationid = alc.applocationid + LEFT JOIN ridershifts c + ON a.shiftid = c.shiftid + WHERE 1=1 + `) + + if roleID != 0 { + queryBuilder.WriteString(" AND a.roleid = ?") + params = append(params, roleID) + } + + if configID != 0 { + queryBuilder.WriteString(" AND a.configid = ?") + params = append(params, configID) + } + + if tenantID != 0 { + queryBuilder.WriteString(" AND a.tenantid = ?") + params = append(params, tenantID) + } + + if keyword != "" { + queryBuilder.WriteString(` + AND ( + LOWER(a.firstname) LIKE ? OR + LOWER(a.contactno) LIKE ? OR + LOWER(a.suburb) LIKE ? + ) + `) + search := "%" + strings.ToLower(keyword) + "%" + params = append(params, search, search, search) + } + + queryBuilder.WriteString(` + GROUP BY a.userid, c.starttime, c.endtime + ORDER BY a.userid DESC + LIMIT ? OFFSET ? + `) + + params = append(params, pagesize, offset) + + // 🔹 Execute query + db.DB.Raw(queryBuilder.String(), params...).Scan(&users) + + // 🔹 Convert RAW → API arrays + for i := range users { + + // ✅ always initialize → [] not null + users[i].Applocationids = []int{} + users[i].Applocationnames = []string{} + + // IDs + if users[i].ApplocationidsRaw != "" { + ids := strings.Split(users[i].ApplocationidsRaw, ",") + for _, id := range ids { + if v, err := strconv.Atoi(id); err == nil { + users[i].Applocationids = append(users[i].Applocationids, v) + } + } + } + + // Names + if users[i].ApplocationnamesRaw != "" { + names := strings.Split(users[i].ApplocationnamesRaw, ",") + for _, name := range names { + users[i].Applocationnames = append( + users[i].Applocationnames, + strings.TrimSpace(name), + ) + } + } + } + + return users +} diff --git a/domain/utilsDomain.go b/domain/utilsDomain.go new file mode 100644 index 0000000..b29faf9 --- /dev/null +++ b/domain/utilsDomain.go @@ -0,0 +1,727 @@ +package domain + +import ( + "context" + "errors" + "nearle/db" + "nearle/models" + "nearle/utils" + "net/http" + "strconv" + + firebase "firebase.google.com/go" + "firebase.google.com/go/messaging" + "google.golang.org/api/option" + "gorm.io/gorm" +) + +var ( + firebaseApp *firebase.App + fcmClient *messaging.Client +) + +func getFCMClient() (*messaging.Client, error) { + if fcmClient != nil { + return fcmClient, nil + } + + opt := option.WithCredentialsFile("nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json") + app, err := firebase.NewApp(context.Background(), nil, opt) + if err != nil { + return nil, errors.New("error initializing firebase app: " + err.Error()) + } + firebaseApp = app + + client, err := app.Messaging(context.Background()) + if err != nil { + return nil, errors.New("error getting messaging client: " + err.Error()) + } + fcmClient = client + return fcmClient, nil +} + +func GetApplocations(aid int) []models.Applocations { + + var data []models.Applocations + var q1 string + + if aid != 0 { + + q1 = "Select * from app_location where status='Active' and applocationid=?" + db.DB.Raw(q1, aid).Find(&data) + + } else { + + q1 = "Select * from app_location where status='Active'" + db.DB.Raw(q1).Find(&data) + + } + + utils.Logger.Debugf("Query executed for aid: %d", aid) + + return data + +} + +func GetApplocationsv2(userid int) []models.Applocations { + + var data []models.Applocations + var q1 string + + if userid != 0 { + q1 = ` + SELECT b.* + FROM app_locationconfig a + INNER JOIN app_location b + ON a.applocationid = b.applocationid + WHERE b.status = 'Active' + AND a.userid = ?` + db.DB.Raw(q1, userid).Find(&data) + } else { + q1 = ` + SELECT * + FROM app_location + WHERE status = 'Active' + ` + db.DB.Raw(q1).Find(&data) + } + + utils.Logger.Debugf("Query executed for userid: %d", userid) + return data +} + +func GetApplocationConfig(aid int) models.Applocations { + + var data models.Applocations + var q1, q2 string + + if aid != 0 { + + q1 = `Select * from app_location where status='Active' and applocationid=` + strconv.Itoa(aid) + q2 = `SELECT distinct a.userid,a.userfcmtoken,b.notify,b.applocationid,c.opentime,c.closetime + FROM app_users a + INNER JOIN app_locationconfig b ON a.userid=b.userid + INNER JOiN app_location c on a.applocationid= c.applocationid + WHERE b.notify='true' AND b.applocationid=` + strconv.Itoa(aid) + + } else { + + q1 = `Select * from app_location where status='Active'` + + } + + // print(q1) + + // db.DB.Raw(q1).Preload("Applocationadmins").Find(&data) + + db.DB.Raw(q1).Preload("Applocationadmins", func(db *gorm.DB) *gorm.DB { + return db.Raw(q2) + }).Find(&data) + + return data + +} + +func GetAppPricing(aid, cid, pid int) []models.Apppricing { + + var data []models.Apppricing + var q1 string + + if aid != 0 { + + q1 = `SELECT a.* FROM app_pricing a + JOIN ( + SELECT pricingtypeid, MAX(pricingdate) AS maxpricingdate + FROM app_pricing + WHERE applocationid = ? + GROUP BY pricingtypeid + ) AS maxpricing ON a.pricingtypeid = maxpricing.pricingtypeid AND a.pricingdate = maxpricing.maxpricingdate WHERE a.applocationid = ?` + + db.DB.Raw(q1, aid, aid).Find(&data) + + } else if cid != 0 { + + q1 = `SELECT * FROM app_pricing WHERE pricingdate = (SELECT MAX(pricingdate) FROM app_pricing where configid= ?)` + db.DB.Raw(q1, cid).Find(&data) + + } + + // print(q1) + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func CreateAppProcing(input models.Apppricing) bool { + + err := db.DB.Table("app_pricing").Create(&input).Error + + return err == nil +} + +func GetAllAppPricing(aid int) []models.Apppricing { + + var data []models.Apppricing + var q1 string + + if aid != 0 { + + q1 = `SELECT a.*,b.locationname as applocation, d.appname FROM app_pricing a + inner join app_location b on a.applocationid=b.applocationid + Inner Join app_types c on a.pricingtypeid=c.apptypeid + INNER JOIN app_config d ON a.configid=d.configid + where a.applocationid=? ORDER BY b.locationname ASC` + + db.DB.Raw(q1, aid).Find(&data) + + } else { + + q1 = `SELECT a.*,b.locationname as applocation, d.appname FROM app_pricing a + inner join app_location b on a.applocationid=b.applocationid + Inner Join app_types c on a.pricingtypeid=c.apptypeid + INNER JOIN app_config d ON a.configid=d.configid ORDER BY b.locationname ASC` + + db.DB.Raw(q1).Find(&data) + + } + + utils.Logger.Debugf("Query executed for aid: %d", aid) + + return data + +} + +func GetApplocationadmins(aid int) []models.Applocationadmins { + + var data []models.Applocationadmins + var q1 string + + if aid != 0 { + + q1 = `SELECT distinct a.userid,a.userfcmtoken,b.notify,b.applocationid,c.opentime,c.closetime + FROM app_users a + INNER JOIN app_locationconfig b ON a.userid=b.userid + INNER JOiN app_location c on a.applocationid= c.applocationid + WHERE b.notify='true' AND b.applocationid=?` + + db.DB.Raw(q1, aid).Find(&data) + + } + + return data + +} + +func GetAppconfig(cid int) models.Appconfig { + + var data models.Appconfig + var q1 string + + if cid != 0 { + + q1 = `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius, + b.providerid,b.providerapi,b.providerkey + FROM app_config a + LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid + WHERE a.configid=?` + db.DB.Raw(q1, cid).Find(&data) + + } else { + + q1 = `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius, + b.providerid,b.providerapi,b.providerkey FROM app_config a + LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid order by configid asc` + db.DB.Raw(q1).Find(&data) + + } + utils.Logger.Debugf("Query executed for cid: %d", cid) + + return data + +} + +func GetAllAppconfig() []models.Appconfig { + + var data []models.Appconfig + + q1 := `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius, + b.providerid,b.providerapi,b.providerkey FROM app_config a + LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid order by configid asc` + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetAppModule() []models.AppModule { + + var data []models.AppModule + + q1 := `SELECT * FROM app_module where status='Active'` + + db.DB.Raw(q1).Find(&data) + + return data + +} + +func GetApptypes(tag string) []models.Apptypes { + + var data []models.Apptypes + + q1 := "Select * from app_types where status ='Active' and tag=?" + db.DB.Raw(q1, tag).Find(&data) + + return data + +} + +func GetSubcategories(moduleid int, categoryid int) []models.Appsubcategories { + var data []models.Appsubcategories + + query := ` + SELECT a.subcategoryid,a.categoryid,a.subcategoryname,b.categoryname,a.status,c.moduleid + FROM app_subcategory a + INNER JOIN app_category b ON a.categoryid = b.categoryid + INNER JOIN app_module c ON a.categoryid = c.categoryid + WHERE 1=1` + + var params []interface{} + + if moduleid != 0 { + query += " AND c.moduleid = ?" + params = append(params, moduleid) + } + + if categoryid != 0 { + query += " AND a.categoryid = ?" + params = append(params, categoryid) + } + + db.DB.Raw(query, params...).Scan(&data) + return data +} + +func GetCategories(mid int) []models.AppCategory { + var data []models.AppCategory + + if mid == 0 { + // No moduleid filter → return all categories + q := ` + SELECT a.*, b.moduleid + FROM app_category a + INNER JOIN app_module b ON a.categoryid = b.categoryid + ` + db.DB.Raw(q).Scan(&data) + } else { + // Filter by moduleid + q := ` + SELECT a.*, b.moduleid + FROM app_category a + INNER JOIN app_module b ON a.categoryid = b.categoryid + WHERE b.moduleid = ? + ` + db.DB.Raw(q, mid).Scan(&data) + } + + return data +} + +func Insertnotification(input models.Data) bool { + + db.DB.Table("tenantnotifications").Create(&input) + + return true +} + +func SendLoginNotification(regid, accessid string, pin int) error { + if regid == "" { + return errors.New("no FCM token provided") + } + + client, err := getFCMClient() + if err != nil { + return err + } + + // Construct the notification payload + body := "Your passcode to Nearle app is: " + strconv.Itoa(pin) + "." + title := "Nearle Code" + + // Prepare FCM message + message := &messaging.Message{ + Notification: &messaging.Notification{ + Title: title, + Body: body, + }, + Token: regid, + Android: &messaging.AndroidConfig{ + Priority: "high", + Notification: &messaging.AndroidNotification{ + Sound: "ring", + }, + }, + } + + // Send the FCM notification + response, err := client.Send(context.Background(), message) + if err != nil { + utils.Logger.Errorf("FCM send error: %v", err) + return errors.New("failed to send FCM: " + err.Error()) + } + + utils.Logger.Infof("Successfully sent message: %v", response) + return nil +} + +func SendSingleNotification(regid, accessid string, Partner string) bool { + + client, err := getFCMClient() + if err != nil { + utils.Logger.Errorf("getFCMClient error: %v", err) + return false + } + + title := "Nearle Admin" + body := "Orders had been placed for delivery by " + Partner + + message := &messaging.Message{ + Notification: &messaging.Notification{ + Title: title, + Body: body, + }, + Token: regid, + Android: &messaging.AndroidConfig{ + Priority: "high", + Notification: &messaging.AndroidNotification{ + Sound: "ring", + }, + }, + } + + response, err := client.Send(context.Background(), message) + if err != nil { + utils.Logger.Errorf("Error sending single notification: %v", err) + return false + } + + utils.Logger.Infof("Successfully sent single message: %v", response) + return true +} + +func NotifyUser(fcm models.NotifyUser) bool { + + client, err := getFCMClient() + if err != nil { + utils.Logger.Errorf("getFCMClient error: %v", err) + return false + } + + message := &messaging.Message{ + Notification: &messaging.Notification{ + Title: fcm.Notification.Notify.Title, + Body: fcm.Notification.Notify.Body, + }, + Token: fcm.Notification.To, + Android: &messaging.AndroidConfig{ + Priority: fcm.Notification.Priority, + Notification: &messaging.AndroidNotification{ + Sound: fcm.Notification.Notify.Sound, + }, + }, + } + + response, err := client.Send(context.Background(), message) + if err != nil { + utils.Logger.Errorf("Error notifying user: %v", err) + return false + } + + utils.Logger.Infof("Successfully notified user: %v", response) + return true +} + +func SendNotifications(fcm models.Notifications) bool { + + client, err := getFCMClient() + if err != nil { + utils.Logger.Errorf("getFCMClient error: %v", err) + return false + } + + successCount := 0 + for _, token := range fcm.Registrationids { + if token == "" { + continue + } + message := &messaging.Message{ + Notification: &messaging.Notification{ + Title: fcm.Notify.Title, + Body: fcm.Notify.Body, + }, + Token: token, + Android: &messaging.AndroidConfig{ + Priority: fcm.Priority, + Notification: &messaging.AndroidNotification{ + Sound: fcm.Notify.Sound, + }, + }, + } + + _, err := client.Send(context.Background(), message) + if err != nil { + utils.Logger.Errorf("Error sending notification to token %s: %v", token, err) + continue + } + successCount++ + } + + utils.Logger.Infof("Successfully sent %d notifications out of %d", successCount, len(fcm.Registrationids)) + return successCount > 0 +} + +func SendNotification(fcm models.Notifications, accessid string) models.Result { + + var result models.Result + + client, err := getFCMClient() + if err != nil { + utils.Logger.Errorf("getFCMClient error: %v", err) + result.Code = http.StatusInternalServerError + result.Message = "Error initializing FCM client" + result.Status = false + return result + } + + // Assuming we send to the first registration ID if multiple provided + if len(fcm.Registrationids) == 0 { + result.Code = http.StatusBadRequest + result.Message = "No registration IDs provided" + result.Status = false + return result + } + + message := &messaging.Message{ + Notification: &messaging.Notification{ + Title: fcm.Notify.Title, + Body: fcm.Notify.Body, + }, + Token: fcm.Registrationids[0], + Android: &messaging.AndroidConfig{ + Priority: fcm.Priority, + Notification: &messaging.AndroidNotification{ + Sound: fcm.Notify.Sound, + }, + }, + } + + response, err := client.Send(context.Background(), message) + if err != nil { + utils.Logger.Errorf("Error sending single notification: %v", err) + result.Code = http.StatusInternalServerError + result.Message = "Error sending notification: " + err.Error() + result.Status = false + return result + } + + utils.Logger.Infof("Successfully sent notification: %v", response) + + status := Insertnotification(fcm.Data) + if !status { + result.Code = http.StatusConflict + result.Message = "Notification sent but Insert UnSuccessfully" + result.Status = false + return result + } + + result.Code = http.StatusCreated + result.Message = "Notification Sent and Inserted Successfully" + result.Status = true + return result +} + +func Getconfigs(categoryid int) models.Appconfig { + var q1 string + var data models.Appconfig + + q1 = `SELECT a.configid,a.appname,a.smsproviderid,COALESCE(a.fcmkey,'') AS fcmkey,COALESCE(a.clientid,'') AS clientid,COALESCE(a.googleapikey,'') AS googleapikey,COALESCE(a.paymentdevkey,'') AS paymentdevkey,COALESCE(a.paymentlivekey,'') AS paymentlivekey,a.applocationradius,COALESCE(b.providername,'') AS providername,COALESCE(b.providerapi,'') AS providerapi,COALESCE(b.providerkey,'') AS providerkey, COALESCE(b.username,'') AS username,COALESCE(b.passcode,'') AS passcode,COALESCE(b.defaultprovider,false) AS defaultprovider + FROM app_config a LEFT OUTER JOIN smsproviders b ON a.smsproviderid = b.providerid AND b.status='Active' WHERE a.configid=?` + + db.DB.Raw(q1, categoryid).Find(&data) + + return data +} + +func UpdateSeqno(tid int, prefix string) error { + var field string + + if prefix == "ORD" { + field = "orderseqno" + } else if prefix == "INV" { + field = "invoiceseqno" + } else { + return errors.New("invalid prefix: " + prefix) + } + + result := db.DB. + Table("ordersequences"). + Where("tenantid = ?", tid). + Update(field, gorm.Expr(field+" + 1")) + + if result.Error != nil { + utils.Logger.Errorf("UpdateSeqno failed for tenantid %d, prefix %s: %v", tid, prefix, result.Error) + return result.Error + } + + if result.RowsAffected == 0 { + utils.Logger.Infof("No rows updated for tenantid %d with prefix %s", tid, prefix) + return errors.New("no rows updated for tenantid " + strconv.Itoa(tid)) + } + + return nil +} + +func GetSequenceno(tid int, prefix string) string { + type SeqResult struct { + Orderseqno string + } + + var q1 string + if prefix == "ORD" { + q1 = ` + SELECT COALESCE(CONCAT(tenantid, '-', subprefix, COALESCE(MAX(orderseqno) + 1, 1)), '') AS orderseqno + FROM ordersequences + WHERE tenantid = ? + GROUP BY tenantid, subprefix + ` + } else if prefix == "INV" { + q1 = ` + SELECT COALESCE(CONCAT(tenantid, '-', subprefix, COALESCE(MAX(invoiceseqno) + 1, 1)), '') AS orderseqno + FROM ordersequences + WHERE tenantid = ? + GROUP BY tenantid, subprefix + ` + } + + var result SeqResult + db.DB.Raw(q1, tid).Scan(&result) + return result.Orderseqno +} + +func GetTenantNotifications(tenantid, locationid, customerid, moduleid, pageno, pagesize int) ([]models.TenantNotification, int) { + + var data []models.TenantNotification + var total int + + offset := (pageno - 1) * pagesize + + baseQuery := db.DB.Table("tenantnotifications"). + Select("*"). + Where("tenantid = ?", tenantid) + + if locationid != 0 { + baseQuery = baseQuery.Where("locationid = ?", locationid) + } + if customerid != 0 { + baseQuery = baseQuery.Where("customerid = ?", customerid) + } + if moduleid != 0 { + baseQuery = baseQuery.Where("moduleid = ?", moduleid) + } + + err := baseQuery. + Order("notificationdate DESC"). + Limit(pagesize). + Offset(offset). + Find(&data).Error + + if err != nil { + utils.Logger.Errorf("DB Error GetTenantNotifications: %v", err) + } + + return data, total +} + +func GetUserBonusSummary(userid int) (models.Riderinfo, error) { + + var user models.Riderinfo + query := ` + SELECT COALESCE(SUM(a.bonuspts), 0) AS bonuspts,b.* + FROM deliveries a + LEFT JOIN app_users b ON a.userid = b.userid + WHERE a.userid = ? + GROUP BY b.userid + ` + + err := db.DB.Raw(query, userid).Scan(&user).Error + if err != nil { + return user, err + } + + return user, nil +} + +func CreateAppLocationConfig(input models.AppLocationConfigRequest) bool { + + if input.Notify == "" { + input.Notify = "true" + } + if input.Status == "" { + input.Status = "Active" + } + + for _, locID := range input.Applocationids { + + data := models.AppLocationConfig{ + Applocationid: locID, + Configid: input.Configid, + Userid: input.Userid, + Partnerid: input.Partnerid, + Notify: input.Notify, + Status: input.Status, + } + + if err := db.DB.Table("app_locationconfig").Create(&data).Error; err != nil { + return false + } + } + + return true +} + +func UpdateAppLocationConfig(input models.AppLocationConfigRequest) bool { + + if input.Status == "" { + input.Status = "Active" + } + + for _, locID := range input.Applocationids { + + if err := db.DB.Table("app_locationconfig"). + Where("userid = ? AND applocationid = ?", input.Userid, locID). + Update("status", input.Status).Error; err != nil { + utils.Logger.Errorf("Error updating app_locationconfig: %v", err) + return false + } + } + + return true +} + +func GetUserRoles() ([]models.UserRoles, error) { + + var roles []models.UserRoles + + query := `SELECT * FROM app_roles + ` + + err := db.DB.Raw(query).Scan(&roles).Error + if err != nil { + return roles, err + } + + return roles, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..44e7b87 --- /dev/null +++ b/go.mod @@ -0,0 +1,142 @@ +module nearle + +go 1.24.0 + +require ( + firebase.google.com/go v3.13.0+incompatible + github.com/apache/arrow/go/v14 v14.0.2 + github.com/aws/aws-sdk-go-v2 v1.41.1 + github.com/aws/aws-sdk-go-v2/config v1.32.7 + github.com/aws/aws-sdk-go-v2/credentials v1.19.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 + github.com/joho/godotenv v1.5.1 + github.com/redis/go-redis/v9 v9.16.0 + go.uber.org/zap v1.27.1 + golang.org/x/oauth2 v0.34.0 + google.golang.org/api v0.260.0 + gorm.io/gorm v1.31.1 +) + +require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.121.6 // indirect + cloud.google.com/go/auth v0.18.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/firestore v1.20.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/longrunning v0.7.0 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect + cloud.google.com/go/storage v1.56.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/apache/thrift v0.22.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect + github.com/aws/smithy-go v1.24.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/google/flatbuffers v25.9.23+incompatible // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.9 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/asmfmt v1.3.2 // indirect + github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.51.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.40.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.78.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect +) + +require ( + github.com/gofiber/fiber/v2 v2.52.10 + github.com/jinzhu/copier v0.4.0 + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/spf13/viper v1.17.0 + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6b018cb --- /dev/null +++ b/go.sum @@ -0,0 +1,772 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.20.0 h1:JLlT12QP0fM2SJirKVyu2spBCO8leElaW0OOtPm6HEo= +cloud.google.com/go/firestore v1.20.0/go.mod h1:jqu4yKdBmDN5srneWzx3HlKrHFWFdlkgjgQ6BKIOFQo= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= +cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI= +cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= +firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= +github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= +github.com/aws/aws-sdk-go-v2/config v1.32.7 h1:vxUyWGUwmkQ2g19n7JY/9YL8MfAIl7bTesIUykECXmY= +github.com/aws/aws-sdk-go-v2/config v1.32.7/go.mod h1:2/Qm5vKUU/r7Y+zUk/Ptt2MDAEKAfUtKc1+3U1Mo3oY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1 h1:C2dUPSnEpy4voWFIq3JNd8gN0Y5vYGDo44eUE58a/p8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.1/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13 h1:gd84Omyu9JLriJVCbGApcLzVR3XtmC4ZDPcAI6Ftvds= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.13/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= +github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY= +github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU= +github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= +github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs= +go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523 h1:H52Mhyrc44wBgLTGzq6+0cmuVuF3LURCSXsLMOqfFos= +golang.org/x/telemetry v0.0.0-20251208220230-2638a1023523/go.mod h1:ArQvPJS723nJQietgilmZA+shuB3CZxH1n2iXq9VSfs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= +google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/backup/mysql_to_parquet.go b/internal/backup/mysql_to_parquet.go new file mode 100644 index 0000000..703070d --- /dev/null +++ b/internal/backup/mysql_to_parquet.go @@ -0,0 +1,156 @@ +package backup + +import ( + "database/sql" + "os" + "time" + + "github.com/apache/arrow/go/v14/arrow" + "github.com/apache/arrow/go/v14/arrow/array" + "github.com/apache/arrow/go/v14/arrow/memory" + "github.com/apache/arrow/go/v14/parquet" + "github.com/apache/arrow/go/v14/parquet/compress" + "github.com/apache/arrow/go/v14/parquet/pqarrow" +) + +func BackupDeliveryLogs(sqlDB *sql.DB) (string, error) { + + rows, err := sqlDB.Query(` + SELECT + logid, logdate, deliveryid, orderheaderid, + tenantid, locationid, userid, partnerid, + orderid, orderstatus, latitude, longitude, + logstatus, created, updated, firstname, lastname + FROM deliverylogs + WHERE logdate >= NOW() - INTERVAL '3 months' + `) + if err != nil { + return "", err + } + defer rows.Close() + + // Arrow schema (exact match with MySQL table) + schema := arrow.NewSchema([]arrow.Field{ + {Name: "logid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "logdate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + {Name: "deliveryid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "orderheaderid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "tenantid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "locationid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "userid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "partnerid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "orderid", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "orderstatus", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "latitude", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "longitude", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "logstatus", Type: arrow.PrimitiveTypes.Int32}, + {Name: "created", Type: arrow.FixedWidthTypes.Timestamp_ms}, + {Name: "updated", Type: arrow.FixedWidthTypes.Timestamp_ms}, + {Name: "firstname", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "lastname", Type: arrow.BinaryTypes.String, Nullable: true}, + }, nil) + + pool := memory.NewGoAllocator() + builder := array.NewRecordBuilder(pool, schema) + defer builder.Release() + + for rows.Next() { + + var ( + logid, deliveryid, orderheaderid int32 + tenantid, locationid, userid int32 + partnerid, logstatus int32 + + logdate, created, updated sql.NullTime + orderid, orderstatus sql.NullString + latitude, longitude sql.NullString + firstname, lastname sql.NullString + ) + + if err := rows.Scan( + &logid, &logdate, &deliveryid, &orderheaderid, + &tenantid, &locationid, &userid, &partnerid, + &orderid, &orderstatus, &latitude, &longitude, + &logstatus, &created, &updated, &firstname, &lastname, + ); err != nil { + return "", err + } + + builder.Field(0).(*array.Int32Builder).Append(logid) + + if logdate.Valid { + builder.Field(1).(*array.TimestampBuilder). + Append(arrow.Timestamp(logdate.Time.UnixMilli())) + } else { + builder.Field(1).(*array.TimestampBuilder).AppendNull() + } + + builder.Field(2).(*array.Int32Builder).Append(deliveryid) + builder.Field(3).(*array.Int32Builder).Append(orderheaderid) + builder.Field(4).(*array.Int32Builder).Append(tenantid) + builder.Field(5).(*array.Int32Builder).Append(locationid) + builder.Field(6).(*array.Int32Builder).Append(userid) + builder.Field(7).(*array.Int32Builder).Append(partnerid) + + appendString(builder.Field(8), orderid) + appendString(builder.Field(9), orderstatus) + appendString(builder.Field(10), latitude) + appendString(builder.Field(11), longitude) + + builder.Field(12).(*array.Int32Builder).Append(logstatus) + + builder.Field(13).(*array.TimestampBuilder). + Append(arrow.Timestamp(created.Time.UnixMilli())) + builder.Field(14).(*array.TimestampBuilder). + Append(arrow.Timestamp(updated.Time.UnixMilli())) + + appendString(builder.Field(15), firstname) + appendString(builder.Field(16), lastname) + } + + record := builder.NewRecord() + defer record.Release() + + _ = os.MkdirAll("backups/parquet/deliverylogs", 0755) + + filePath := "backups/parquet/deliverylogs/deliverylogs_last_3_months_" + + time.Now().Format("2006_01_02") + ".parquet" + + file, err := os.Create(filePath) + if err != nil { + return "", err + } + defer file.Close() + + // ✅ Correct Parquet + Arrow writer props (Arrow v14) + parquetProps := parquet.NewWriterProperties( + parquet.WithCompression(compress.Codecs.Snappy), + ) + + writer, err := pqarrow.NewFileWriter( + schema, + file, + parquetProps, + pqarrow.ArrowWriterProperties{}, // zero-value is valid + ) + if err != nil { + return "", err + } + defer writer.Close() + + if err := writer.Write(record); err != nil { + return "", err + } + + return filePath, nil +} + +// helper for nullable strings +func appendString(b array.Builder, v sql.NullString) { + sb := b.(*array.StringBuilder) + if v.Valid { + sb.Append(v.String) + } else { + sb.AppendNull() + } +} diff --git a/internal/backup/mysql_to_parquet_deliveries.go b/internal/backup/mysql_to_parquet_deliveries.go new file mode 100644 index 0000000..edf49dc --- /dev/null +++ b/internal/backup/mysql_to_parquet_deliveries.go @@ -0,0 +1,764 @@ +package backup + +import ( + "database/sql" + "os" + "time" + + "github.com/apache/arrow/go/v14/arrow" + "github.com/apache/arrow/go/v14/arrow/array" + "github.com/apache/arrow/go/v14/arrow/memory" + "github.com/apache/arrow/go/v14/parquet" + "github.com/apache/arrow/go/v14/parquet/compress" + "github.com/apache/arrow/go/v14/parquet/pqarrow" +) + +func BackupDeliveries(sqlDB *sql.DB) (string, error) { + + rows, err := sqlDB.Query(` + SELECT DISTINCT a.deliveryid,a.applocationid,f.locationname AS applocation,a.orderheaderid,a.configid,a.tenantid,a.partnerid,a.locationid,a.userid,a.categoryid,a.subcategoryid,a.moduleid, + a.orderid,a.deliverydate,a.orderstatus,a.assigntime,a.starttime,a.arrivaltime,a.pickuptime,a.deliverytime,a.canceltime,a.acceptedtime, + a.customerid,a.pickupcustomer,a.pickupcontactno,a.pickuplocationid,a.pickupaddress,a.pickuplocation,a.pickuplat,a.pickuplon, + a.deliverycustomerid,a.deliverycustomer,a.deliverycontactno,a.deliverylocationid,a.deliveryaddress,a.deliverylocation, + a.droplat,a.droplon,a.deliverylat,a.deliverylong, + a.riderslat,a.riderslon,a.deliveryamt,a.kms,a.actualkms,a.riderkms,a.deliverycharges,a.deliverytype,a.paymenttype,a.smsdelivery,a.quantity,a.collectionamt,a.collectedamt,a.collectionstatus, + a.notes,a.ordernotes,a.step,a.eta,a.previouskms,a.cumulativekms,a.ridertime,b.tenantname,b.primarycontact as tenantcontactno,b.tenanttoken,b.suburb as tenantsuburb,b.city as tenantcity, + b.address AS tenantaddress, CONCAT(c.firstname, ' ', c.lastname) AS ridername,c.userfcmtoken,c.contactno as ridercontact,e.locationname,e.suburb AS locationsuburb,e.contactno AS locationcontactno,e.address AS locationaddress, + h.slab, h.pricingdate, h.baseprice, h.minkm, h.priceperkm, h.maxkm, h.orders, h.othercharges, h.surgecharges + FROM deliveries a + INNER JOIN tenants b ON a.tenantid=b.tenantid + INNER JOIN app_users c ON a.userid=c.userid + INNER JOIN tenantlocations e ON a.locationid=e.locationid + INNER JOIN app_location f ON a.applocationid = f.applocationid + INNER JOIN app_locationconfig g ON f.applocationid = g.applocationid + LEFT JOIN tenantpricing h ON a.tenantid = h.tenantid + WHERE a.deliverydate >= NOW() - INTERVAL '3 months' + `) + if err != nil { + return "", err + } + defer rows.Close() + + // ✅ REAL schema (no placeholders) + schema := arrow.NewSchema([]arrow.Field{ + {Name: "deliveryid", Type: arrow.PrimitiveTypes.Int32}, + + {Name: "applocationid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "applocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "orderheaderid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "configid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "tenantid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "partnerid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "locationid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "userid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "categoryid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "subcategoryid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "moduleid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + + {Name: "orderid", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverydate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + {Name: "orderstatus", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "assigntime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "starttime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "arrivaltime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuptime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverytime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "canceltime", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "acceptedtime", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "customerid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "pickupcustomer", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickupcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplocationid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "pickupaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplon", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "deliverycustomerid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "deliverycustomer", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverycontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylocationid", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "deliveryaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "droplat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "droplon", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylong", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "riderslat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "riderslon", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliveryamt", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "kms", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "actualkms", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "riderkms", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverycharges", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "deliverytype", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "paymenttype", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "smsdelivery", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "quantity", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "collectionamt", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "collectedamt", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "collectionstatus", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + + {Name: "notes", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "ordernotes", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "step", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "eta", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "previouskms", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "cumulativekms", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "ridertime", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + + {Name: "tenantname", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenanttoken", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantsuburb", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantcity", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "ridername", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "userfcmtoken", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "ridercontact", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "locationname", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationsuburb", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + + {Name: "slab", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pricingdate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + {Name: "baseprice", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "minkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "priceperkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "maxkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "orders", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "othercharges", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "surgecharges", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + }, nil) + + pool := memory.NewGoAllocator() + builder := array.NewRecordBuilder(pool, schema) + defer builder.Release() + + for rows.Next() { + + var ( + deliveryid int32 + orderheaderid, configid, applocationid sql.NullInt32 + tenantid, locationid, partnerid, moduleid, userid sql.NullInt32 + categoryid, subcategoryid sql.NullInt32 + orderid, orderstatus, assigntime, starttime sql.NullString + arrivaltime, pickuptime, acceptedtime, deliverytime sql.NullString + canceltime sql.NullString + + customerid, pickuplocationid sql.NullInt32 + pickupcustomer, pickupaddress, pickuplocation sql.NullString + pickupcontactno, pickuplat, pickuplon sql.NullString + deliverycustomerid, deliverylocationid sql.NullInt32 + deliverycustomer, deliverycontactno, deliveryaddress sql.NullString + deliverylocation, droplat, droplon sql.NullString + deliverylat, deliverylong, riderslat, riderslon sql.NullString + + kms, riderkms, actualkms sql.NullString + deliverycharges, deliveryamt sql.NullFloat64 + notes, deliverytype, ordernotes sql.NullString + paymenttype sql.NullInt32 + + smsdelivery sql.NullInt32 + previouskms, cumulativekms, step, quantity sql.NullInt32 + collectionamt, collectedamt sql.NullFloat64 + collectionstatus sql.NullInt32 + eta sql.NullString + ridertime sql.NullInt32 + deliverydate sql.NullTime + + applocation sql.NullString + + tenantname, tenantcontactno, tenanttoken sql.NullString + tenantsuburb, tenantcity, tenantaddress sql.NullString + + ridername, userfcmtoken, ridercontact sql.NullString + + locationname, locationsuburb sql.NullString + locationcontactno, locationaddress sql.NullString + + slab sql.NullString + pricingdate sql.NullTime + baseprice, minkm, priceperkm, maxkm sql.NullFloat64 + pricingorders sql.NullInt32 + othercharges, surgecharges sql.NullFloat64 + ) + + if err := rows.Scan( + &deliveryid, &applocationid, &applocation, + &orderheaderid, &configid, &tenantid, &partnerid, + &locationid, &userid, &categoryid, &subcategoryid, &moduleid, + + &orderid, &deliverydate, &orderstatus, + &assigntime, &starttime, &arrivaltime, &pickuptime, + &deliverytime, &canceltime, &acceptedtime, + + &customerid, &pickupcustomer, &pickupcontactno, + &pickuplocationid, &pickupaddress, &pickuplocation, + &pickuplat, &pickuplon, + + &deliverycustomerid, &deliverycustomer, &deliverycontactno, + &deliverylocationid, &deliveryaddress, &deliverylocation, + &droplat, &droplon, &deliverylat, &deliverylong, + + &riderslat, &riderslon, + &deliveryamt, &kms, &actualkms, &riderkms, + &deliverycharges, &deliverytype, &paymenttype, + &smsdelivery, &quantity, &collectionamt, + &collectedamt, &collectionstatus, + + ¬es, &ordernotes, &step, &eta, + &previouskms, &cumulativekms, &ridertime, + + &tenantname, &tenantcontactno, &tenanttoken, + &tenantsuburb, &tenantcity, &tenantaddress, + + &ridername, &userfcmtoken, &ridercontact, + + &locationname, &locationsuburb, &locationcontactno, &locationaddress, + + &slab, &pricingdate, &baseprice, &minkm, + &priceperkm, &maxkm, &pricingorders, + &othercharges, &surgecharges, + ); err != nil { + return "", err + } + + // ✅ Append safely + builder.Field(0).(*array.Int32Builder).Append(deliveryid) + + if applocationid.Valid { + builder.Field(1).(*array.Int32Builder).Append(applocationid.Int32) + } else { + builder.Field(1).(*array.Int32Builder).AppendNull() + } + + if applocation.Valid { + builder.Field(2).(*array.StringBuilder).Append(applocation.String) + } else { + builder.Field(2).(*array.StringBuilder).AppendNull() + } + + if orderheaderid.Valid { + builder.Field(3).(*array.Int32Builder).Append(orderheaderid.Int32) + } else { + builder.Field(3).(*array.Int32Builder).AppendNull() + } + + if configid.Valid { + builder.Field(4).(*array.Int32Builder).Append(configid.Int32) + } else { + builder.Field(4).(*array.Int32Builder).AppendNull() + } + + if tenantid.Valid { + builder.Field(5).(*array.Int32Builder).Append(tenantid.Int32) + } else { + builder.Field(5).(*array.Int32Builder).AppendNull() + } + + if partnerid.Valid { + builder.Field(6).(*array.Int32Builder).Append(partnerid.Int32) + } else { + builder.Field(6).(*array.Int32Builder).AppendNull() + } + + if locationid.Valid { + builder.Field(7).(*array.Int32Builder).Append(locationid.Int32) + } else { + builder.Field(7).(*array.Int32Builder).AppendNull() + } + + if userid.Valid { + builder.Field(8).(*array.Int32Builder).Append(userid.Int32) + } else { + builder.Field(8).(*array.Int32Builder).AppendNull() + } + + if categoryid.Valid { + builder.Field(9).(*array.Int32Builder).Append(categoryid.Int32) + } else { + builder.Field(9).(*array.Int32Builder).AppendNull() + } + + if subcategoryid.Valid { + builder.Field(10).(*array.Int32Builder).Append(subcategoryid.Int32) + } else { + builder.Field(10).(*array.Int32Builder).AppendNull() + } + + if moduleid.Valid { + builder.Field(11).(*array.Int32Builder).Append(moduleid.Int32) + } else { + builder.Field(11).(*array.Int32Builder).AppendNull() + } + + if orderid.Valid { + builder.Field(12).(*array.StringBuilder).Append(orderid.String) + } else { + builder.Field(12).(*array.StringBuilder).AppendNull() + } + + if deliverydate.Valid { + builder.Field(13).(*array.TimestampBuilder). + Append(arrow.Timestamp(deliverydate.Time.UnixMilli())) + } else { + builder.Field(13).(*array.TimestampBuilder).AppendNull() + } + + if orderstatus.Valid { + builder.Field(14).(*array.StringBuilder).Append(orderstatus.String) + } else { + builder.Field(14).(*array.StringBuilder).AppendNull() + } + + if assigntime.Valid { + builder.Field(15).(*array.StringBuilder).Append(assigntime.String) + } else { + builder.Field(15).(*array.StringBuilder).AppendNull() + } + + if starttime.Valid { + builder.Field(16).(*array.StringBuilder).Append(starttime.String) + } else { + builder.Field(16).(*array.StringBuilder).AppendNull() + } + + if arrivaltime.Valid { + builder.Field(17).(*array.StringBuilder).Append(arrivaltime.String) + } else { + builder.Field(17).(*array.StringBuilder).AppendNull() + } + + if pickuptime.Valid { + builder.Field(18).(*array.StringBuilder).Append(pickuptime.String) + } else { + builder.Field(18).(*array.StringBuilder).AppendNull() + } + + if deliverytime.Valid { + builder.Field(19).(*array.StringBuilder).Append(deliverytime.String) + } else { + builder.Field(19).(*array.StringBuilder).AppendNull() + } + + if canceltime.Valid { + builder.Field(20).(*array.StringBuilder).Append(canceltime.String) + } else { + builder.Field(20).(*array.StringBuilder).AppendNull() + } + + if acceptedtime.Valid { + builder.Field(21).(*array.StringBuilder).Append(acceptedtime.String) + } else { + builder.Field(21).(*array.StringBuilder).AppendNull() + } + + if customerid.Valid { + builder.Field(22).(*array.Int32Builder).Append(customerid.Int32) + } else { + builder.Field(22).(*array.Int32Builder).AppendNull() + } + + if pickupcustomer.Valid { + builder.Field(23).(*array.StringBuilder).Append(pickupcustomer.String) + } else { + builder.Field(23).(*array.StringBuilder).AppendNull() + } + + if pickupcontactno.Valid { + builder.Field(24).(*array.StringBuilder).Append(pickupcontactno.String) + } else { + builder.Field(24).(*array.StringBuilder).AppendNull() + } + + if pickuplocationid.Valid { + builder.Field(25).(*array.Int32Builder).Append(pickuplocationid.Int32) + } else { + builder.Field(25).(*array.Int32Builder).AppendNull() + } + + if pickupaddress.Valid { + builder.Field(26).(*array.StringBuilder).Append(pickupaddress.String) + } else { + builder.Field(26).(*array.StringBuilder).AppendNull() + } + + if pickuplocation.Valid { + builder.Field(27).(*array.StringBuilder).Append(pickuplocation.String) + } else { + builder.Field(27).(*array.StringBuilder).AppendNull() + } + + if pickuplat.Valid { + builder.Field(28).(*array.StringBuilder).Append(pickuplat.String) + } else { + builder.Field(28).(*array.StringBuilder).AppendNull() + } + + if pickuplon.Valid { + builder.Field(29).(*array.StringBuilder).Append(pickuplon.String) + } else { + builder.Field(29).(*array.StringBuilder).AppendNull() + } + + if deliverycustomerid.Valid { + builder.Field(30).(*array.Int32Builder).Append(deliverycustomerid.Int32) + } else { + builder.Field(30).(*array.Int32Builder).AppendNull() + } + + if deliverycustomer.Valid { + builder.Field(31).(*array.StringBuilder).Append(deliverycustomer.String) + } else { + builder.Field(31).(*array.StringBuilder).AppendNull() + } + + if deliverycontactno.Valid { + builder.Field(32).(*array.StringBuilder).Append(deliverycontactno.String) + } else { + builder.Field(32).(*array.StringBuilder).AppendNull() + } + + if deliverylocationid.Valid { + builder.Field(33).(*array.Int32Builder).Append(deliverylocationid.Int32) + } else { + builder.Field(33).(*array.Int32Builder).AppendNull() + } + + if deliveryaddress.Valid { + builder.Field(34).(*array.StringBuilder).Append(deliveryaddress.String) + } else { + builder.Field(34).(*array.StringBuilder).AppendNull() + } + + if deliverylocation.Valid { + builder.Field(35).(*array.StringBuilder).Append(deliverylocation.String) + } else { + builder.Field(35).(*array.StringBuilder).AppendNull() + } + + if droplat.Valid { + builder.Field(36).(*array.StringBuilder).Append(droplat.String) + } else { + builder.Field(36).(*array.StringBuilder).AppendNull() + } + + if droplon.Valid { + builder.Field(37).(*array.StringBuilder).Append(droplon.String) + } else { + builder.Field(37).(*array.StringBuilder).AppendNull() + } + + if deliverylat.Valid { + builder.Field(38).(*array.StringBuilder).Append(deliverylat.String) + } else { + builder.Field(38).(*array.StringBuilder).AppendNull() + } + + if deliverylong.Valid { + builder.Field(39).(*array.StringBuilder).Append(deliverylong.String) + } else { + builder.Field(39).(*array.StringBuilder).AppendNull() + } + + if riderslat.Valid { + builder.Field(40).(*array.StringBuilder).Append(riderslat.String) + } else { + builder.Field(40).(*array.StringBuilder).AppendNull() + } + + if riderslon.Valid { + builder.Field(41).(*array.StringBuilder).Append(riderslon.String) + } else { + builder.Field(41).(*array.StringBuilder).AppendNull() + } + + if deliveryamt.Valid { + builder.Field(42).(*array.Float64Builder).Append(deliveryamt.Float64) + } else { + builder.Field(42).(*array.Float64Builder).AppendNull() + } + + if kms.Valid { + builder.Field(43).(*array.StringBuilder).Append(kms.String) + } else { + builder.Field(43).(*array.StringBuilder).AppendNull() + } + + if actualkms.Valid { + builder.Field(44).(*array.StringBuilder).Append(actualkms.String) + } else { + builder.Field(44).(*array.StringBuilder).AppendNull() + } + + if riderkms.Valid { + builder.Field(45).(*array.StringBuilder).Append(riderkms.String) + } else { + builder.Field(45).(*array.StringBuilder).AppendNull() + } + + if deliverycharges.Valid { + builder.Field(46).(*array.Float64Builder).Append(deliverycharges.Float64) + } else { + builder.Field(46).(*array.Float64Builder).AppendNull() + } + + if deliverytype.Valid { + builder.Field(47).(*array.StringBuilder).Append(deliverytype.String) + } else { + builder.Field(47).(*array.StringBuilder).AppendNull() + } + + if paymenttype.Valid { + builder.Field(48).(*array.Int32Builder).Append(paymenttype.Int32) + } else { + builder.Field(48).(*array.Int32Builder).AppendNull() + } + + if smsdelivery.Valid { + builder.Field(49).(*array.Int32Builder).Append(smsdelivery.Int32) + } else { + builder.Field(49).(*array.Int32Builder).AppendNull() + } + + if quantity.Valid { + builder.Field(50).(*array.Int32Builder).Append(quantity.Int32) + } else { + builder.Field(50).(*array.Int32Builder).AppendNull() + } + + if collectionamt.Valid { + builder.Field(51).(*array.Float64Builder).Append(collectionamt.Float64) + } else { + builder.Field(51).(*array.Float64Builder).AppendNull() + } + + if collectedamt.Valid { + builder.Field(52).(*array.Float64Builder).Append(collectedamt.Float64) + } else { + builder.Field(52).(*array.Float64Builder).AppendNull() + } + + if collectionstatus.Valid { + builder.Field(53).(*array.Int32Builder).Append(collectionstatus.Int32) + } else { + builder.Field(53).(*array.Int32Builder).AppendNull() + } + + if notes.Valid { + builder.Field(54).(*array.StringBuilder).Append(notes.String) + } else { + builder.Field(54).(*array.StringBuilder).AppendNull() + } + + if ordernotes.Valid { + builder.Field(55).(*array.StringBuilder).Append(ordernotes.String) + } else { + builder.Field(55).(*array.StringBuilder).AppendNull() + } + + if step.Valid { + builder.Field(56).(*array.Int32Builder).Append(step.Int32) + } else { + builder.Field(56).(*array.Int32Builder).AppendNull() + } + + if eta.Valid { + builder.Field(57).(*array.StringBuilder).Append(eta.String) + } else { + builder.Field(57).(*array.StringBuilder).AppendNull() + } + + if previouskms.Valid { + builder.Field(58).(*array.Int32Builder).Append(previouskms.Int32) + } else { + builder.Field(58).(*array.Int32Builder).AppendNull() + } + + if cumulativekms.Valid { + builder.Field(59).(*array.Int32Builder).Append(cumulativekms.Int32) + } else { + builder.Field(59).(*array.Int32Builder).AppendNull() + } + + if ridertime.Valid { + builder.Field(60).(*array.Int32Builder).Append(ridertime.Int32) + } else { + builder.Field(60).(*array.Int32Builder).AppendNull() + } + + if tenantname.Valid { + builder.Field(61).(*array.StringBuilder).Append(tenantname.String) + } else { + builder.Field(61).(*array.StringBuilder).AppendNull() + } + + if tenantcontactno.Valid { + builder.Field(62).(*array.StringBuilder).Append(tenantcontactno.String) + } else { + builder.Field(62).(*array.StringBuilder).AppendNull() + } + + if tenanttoken.Valid { + builder.Field(63).(*array.StringBuilder).Append(tenanttoken.String) + } else { + builder.Field(63).(*array.StringBuilder).AppendNull() + } + + if tenantsuburb.Valid { + builder.Field(64).(*array.StringBuilder).Append(tenantsuburb.String) + } else { + builder.Field(64).(*array.StringBuilder).AppendNull() + } + + if tenantcity.Valid { + builder.Field(65).(*array.StringBuilder).Append(tenantcity.String) + } else { + builder.Field(65).(*array.StringBuilder).AppendNull() + } + + if tenantaddress.Valid { + builder.Field(66).(*array.StringBuilder).Append(tenantaddress.String) + } else { + builder.Field(66).(*array.StringBuilder).AppendNull() + } + + if ridername.Valid { + builder.Field(67).(*array.StringBuilder).Append(ridername.String) + } else { + builder.Field(67).(*array.StringBuilder).AppendNull() + } + + if userfcmtoken.Valid { + builder.Field(68).(*array.StringBuilder).Append(userfcmtoken.String) + } else { + builder.Field(68).(*array.StringBuilder).AppendNull() + } + + if ridercontact.Valid { + builder.Field(69).(*array.StringBuilder).Append(ridercontact.String) + } else { + builder.Field(69).(*array.StringBuilder).AppendNull() + } + + if locationname.Valid { + builder.Field(70).(*array.StringBuilder).Append(locationname.String) + } else { + builder.Field(70).(*array.StringBuilder).AppendNull() + } + + if locationsuburb.Valid { + builder.Field(71).(*array.StringBuilder).Append(locationsuburb.String) + } else { + builder.Field(71).(*array.StringBuilder).AppendNull() + } + + if locationcontactno.Valid { + builder.Field(72).(*array.StringBuilder).Append(locationcontactno.String) + } else { + builder.Field(72).(*array.StringBuilder).AppendNull() + } + + if locationaddress.Valid { + builder.Field(73).(*array.StringBuilder).Append(locationaddress.String) + } else { + builder.Field(73).(*array.StringBuilder).AppendNull() + } + + if slab.Valid { + builder.Field(74).(*array.StringBuilder).Append(slab.String) + } else { + builder.Field(74).(*array.StringBuilder).AppendNull() + } + + if pricingdate.Valid { + builder.Field(75).(*array.TimestampBuilder). + Append(arrow.Timestamp(pricingdate.Time.UnixMilli())) + } else { + builder.Field(75).(*array.TimestampBuilder).AppendNull() + } + + if baseprice.Valid { + builder.Field(76).(*array.Float64Builder).Append(baseprice.Float64) + } else { + builder.Field(76).(*array.Float64Builder).AppendNull() + } + + if minkm.Valid { + builder.Field(77).(*array.Float64Builder).Append(minkm.Float64) + } else { + builder.Field(77).(*array.Float64Builder).AppendNull() + } + + if priceperkm.Valid { + builder.Field(78).(*array.Float64Builder).Append(priceperkm.Float64) + } else { + builder.Field(78).(*array.Float64Builder).AppendNull() + } + + if maxkm.Valid { + builder.Field(79).(*array.Float64Builder).Append(maxkm.Float64) + } else { + builder.Field(79).(*array.Float64Builder).AppendNull() + } + + if pricingorders.Valid { + builder.Field(80).(*array.Int32Builder).Append(pricingorders.Int32) + } else { + builder.Field(80).(*array.Int32Builder).AppendNull() + } + + if othercharges.Valid { + builder.Field(81).(*array.Float64Builder).Append(othercharges.Float64) + } else { + builder.Field(81).(*array.Float64Builder).AppendNull() + } + + if surgecharges.Valid { + builder.Field(82).(*array.Float64Builder).Append(surgecharges.Float64) + } else { + builder.Field(82).(*array.Float64Builder).AppendNull() + } + + } + record := builder.NewRecord() + defer record.Release() + + _ = os.MkdirAll("backups/parquet/deliveries", 0755) + + filePath := "backups/parquet/deliveries/deliveries_" + + time.Now().Format("2006_01_02") + ".parquet" + + file, err := os.Create(filePath) + if err != nil { + return "", err + } + defer file.Close() + + parquetProps := parquet.NewWriterProperties( + parquet.WithCompression(compress.Codecs.Snappy), + ) + + writer, err := pqarrow.NewFileWriter( + schema, + file, + parquetProps, + pqarrow.ArrowWriterProperties{}, + ) + if err != nil { + return "", err + } + defer writer.Close() + + if err := writer.Write(record); err != nil { + return "", err + } + + return filePath, nil +} diff --git a/internal/backup/mysql_to_parquet_orders.go b/internal/backup/mysql_to_parquet_orders.go new file mode 100644 index 0000000..f22a202 --- /dev/null +++ b/internal/backup/mysql_to_parquet_orders.go @@ -0,0 +1,388 @@ +package backup + +import ( + "database/sql" + "os" + "time" + + "github.com/apache/arrow/go/v14/arrow" + "github.com/apache/arrow/go/v14/arrow/array" + "github.com/apache/arrow/go/v14/arrow/memory" + "github.com/apache/arrow/go/v14/parquet" + "github.com/apache/arrow/go/v14/parquet/compress" + "github.com/apache/arrow/go/v14/parquet/pqarrow" +) + +func BackupOrders(sqlDB *sql.DB) (string, error) { + + rows, err := sqlDB.Query(` + SELECT DISTINCT a.orderheaderid, a.applocationid, + a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, + a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, + a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, + a.deliverycharge, a.kms, + a.customerid, a.pickupaddress, a.pickuplat, a.pickuplong, + a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, + a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, + a.deliverycustomer, a.deliverycontactno, a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, a.orderamount, a.quantity, a.collectionamt, + b.tenantname, b.tenanttoken, b.primarycontact AS tenantcontactno, b.postcode AS tenantpostcode, b.suburb AS tenantsuburb, b.city AS tenantcity, b.address AS tenantaddress, + c.locationname, c.contactno AS locationcontactno, c.postcode AS locationpostcode, c.suburb AS locationsuburb, c.city AS locationcity, c.address AS locationaddress, + d.locationname AS applocation, f.slab, f.pricingdate, f.baseprice, f.minkm, f.priceperkm, f.maxkm, f.orders, f.othercharges, f.surgecharges + FROM orders a + INNER JOIN tenants b ON a.tenantid = b.tenantid + INNER JOIN tenantlocations c ON a.locationid = c.locationid + INNER JOIN app_location d ON a.applocationid = d.applocationid + INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid + LEFT JOIN tenantpricing f ON a.tenantid = f.tenantid + WHERE a.orderdate >= NOW() - INTERVAL '3 months' + `) + if err != nil { + return "", err + } + defer rows.Close() + + // ---------- Arrow Schema (aligned with SQL SELECT — 67 columns) ---------- + schema := arrow.NewSchema([]arrow.Field{ + // core ids + {Name: "orderheaderid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "applocationid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "tenantid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "locationid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "partnerid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "configid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "categoryid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "subcategoryid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "moduleid", Type: arrow.PrimitiveTypes.Int32}, + // order meta + {Name: "orderid", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "orderstatus", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "orderdate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + {Name: "ordernotes", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "itemcount", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "deliverydate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + // lifecycle + {Name: "pending", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "processing", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "ready", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "completed", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "cancelled", Type: arrow.BinaryTypes.String, Nullable: true}, + // charges & distance + {Name: "deliverycharge", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "kms", Type: arrow.BinaryTypes.String, Nullable: true}, + // pickup + {Name: "customerid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "pickupaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplong", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickupcustomer", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickupcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickuplocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pickupcity", Type: arrow.BinaryTypes.String, Nullable: true}, + // delivery + {Name: "deliverycustomerid", Type: arrow.PrimitiveTypes.Int32}, + {Name: "deliveryaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylat", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylong", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverytype", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverycustomer", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverycontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverylocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "deliverycity", Type: arrow.BinaryTypes.String, Nullable: true}, + // payment + {Name: "paymenttype", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "smsdelivery", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "orderamount", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "quantity", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "collectionamt", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + // tenant + {Name: "tenantname", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenanttoken", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantpostcode", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantsuburb", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantcity", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "tenantaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + // location + {Name: "locationname", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationcontactno", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationpostcode", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationsuburb", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationcity", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "locationaddress", Type: arrow.BinaryTypes.String, Nullable: true}, + // app location + pricing + {Name: "applocation", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "slab", Type: arrow.BinaryTypes.String, Nullable: true}, + {Name: "pricingdate", Type: arrow.FixedWidthTypes.Timestamp_ms, Nullable: true}, + {Name: "baseprice", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "minkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "priceperkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "maxkm", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "pricingorders", Type: arrow.PrimitiveTypes.Int32, Nullable: true}, + {Name: "othercharges", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + {Name: "surgecharges", Type: arrow.PrimitiveTypes.Float64, Nullable: true}, + }, nil) + + pool := memory.NewGoAllocator() + builder := array.NewRecordBuilder(pool, schema) + defer builder.Release() + + // ---------- Scan & Append ---------- + for rows.Next() { + + var ( + orderheaderid, applocationid, tenantid, locationid, partnerid int32 + configid, categoryid, subcategoryid, moduleid int32 + + orderid, orderstatus, ordernotes sql.NullString + pending, processing, ready, completed, cancelled sql.NullString + pickupaddress, pickuplat, pickuplong sql.NullString + pickupcustomer, pickupcontactno, pickupsuburb, pickupcity sql.NullString + deliveryaddress, deliverylat, deliverylong sql.NullString + deliverytype, deliverycustomer, deliverycontactno sql.NullString + deliverysuburb, deliverycity sql.NullString + kms sql.NullString + + itemcount, paymenttype, smsdelivery, quantity sql.NullInt32 + customerid, deliverycustomerid int32 + orderamount, deliverycharge, collectionamt sql.NullFloat64 + + orderdate, deliverydate sql.NullTime + + tenantname, tenanttoken, tenantcontactno sql.NullString + tenantpostcode, tenantsuburb, tenantcity, tenantaddress sql.NullString + + locationname, locationcontactno, locationpostcode sql.NullString + locationsuburb, locationcity, locationaddress sql.NullString + + applocation sql.NullString + slab sql.NullString + pricingdate sql.NullTime + baseprice, minkm, priceperkm, maxkm sql.NullFloat64 + pricingorders sql.NullInt32 + othercharges, surgecharges sql.NullFloat64 + ) + + if err := rows.Scan( + // core ids + &orderheaderid, &applocationid, + &tenantid, &locationid, &partnerid, &configid, &categoryid, &subcategoryid, &moduleid, + // order meta + &orderid, &orderstatus, &orderdate, &ordernotes, &itemcount, &deliverydate, + // lifecycle + &pending, &processing, &ready, &completed, &cancelled, + // charges & distance + &deliverycharge, &kms, + // pickup + &customerid, &pickupaddress, &pickuplat, &pickuplong, + &pickupcustomer, &pickupcontactno, &pickupsuburb, &pickupcity, + // delivery + &deliverycustomerid, &deliveryaddress, &deliverylat, &deliverylong, + &deliverytype, &deliverycustomer, &deliverycontactno, + &deliverysuburb, &deliverycity, + // payment + &paymenttype, &smsdelivery, &orderamount, &quantity, &collectionamt, + // tenant + &tenantname, &tenanttoken, &tenantcontactno, + &tenantpostcode, &tenantsuburb, &tenantcity, &tenantaddress, + // location + &locationname, &locationcontactno, &locationpostcode, + &locationsuburb, &locationcity, &locationaddress, + // app location + pricing + &applocation, + &slab, &pricingdate, &baseprice, &minkm, &priceperkm, + &maxkm, &pricingorders, &othercharges, &surgecharges, + ); err != nil { + return "", err + } + + // core ids (not null) + builder.Field(0).(*array.Int32Builder).Append(orderheaderid) + builder.Field(1).(*array.Int32Builder).Append(applocationid) + builder.Field(2).(*array.Int32Builder).Append(tenantid) + builder.Field(3).(*array.Int32Builder).Append(locationid) + builder.Field(4).(*array.Int32Builder).Append(partnerid) + builder.Field(5).(*array.Int32Builder).Append(configid) + builder.Field(6).(*array.Int32Builder).Append(categoryid) + builder.Field(7).(*array.Int32Builder).Append(subcategoryid) + builder.Field(8).(*array.Int32Builder).Append(moduleid) + + // order meta + appendString(builder.Field(9), orderid) + appendString(builder.Field(10), orderstatus) + if orderdate.Valid { + builder.Field(11).(*array.TimestampBuilder).Append(arrow.Timestamp(orderdate.Time.UnixMilli())) + } else { + builder.Field(11).(*array.TimestampBuilder).AppendNull() + } + appendString(builder.Field(12), ordernotes) + if itemcount.Valid { + builder.Field(13).(*array.Int32Builder).Append(itemcount.Int32) + } else { + builder.Field(13).(*array.Int32Builder).AppendNull() + } + if deliverydate.Valid { + builder.Field(14).(*array.TimestampBuilder).Append(arrow.Timestamp(deliverydate.Time.UnixMilli())) + } else { + builder.Field(14).(*array.TimestampBuilder).AppendNull() + } + + // lifecycle + appendString(builder.Field(15), pending) + appendString(builder.Field(16), processing) + appendString(builder.Field(17), ready) + appendString(builder.Field(18), completed) + appendString(builder.Field(19), cancelled) + + // charges & distance + if deliverycharge.Valid { + builder.Field(20).(*array.Float64Builder).Append(deliverycharge.Float64) + } else { + builder.Field(20).(*array.Float64Builder).AppendNull() + } + appendString(builder.Field(21), kms) + + // pickup + builder.Field(22).(*array.Int32Builder).Append(customerid) + appendString(builder.Field(23), pickupaddress) + appendString(builder.Field(24), pickuplat) + appendString(builder.Field(25), pickuplong) + appendString(builder.Field(26), pickupcustomer) + appendString(builder.Field(27), pickupcontactno) + appendString(builder.Field(28), pickupsuburb) + appendString(builder.Field(29), pickupcity) + + // delivery + builder.Field(30).(*array.Int32Builder).Append(deliverycustomerid) + appendString(builder.Field(31), deliveryaddress) + appendString(builder.Field(32), deliverylat) + appendString(builder.Field(33), deliverylong) + appendString(builder.Field(34), deliverytype) + appendString(builder.Field(35), deliverycustomer) + appendString(builder.Field(36), deliverycontactno) + appendString(builder.Field(37), deliverysuburb) + appendString(builder.Field(38), deliverycity) + + // payment + if paymenttype.Valid { + builder.Field(39).(*array.Int32Builder).Append(paymenttype.Int32) + } else { + builder.Field(39).(*array.Int32Builder).AppendNull() + } + if smsdelivery.Valid { + builder.Field(40).(*array.Int32Builder).Append(smsdelivery.Int32) + } else { + builder.Field(40).(*array.Int32Builder).AppendNull() + } + if orderamount.Valid { + builder.Field(41).(*array.Float64Builder).Append(orderamount.Float64) + } else { + builder.Field(41).(*array.Float64Builder).AppendNull() + } + if quantity.Valid { + builder.Field(42).(*array.Int32Builder).Append(quantity.Int32) + } else { + builder.Field(42).(*array.Int32Builder).AppendNull() + } + if collectionamt.Valid { + builder.Field(43).(*array.Float64Builder).Append(collectionamt.Float64) + } else { + builder.Field(43).(*array.Float64Builder).AppendNull() + } + + // tenant + appendString(builder.Field(44), tenantname) + appendString(builder.Field(45), tenanttoken) + appendString(builder.Field(46), tenantcontactno) + appendString(builder.Field(47), tenantpostcode) + appendString(builder.Field(48), tenantsuburb) + appendString(builder.Field(49), tenantcity) + appendString(builder.Field(50), tenantaddress) + + // location + appendString(builder.Field(51), locationname) + appendString(builder.Field(52), locationcontactno) + appendString(builder.Field(53), locationpostcode) + appendString(builder.Field(54), locationsuburb) + appendString(builder.Field(55), locationcity) + appendString(builder.Field(56), locationaddress) + + // app location + pricing + appendString(builder.Field(57), applocation) + appendString(builder.Field(58), slab) + if pricingdate.Valid { + builder.Field(59).(*array.TimestampBuilder).Append(arrow.Timestamp(pricingdate.Time.UnixMilli())) + } else { + builder.Field(59).(*array.TimestampBuilder).AppendNull() + } + if baseprice.Valid { + builder.Field(60).(*array.Float64Builder).Append(baseprice.Float64) + } else { + builder.Field(60).(*array.Float64Builder).AppendNull() + } + if minkm.Valid { + builder.Field(61).(*array.Float64Builder).Append(minkm.Float64) + } else { + builder.Field(61).(*array.Float64Builder).AppendNull() + } + if priceperkm.Valid { + builder.Field(62).(*array.Float64Builder).Append(priceperkm.Float64) + } else { + builder.Field(62).(*array.Float64Builder).AppendNull() + } + if maxkm.Valid { + builder.Field(63).(*array.Float64Builder).Append(maxkm.Float64) + } else { + builder.Field(63).(*array.Float64Builder).AppendNull() + } + if pricingorders.Valid { + builder.Field(64).(*array.Int32Builder).Append(pricingorders.Int32) + } else { + builder.Field(64).(*array.Int32Builder).AppendNull() + } + if othercharges.Valid { + builder.Field(65).(*array.Float64Builder).Append(othercharges.Float64) + } else { + builder.Field(65).(*array.Float64Builder).AppendNull() + } + if surgecharges.Valid { + builder.Field(66).(*array.Float64Builder).Append(surgecharges.Float64) + } else { + builder.Field(66).(*array.Float64Builder).AppendNull() + } + } + + record := builder.NewRecord() + defer record.Release() + + _ = os.MkdirAll("backups/parquet/orders", 0755) + + filePath := "backups/parquet/orders/orders_last_3_months_" + + time.Now().Format("2006_01_02") + ".parquet" + + file, err := os.Create(filePath) + if err != nil { + return "", err + } + defer file.Close() + + parquetProps := parquet.NewWriterProperties( + parquet.WithCompression(compress.Codecs.Snappy), + ) + + writer, err := pqarrow.NewFileWriter( + schema, + file, + parquetProps, + pqarrow.ArrowWriterProperties{}, + ) + if err != nil { + return "", err + } + defer writer.Close() + + if err := writer.Write(record); err != nil { + return "", err + } + + return filePath, nil +} diff --git a/internal/backup/s3_upload.go b/internal/backup/s3_upload.go new file mode 100644 index 0000000..879ef1d --- /dev/null +++ b/internal/backup/s3_upload.go @@ -0,0 +1,67 @@ +package backup + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +func UploadParquetToSpaces(localFilePath, objectPath string) (string, error) { + + region := os.Getenv("DO_SPACES_REGION") + endpoint := os.Getenv("DO_SPACES_ENDPOINT") + bucket := os.Getenv("DO_SPACES_BUCKET") + accessKey := os.Getenv("DO_SPACES_ACCESS_KEY") + secretKey := os.Getenv("DO_SPACES_SECRET_KEY") + + cfg, err := config.LoadDefaultConfig( + context.TODO(), + config.WithRegion(region), + config.WithCredentialsProvider( + credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""), + ), + config.WithEndpointResolver( + aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) { + return aws.Endpoint{ + URL: "https://" + endpoint, + SigningRegion: region, + }, nil + }), + ), + ) + if err != nil { + return "", err + } + + client := s3.NewFromConfig(cfg) + + file, err := os.Open(localFilePath) + if err != nil { + return "", err + } + defer file.Close() + + _, err = client.PutObject(context.TODO(), &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(objectPath), + Body: file, + ContentType: aws.String("application/octet-stream"), + ACL: "public-read", + }) + if err != nil { + return "", err + } + + cdnURL := fmt.Sprintf( + "https://images.nearle.app/%s", + filepath.ToSlash(objectPath), + ) + + return cdnURL, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7f8ab88 --- /dev/null +++ b/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + "time" + + "nearle/config" + "nearle/db" + "nearle/middlewares" + "nearle/routes" + "nearle/utils" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/joho/godotenv" +) + +func main() { + + _ = godotenv.Load() + cfg := config.Load() + + app := fiber.New() + + app.Use(cors.New(cors.Config{ + AllowHeaders: "Origin,Content-Type,Accept,Authorization", + AllowOrigins: "http://localhost:3000,http://localhost:3001,http://localhost:3002,http://localhost:3003,http://localhost:3004,http://localhost:3005,https://console.nearlexpress.com,https://app.nearlexpress.com,https://admin.nearlexpress.com,https://developer.nearlexpress.com", // 🔥 simplify (or keep your domains) + AllowCredentials: true, + AllowMethods: "GET,POST,PUT,DELETE,PATCH,OPTIONS", + })) + + app.Use(middlewares.ZapLogger()) + + utils.Info("Connecting to database...") + + db.Connect() + db.InitRedis() + + utils.Info("All connections established!") + + routes.LiveSetup(app) + + go func() { + utils.Info("Server starting", "port", cfg.Port) + if err := app.Listen(":" + cfg.Port); err != nil { + utils.Logger.Fatalf("Server failed: %v", err) + } + }() + + gracefulShutdown() +} + +func gracefulShutdown() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + <-c + utils.Info("Shutting down...") + + db.CloseDB() + + time.Sleep(2 * time.Second) + utils.Info("Shutdown complete") +} diff --git a/mainOLD copy.go b/mainOLD copy.go new file mode 100644 index 0000000..4db76cc --- /dev/null +++ b/mainOLD copy.go @@ -0,0 +1,57 @@ +package main + +// import ( +// "strings" + +// "nearle/db" +// "nearle/routes" + +// "github.com/gofiber/fiber/v2" +// "github.com/gofiber/fiber/v2/middleware/cors" +// ) + +// var ( +// router = fiber.New() +// ) + +// func main() { + +// //app := fiber.New() + +// router.Use(cors.New(cors.Config{ +// AllowHeaders: "Origin,Content-Type,Accept,Content-Length,Accept-Language,Accept-Encoding,Connection,Access-Control-Allow-Origin", +// AllowOrigins: "*", +// AllowCredentials: true, +// AllowMethods: "GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS", +// })) + +// router.Use(handler) + +// routes.DevSetup(router) +// routes.LiveSetup(router) + +// router.Listen(":1009") + +// } + +// func handler(c *fiber.Ctx) error { + +// path := c.Path() +// result := strings.Split(path, "/") +// var flavour string + +// for _ = range result { +// flavour = result[1] +// } +// if flavour == "dev" { + +// db.DevConnect() + +// } else if flavour == "live" { + +// db.LiveConnect() +// defer db.CloseDB() + +// } +// return c.Next() +// } diff --git a/middlewares/loggerMiddleware.go b/middlewares/loggerMiddleware.go new file mode 100644 index 0000000..e0f2763 --- /dev/null +++ b/middlewares/loggerMiddleware.go @@ -0,0 +1,43 @@ +package middlewares + +import ( + "time" + + "nearle/utils" + + "github.com/gofiber/fiber/v2" +) + +// ZapLogger is a Fiber middleware that logs HTTP requests using Zap. +func ZapLogger() fiber.Handler { + return func(c *fiber.Ctx) error { + start := time.Now() + + // Handle the request + err := c.Next() + + latency := time.Since(start).String() + status := c.Response().StatusCode() + method := c.Method() + path := c.Path() + ip := c.IP() + + fields := []interface{}{ + "status", status, + "method", method, + "path", path, + "ip", ip, + "latency", latency, + } + + if err != nil { + fields = append(fields, "error", err.Error()) + utils.Logger.Errorw("API Request Failed", fields...) + return err + } + + utils.Logger.Infow("API Request Successful", fields...) + + return nil + } +} diff --git a/middlewares/roleMiddleware.go b/middlewares/roleMiddleware.go new file mode 100644 index 0000000..e7fc448 --- /dev/null +++ b/middlewares/roleMiddleware.go @@ -0,0 +1,89 @@ +package middlewares + +import ( + "nearle/db" + "nearle/models" + + "github.com/gofiber/fiber/v2" +) + +type RolePayload struct { + Roleid int `json:"roleid"` + + + +} + +func RoleCheckMiddleware(allowedRoles ...int) fiber.Handler { + return func(c *fiber.Ctx) error { + var user models.User + + if err := c.BodyParser(&user); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "Invalid request body", + }) + } + + var uid, dbRoleId int + var status, dbPassword string + var query string + + if user.Authname != "" { + query = `SELECT userid, password, status, roleid FROM app_users WHERE authname = ? AND configid = ?` + db.DB.Raw(query, user.Authname, user.Configid).Row().Scan(&uid, &dbPassword, &status, &dbRoleId) + } else if user.Contactno != "" { + query = `SELECT userid, password, status, roleid FROM app_users WHERE contactno = ? AND configid = ?` + db.DB.Raw(query, user.Contactno, user.Configid).Row().Scan(&uid, &dbPassword, &status, &dbRoleId) + } else { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "status": false, + "code": 400, + "message": "authname or contactno required", + }) + } + + // No user found + if uid == 0 { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 409, + "message": "Invalid Email", + "tenantform": true, + }) + } + + // Inactive user + if status == "InActive" { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Inactive Account. Contact admin.", + }) + } + + // Check allowed role + allowed := false + for _, r := range allowedRoles { + if dbRoleId == r { + allowed = true + break + } + } + + if !allowed { + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "status": false, + "code": 403, + "message": "Unauthorized role", + }) + } + + // Store user data in context + c.Locals("uid", uid) + c.Locals("password", dbPassword) + + return c.Next() + } +} diff --git a/models/common.go b/models/common.go new file mode 100644 index 0000000..4dfd11e --- /dev/null +++ b/models/common.go @@ -0,0 +1,91 @@ +package models + +type Result struct { + Code int `json:"code"` + Status bool `json:"status"` + Message string `json:"message"` +} + +type GroupNotifications struct { + Notifications []NotificationData `json:"notifications"` + Fcmmodel *Notification `json:"fcmmodel"` +} + +type Notifications struct { + Priority string `json:"priority"` + Registrationids []string `json:"registration_ids"` + Data Data `json:"data"` + Notify Notify `json:"notification"` +} + +type NotifyUser struct { + Sender string `json:"sender"` + Accessid string `json:"accessid"` + Notification Notification `json:"notification"` +} + +type Notification struct { + Priority string `json:"priority"` + To string `json:"to"` + Notify Notify `json:"notification"` +} + +type LoginNotification struct { + Priority string `json:"priority"` + To string `json:"to"` + Notification Notify `json:"notification"` +} + +type Notify struct { + Title string `json:"title"` + Body string `json:"body"` + Sound string `json:"sound"` +} + +type FcmNotification struct { + Title string `json:"title"` + Body string `json:"body"` + // Type string `json:"type"` +} + +type NotificationResult struct { + StatusCode int `json:"statuscode"` + Success int `json:"success"` + Failure int `json:"failure"` +} + +type Data struct { + Notificationid int `json:"notificationid" gorm:"Primary_Key"` + Notificationdate string `json:"notificationdate"` + Title string `json:"title"` + Message string `json:"message"` + Configid int `json:"configid"` + Tenantid int64 `json:"tenantid"` + Orderheaderid int `json:"orderheaderid"` + Orderid string `json:"orderid"` + Success int `json:"success"` + Notifytype int `json:"notifytype"` + Notifyreason string `json:"notifyreason"` + Accessid string `json:"accessid" gorm:"<-:false"` + Customerid int64 `json:"customerid" gorm:"<-:false"` + ClickAction string `json:"clickaction" gorm:"<-:false"` +} + +type NotificationData struct { + Notificationid int `json:"notificationid" gorm:"Primary_Key"` + Notificationdate string `json:"notificationdate"` + Title string `json:"title"` + Message string `json:"message"` + Configid int `json:"configid"` + Tenantid int64 `json:"tenantid"` + Orderheaderid int `json:"orderheaderid"` + Orderprocessid int `json:"orderprocessid"` + Orderstatus string `json:"orderstatus" gorm:"<-:false"` + Processing string `json:"processing" gorm:"<-:false"` + Shiftid int64 `json:"shiftid"` + Userid int `json:"userid"` + Orderid string `json:"orderid"` + Notifytype int `json:"notifytype"` + Notifyreason string `json:"notifyreason"` + Success int `json:"success"` +} diff --git a/models/customer.go b/models/customer.go new file mode 100644 index 0000000..d4f5d39 --- /dev/null +++ b/models/customer.go @@ -0,0 +1,189 @@ +package models + +import "time" + +type CustomerResult struct { + Code int `json:"code"` + Status bool `json:"status"` + Message string `json:"message"` + Details Customers `json:"details"` +} + +type CustomersResult struct { + Code int `json:"code"` + Status bool `json:"status"` + Message string `json:"message"` + Details []Customers `json:"details"` +} + +type CustomerInfo struct { + Customerid int `json:"customerid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Profileimage string `json:"profileimage"` + Gender string `json:"gender"` + Dob string `json:"dob"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Email string `json:"email"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Authmode int `json:"authmode"` + Configid int `json:"configid"` + Customertoken string `json:"customertoken"` + Deliverylocationid int `json:"deliverylocationid"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Landmark string `json:"landmark"` + Doorno string `json:"doorno"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Applocationid int `json:"applocationid"` + Allocationid int `json:"allocationid"` + Primaryaddress int `json:"primaryaddress"` + Tenantlocationid int `json:"tenantlocationid"` + Tenantid int `json:"tenantid"` + Status int `json:"status"` + Intro string `json:"intro"` + Selectedlatitude string `json:"selectedlatitude"` + Selectedlongitude string `json:"selectedlongitude"` + Radius string `json:"radius"` + Qrmode int `json:"qrmode"` + Quantity int `json:"quantity" gorm:"default:1"` + Collectionamt float32 `json:"collectionamt" gorm:"default:0.0"` +} + +type Customers struct { + Customerid int `json:"customerid" gorm:"Primary_Key"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Profileimage string `json:"profileimage"` + Gender string `json:"gender"` + Dob string `json:"dob"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Email string `json:"email"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Authmode int `json:"authmode"` + Configid int `json:"configid"` + Customertoken string `json:"customertoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Landmark string `json:"landmark"` + Doorno string `json:"doorno"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Applocationid int `json:"applocationid"` + Locationid int `json:"locationid,omitempty" gorm:"-"` + Defaultaddress string `json:"defaultaddress,omitempty" gorm:"-"` + Primaryaddress int `json:"primaryaddress,omitempty" gorm:"-"` + Tenantid int `json:"tenantid,omitempty" gorm:"-"` + Status int `json:"status"` + Intro string `json:"intro"` + Qrmode int `json:"qrmode,omitempty" gorm:"-"` +} + +type Customer struct { + Customerid int `json:"customerid" gorm:"Primary_Key"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Profileimage string `json:"profileimage"` + Gender string `json:"gender"` + Dob string `json:"dob"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Email string `json:"email"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Authmode int `json:"authmode"` + Configid int `json:"configid"` + Customertoken string `json:"customertoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Landmark string `json:"landmark"` + Doorno string `json:"doorno"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Applocationid int `json:"applocationid"` + Locationid int `json:"locationid,omitempty"` + Defaultaddress string `json:"defaultaddress,omitempty"` + Primaryaddress int `json:"primaryaddress,omitempty"` + Tenantid int `json:"tenantid,omitempty"` + Status int `json:"status"` + Intro string `json:"intro"` + Qrmode int `json:"qrmode,omitempty"` +} + +type Tenantcustomers struct { + Tenantcustomerid int `json:"tenantcustomerid" gorm:"Primary_Key;autoIncrement"` + Tenantid int `json:"tenantid" ` + Locationid int `json:"locationid"` + Customerid int `json:"customerid"` + Moduleid int `json:"moduleid"` + Status int `json:"status"` +} + +type Tenantcustomer struct { + Tenantcustomerid int `json:"tenantcustomerid" gorm:"Primary_Key"` + // Tenantid int `json:"tenantid" ` + Locationid int `json:"locationid"` + Customerid int `json:"customerid"` + Moduleid int `json:"moduleid"` + Status int `json:"status"` +} + +type CustomersId struct { + Customerid int `json:"customerid"` + Locationid int `json:"locationsid"` + Pickuplocationid int `json:"pickuplocationid"` + Deliveryid int `json:"deliveryid"` + Droplocationid int `json:"droplocationid"` +} + +type CustomerLocationResult struct { + Code int `json:"code"` + Status bool `json:"status"` + Message string `json:"message"` + Details []Customerlocations `json:"details"` +} + +type Customerlocations struct { + Locationid int `json:"locationid" gorm:"Primary_Key"` + Customerid int `json:"customerid"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Landmark string `json:"landmark"` + Doorno string `json:"doorno"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Primaryaddress int `json:"primaryaddress"` + Status int `json:"status"` +} + +type CustomerRequest struct { + Customerrequestid int `json:"customerrequestid"` + Referencedate time.Time `json:"referencedate"` + Referencetype string `json:"referencetype"` + Customerid int `json:"customerid"` + Tenantid int `json:"tenantid"` + Apptypeid int `json:"apptypeid"` + Locationid int `json:"locationid"` + Subject string `json:"subject"` + Remarks string `json:"remarks"` + Status int `json:"status"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} diff --git a/models/delivery.go b/models/delivery.go new file mode 100644 index 0000000..595501d --- /dev/null +++ b/models/delivery.go @@ -0,0 +1,443 @@ +package models + +import "gorm.io/gorm" + +type Deliveries struct { + Deliveryid int `json:"deliveryid" gorm:"Primary_Key"` + Orderheaderid int `json:"orderheaderid"` + Applocationid int `json:"applocationid"` + Configid int `json:"configid"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Moduleid int `json:"moduleid"` + Locationid int `json:"locationid"` + Categoryid int `json:"categoryid"` + Userid int `json:"userid"` + Subcategoryid int `json:"subcategoryid"` + Orderid string `json:"orderid"` + Deliverydate string `json:"deliverydate"` + Orderstatus string `json:"orderstatus"` + Assigntime string `json:"assigntime"` + Starttime string `json:"starttime"` + Arrivaltime string `json:"arrivaltime"` + Pickuptime string `json:"pickuptime"` + Acceptedtime string `json:"acceptedtime"` + Canceltime string `json:"canceltime"` + Itemcount int `json:"itemcount"` + Orderamount float64 `json:"orderamount" gorm:"type:numeric"` + Customerid int `json:"customerid"` + Pickupcustomer string `json:"pickupcustomer"` + Pickupcontactno string `json:"pickupcontactno"` + Pickuplocationid int `json:"pickuplocationid"` + Pickupaddress string `json:"Pickupaddress"` + Pickuplocation string `json:"pickuplocation"` + Pickuplat string `json:"pickuplat"` + Pickuplon string `json:"pickuplon"` + Deliverycustomerid int `json:"deliverycustomerid"` + Deliverylocationid int `json:"deliverylocationid"` + Deliverycustomer string `json:"deliverycustomer"` + Deliverycontactno string `json:"deliverycontactno"` + Deliveryaddress string `json:"deliveryaddress"` + Deliverylocation string `json:"deliverylocation"` + Droplat string `json:"droplat"` + Droplon string `json:"droplon"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Deliverycharges float64 `json:"deliverycharges" gorm:"type:numeric"` + Deliveryamt float64 `json:"deliveryamt" gorm:"type:numeric"` + Deliverytype string `json:"deliverytype"` + Notes string `json:"notes"` + Ordernotes string `json:"ordernotes"` + Riderslat string `json:"riderslat"` + Riderslon string `json:"riderslon"` + Firstmilekm float64 `json:"firstmilekm" gorm:"type:numeric"` + Firstmilecharges float64 `json:"firstmilecharges" gorm:"type:numeric"` + Lastmilecharges float64 `json:"lastmilecharges" gorm:"type:numeric"` + Ridercharges float64 `json:"ridercharges" gorm:"type:numeric"` + Kms string `json:"kms"` + Actualkms string `json:"actualkms"` + Smsdelivery int `json:"smsdelivery"` + Paymenttype int `json:"paymenttype" gorm:"default:0"` + Previouskms int `json:"previouskms" gorm:"default:0"` + Cumulativekms int `json:"cumulativekms" gorm:"default:0"` + Step int `json:"step" gorm:"default:0"` + Eta string `json:"eta" gorm:"type:varchar(100);default:''"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Collectedamt float64 `json:"collectedamt" gorm:"type:numeric"` + Collectionstatus float64 `json:"collectionstatus" gorm:"type:numeric"` + Ridertime int `json:"ridertime"` + // GPS tracking fields (nullable — old app versions may not send these) + RawLatitude *string `json:"raw_latitude" gorm:"column:raw_latitude"` + RawLongitude *string `json:"raw_longitude" gorm:"column:raw_longitude"` + VelocityLat *string `json:"velocity_lat" gorm:"column:velocity_lat"` + VelocityLng *string `json:"velocity_lng" gorm:"column:velocity_lng"` + Speed *string `json:"speed" gorm:"column:speed"` + Heading *string `json:"heading" gorm:"column:heading"` + Expecteddeliverytime string `json:"expecteddeliverytime"` + Profit float64 `json:"profit"` + Transitminutes float64 `json:"transitminutes" gorm:"type:numeric"` + Calculationdistancekm float64 `json:"calculationdistancekm" gorm:"type:numeric"` +} + +type UpdateDeliveryStatus struct { + Deliveryid int `json:"deliveryid"` + Deliverytype string `json:"deliverytype"` + Pickuplocationid int `json:"pickuplocationid"` + Deliverylocationid int `json:"deliverylocationid"` + Orderheaderid int `json:"orderheaderid"` + Userid int `json:"userid"` + Orderstatus string `json:"orderstatus"` + Assigntime string `json:"assigntime"` + Starttime string `json:"starttime"` + Arrivaltime string `json:"arrivaltime"` + Pickuptime string `json:"pickuptime"` + Acceptedtime string `json:"acceptedtime"` + Deliverytime string `json:"deliverytime"` + Canceltime string `json:"canceltime"` + Pickuplat string `json:"pickuplat"` + Pickuplon string `json:"picklon"` + Pickupimage string `json:"pickupimage"` + Riderslat string `json:"riderslat"` + Riderslon string `json:"riderslon"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Dropimage string `json:"dropimage"` + Address string `json:"address" gorm:"<-:false"` + Suburb string `json:"suburb" gorm:"<-:false"` + City string `json:"city" gorm:"<-:false"` + State string `json:"state" gorm:"<-:false"` + Postcode string `json:"postcode" gorm:"<-:false"` + Deliveryamt float64 `json:"deliveryamt" gorm:"type:numeric"` + Kms string `json:"kms"` + // Actualkms string `json:"actualkms"` + Riderkms string `json:"riderkms"` + Ridercharges float64 `json:"ridercharges" gorm:"type:numeric"` + Kmcal string `json:"kmcal"` + Notes string `json:"notes"` + Feedback string `json:"feedback"` + Smsdelivery int `json:"smsdelivery"` + Previouskms int `json:"previouskms" gorm:"default:0"` + Cumulativekms int `json:"cumulativekms" gorm:"default:0"` + Step int `json:"step" gorm:"default:0"` + Eta string `json:"eta"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Collectedamt float64 `json:"collectedamt" gorm:"type:numeric"` + Collectionstatus int `json:"collectionstatus" gorm:"default:0"` + Ridertime int `json:"ridertime" gorm:"default:0"` + Bonuspts int `json:"bonuspts"` + Skippedtime string `json:"skippedtime"` + Activelat string `json:"activelat"` + Activelon string `json:"activelon"` + // GPS tracking fields (nullable — old app versions may not send these) + RawLatitude *string `json:"raw_latitude" gorm:"column:raw_latitude"` + RawLongitude *string `json:"raw_longitude" gorm:"column:raw_longitude"` + VelocityLat *string `json:"velocity_lat" gorm:"column:velocity_lat"` + VelocityLng *string `json:"velocity_lng" gorm:"column:velocity_lng"` + Speed *string `json:"speed" gorm:"column:speed"` + Heading *string `json:"heading" gorm:"column:heading"` + Expecteddeliverytime string `json:"expecteddeliverytime"` + Profit float64 `json:"profit"` + Transitminutes float64 `json:"transitminutes" gorm:"type:numeric"` + Calculationdistancekm float64 `json:"calculationdistancekm" gorm:"type:numeric"` +} + +type Deliveryinfo struct { + Deliveryid int `json:"deliveryid"` + Orderheaderid int `json:"orderheaderid"` + Applocationid int `json:"applocationid"` + Applocation string `json:"applocation"` + Configid int `json:"configid"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Moduleid int `json:"moduleid"` + Locationid int `json:"locationid"` + Categoryid int `json:"categoryid"` + Userid int `json:"userid"` + Subcategoryid int `json:"subcategoryid"` + Orderid string `json:"orderid"` + Deliverydate string `json:"deliverydate"` + Orderstatus string `json:"orderstatus"` + Assigntime string `json:"assigntime"` + Starttime string `json:"starttime"` + Arrivaltime string `json:"arrivaltime"` + Pickuptime string `json:"pickuptime"` + Acceptedtime string `json:"acceptedtime"` + Deliverytime string `json:"deliverytime"` + Canceltime string `json:"canceltime"` + Itemcount int `json:"itemcount"` + Orderamount float64 `json:"orderamount" gorm:"type:numeric"` + Customerid int `json:"customerid"` + Pickupcustomer string `json:"pickupcustomer"` + Pickupcontactno string `json:"pickupcontactno"` + Pickuplocationid int `json:"pickuplocationid"` + Pickupaddress string `json:"Pickupaddress"` + Pickuplocation string `json:"pickuplocation"` + Pickuplat string `json:"pickuplat"` + Pickuplon string `json:"pickuplon"` + Deliverycustomerid int `json:"deliverycustomerid"` + Deliverylocationid int `json:"deliverylocationid"` + Deliverycustomer string `json:"deliverycustomer"` + Deliverycontactno string `json:"deliverycontactno"` + Deliveryaddress string `json:"deliveryaddress"` + Deliverylocation string `json:"deliverylocation"` + Droplat string `json:"droplat"` + Droplon string `json:"droplon"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Deliverycharges float64 `json:"deliverycharges" gorm:"type:numeric"` + Deliveryamt float64 `json:"deliveryamt" gorm:"type:numeric"` + Deliverytype string `json:"deliverytype"` + Notes string `json:"notes"` + Ordernotes string `json:"ordernotes"` + Riderslat string `json:"riderslat"` + Riderslon string `json:"riderslon"` + Firstmilekm float64 `json:"firstmilekm" gorm:"type:numeric"` + Firstmilecharges float64 `json:"firstmilecharges" gorm:"type:numeric"` + Lastmilecharges float64 `json:"lastmilecharges" gorm:"type:numeric"` + Ridercharges float64 `json:"ridercharges" gorm:"type:numeric"` + Kms string `json:"kms"` + Actualkms string `json:"actualkms"` + Riderkms string `json:"riderkms"` + Paymenttype int `json:"paymenttype"` + Tenantname string `json:"tenantname"` + Tenantcontactno string `json:"tenantcontactno"` + Tenanttoken string `json:"tenanttoken"` + Tenantsuburb string `json:"tenantsuburb"` + Tenantcity string `json:"tenantcity"` + Tenantaddress string `json:"tenantaddress"` + Locationname string `json:"locationname"` + Locationcontactno string `json:"locationcontactno"` + Locationsuburb string `json:"locationsuburb"` + Locationaddress string `json:"locationaddress"` + Ridername string `json:"ridername"` + Userfcmtoken string `json:"userfcmtoken"` + Queueid int `json:"queueid"` + Smsdelivery int `json:"smsdelivery"` + Customertoken string `json:"customertoken"` + Ridercontact string `json:"ridercontact"` + Previouskms int `json:"previouskms" ` + Cumulativekms int `json:"cumulativekms" ` + Step int `json:"step" gorm:"default:0"` + Eta string `json:"eta" gorm:"type:varchar(100);default:''"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Collectedamt float64 `json:"collectedamt" gorm:"type:numeric"` + Collectionstatus float64 `json:"collectionstatus" gorm:"type:numeric"` + Ridertime int `json:"ridertime"` + Slab string `json:"slab"` + Pricingdate string `json:"pricingdate"` + Baseprice float64 `json:"baseprice" gorm:"type:numeric"` + Minkm int `json:"minkm"` + Priceperkm float64 `json:"priceperkm" gorm:"type:numeric"` + Maxkm int `json:"maxkm"` + Orders int `json:"orders"` + Othercharges float64 `json:"othercharges" gorm:"type:numeric"` + Surgecharges float64 `json:"surgecharges" gorm:"type:numeric"` + Expecteddeliverytime string `json:"expecteddeliverytime"` + Profit float64 `json:"profit"` + Transitminutes float64 `json:"transitminutes" gorm:"type:numeric"` + Calculationdistancekm float64 `json:"calculationdistancekm" gorm:"type:numeric"` +} + +type Deliveryqueues struct { + Queueid int `json:"queueid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Orderheaderid int `json:"orderheaderid"` + Deliveryid int `json:"deliveryid"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Orderid string `json:"orderid"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Accept int `json:"accept"` + Decline int `json:"decline"` + Queuestatus int `json:"queuestatus"` +} + +type Deliverylogs struct { + Logid int `json:"logid" gorm:"Primary_Key"` + Logdate string `json:"logdate"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Orderheaderid int `json:"orderheaderid"` + Deliveryid int `json:"deliveryid"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Orderid string `json:"orderid"` + Orderstatus string `json:"orderstatus"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Logstatus *int `json:"logstatus" gorm:"default:0"` + + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + // GPS tracking fields (nullable — old app versions may not send these) + RawLatitude *string `json:"raw_latitude" gorm:"column:raw_latitude"` + RawLongitude *string `json:"raw_longitude" gorm:"column:raw_longitude"` + VelocityLat *string `json:"velocity_lat" gorm:"column:velocity_lat"` + VelocityLng *string `json:"velocity_lng" gorm:"column:velocity_lng"` + Speed *string `json:"speed" gorm:"column:speed"` + Heading *string `json:"heading" gorm:"column:heading"` +} + +type Deliverydet struct { + Deliveries int `json:"deliveries"` + Delivered int `json:"Delivered"` + Cancelled int `json:"cancelled"` + Kms float64 `json:"kms" gorm:"type:numeric"` + Amount float64 `json:"amount" gorm:"type:numeric"` +} + +type DeliverySummary struct { + Total int `json:"total"` + Created int `json:"created"` + Pending int `json:"pending"` + Accepted int `json:"accepted"` + Arrived int `json:"arrived"` + Picked int `json:"picked"` + Active int `json:"active"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` + Skipped int `json:"skipped"` +} + +type DeliveryDetailsCount struct { + Assigned int `json:"assigned"` + Accepted int `json:"accepted"` + Arrived int `json:"Arrived"` + Picked int `json:"picked"` + Active int `json:"active"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` +} + +type ReportSummary struct { + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + Totalorders int `json:"totalorders"` + Orderscreated int `json:"orderscreated"` + Orderspending int `json:"Orderspending"` + Orderscompleted int `json:"orderscompleted"` + Orderscancelled int `json:"orderscancelled"` + Deliveriespending int `json:"deliveriespending"` + Deliveriescompleted int `json:"deliveriescompleted"` + Deliveriescancelled int `json:"deliveriescancelled"` + Paylater float64 `json:"paylater" gorm:"type:numeric"` + Payondelivery float64 `json:"payondelivery" gorm:"type:numeric"` + Kms float64 `json:"kms" gorm:"type:numeric"` + Actualkms float64 `json:"actualkms" gorm:"type:numeric"` + Charges float64 `json:"charges" gorm:"type:numeric"` + Previouskms int `json:"previouskms"` + Cumulativekms int `json:"cumulativekms"` + Deliveryamt float64 `json:"deliveryamt" gorm:"type:numeric"` + Riderkms float64 `json:"riderkms" gorm:"type:numeric"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Collectedamt float64 `json:"collectedamt" gorm:"type:numeric"` + Collectionstatus float64 `json:"collectionstatus" gorm:"type:numeric"` +} + +type UserReportSummary struct { + + // 🔹 User (Rider) details + Userid int `json:"userid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Ridercontact string `json:"ridercontact"` + + // 🔹 Tenant details + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + + // 🔹 Location details + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + + // 🔹 Order summary + Totalorders int `json:"totalorders"` + Orderscreated int `json:"orderscreated"` + Orderspending int `json:"orderspending"` + Orderscompleted int `json:"orderscompleted"` + Orderscancelled int `json:"orderscancelled"` + + // 🔹 Delivery summary + Deliveriespending int `json:"deliveriespending"` + Deliveriescompleted int `json:"deliveriescompleted"` + Deliveriescancelled int `json:"deliveriescancelled"` + + // 🔹 Payment summary + Paylater float64 `json:"paylater"` + Payondelivery float64 `json:"payondelivery"` + + // 🔹 Distance summary + Kms float64 `json:"kms"` + Actualkms float64 `json:"actualkms"` + Previouskms float64 `json:"previouskms"` + Cumulativekms float64 `json:"cumulativekms"` + Riderkms float64 `json:"riderkms"` + + // 🔹 Amount summary + Charges float64 `json:"charges"` + Deliveryamt float64 `json:"deliveryamt"` + + // 🔹 Collection summary + Quantity int `json:"quantity"` + Collectionamt float64 `json:"collectionamt"` + Collectedamt float64 `json:"collectedamt"` + Collectionstatus int `json:"collectionstatus"` +} + +type Ridersummary struct { + Userid int `json:"userid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Ridercontact string `json:"ridercontact"` + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + Totalorders int `json:"totalorders"` + Pending int `json:"pending"` + Assigned int `json:"assigned"` + Accepted int `json:"accepted"` + Picked int `json:"picked"` + Arrived int `json:"arrived"` + Active int `json:"active"` + Skipped int `json:"skipped"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` + Kms float64 `json:"kms" gorm:"type:numeric"` + Actualkms float64 `json:"actualkms" gorm:"type:numeric"` + Previouskms int `json:"previouskms"` + Cumulativekms int `json:"cumulativekms"` + Payondelivery float64 `json:"payondelivery" gorm:"type:numeric"` + Paylater float64 `json:"Paylater" gorm:"type:numeric"` + Deliveryamt float64 `json:"deliveryamt" gorm:"type:numeric"` + Status string `json:"status"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Collectedamt float64 `json:"collectedamt" gorm:"type:numeric"` + Collectionstatus float64 `json:"collectionstatus" gorm:"type:numeric"` +} + +type DeliveryQuery struct { + Tenantid int + Partnerid int + UserID int + Appuserid int + Applocationid int + Locationid int + Configid int + Fromdate string + ToDate string + Status string + Pageno int + Pagesize int + Conn *gorm.DB + Keyword string `json:"keyword"` +} diff --git a/models/invoice.go b/models/invoice.go new file mode 100644 index 0000000..85c830a --- /dev/null +++ b/models/invoice.go @@ -0,0 +1,89 @@ +package models + +type Tenantsales struct { + Salesid int `json:"salesid" gorm:"Primary_Key"` + Transactiondate string `json:"transactiondate"` + Duedate string `json:"duedate"` + Applocationid int `json:"applocationid"` + Invoiceid string `json:"invoiceid"` + Invoiceno string `json:"invoiceno"` + Tenantid int `json:"tenantid"` + Pricingtypeid int `json:"pricingtypeid"` + Pricingtype string `json:"pricingtype"` + Tenantname string `json:"tenantname"` + Contactperson string `json:"contactperson"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Itemcount int `json:"itemcount"` + Salesamount float32 `json:"salesamount"` + Discountpercent float32 `json:"discountpercent"` + Discountamt float32 `json:"discountamt"` + Taxpercent float32 `json:"taxpercent"` + Taxamount float32 `json:"taxamount"` + Totalamount float32 `json:"totalamount"` + Billstatus int `json:"billstatus"` + Billcancel int `json:"billcancel"` + Remarks string `json:"remarks"` + Referenceno string `json:"referenceno"` + Referencedate string `json:"referencedate" gorm:"default:NULL"` + Paymentremarks string `json:"paymentremarks"` + Status string `json:"status" gorm:"default:pending"` + Tenantsalesdetails []Tenantsalesdetails `json:"tenantsalesdetails" gorm:"ForeignKey:salesid"` +} + +type Tenantsalesdetails struct { + Salesdetailid int `json:"salesdetailid" gorm:"Primary_Key"` + Salesid int `json:"salesid" ` + Tenantid int `json:"tenantid"` + Particulars string `json:"particulars"` + Fromdate string `json:"fromdate"` + Todate string `json:"todate"` + Pricingtypeid int `json:"pricingtypeid"` + Pricingtype string `json:"pricingtype"` + Baserate float32 `json:"baserate"` + Quantity float32 `json:"quantity"` + Amount float32 `json:"amount"` + Othercharges float32 `json:"othercharges"` + Taxpercent float32 `json:"taxpercent"` + Taxamount float32 `json:"taxamount"` + Totalamount float32 `json:"totalamount"` + Billstatus int `json:"billstatus" gorm:"<-:false"` + Orderheaderid int `json:"orderheaderid" gorm:"<-:false"` + Orderid string `json:"orderid" gorm:"<-:false"` + Orderdate string `json:"orderdate" gorm:"<-:false"` + Pickup string `json:"pickup" gorm:"<-:false"` + Drop string `json:"drop" gorm:"<-:false"` +} + +type InvoiceOrders struct { + Deliveries int `json:"deliveries"` + Kms int `json:"kms"` + Actualkms int `json:"actualkms"` +} + +type InvoiceInsight struct { + Total float32 `json:"total"` + Totalcount int `json:"totalcount"` + Pending float32 `json:"pending"` + Pendingcount int `json:"pendingcount"` + Pendingpercent float32 `json:"pendingpercent"` + Confirmed float32 `json:"confirmed"` + Confirmedcount int `json:"confirmedcount"` + Confirmedpercent float32 `json:"confirmedpercent"` + Paid float32 `json:"paid"` + Paidcount int `json:"paidcount"` + Paidpercent float32 `json:"paidpercent"` + Overduecount int `json:"overduecount"` + Overdue float32 `json:"overdue"` + Overduepercent float32 `json:"overduepercent"` +} + +type InvoiceStatus struct { + Salesid int `json:"salesid" gorm:"Primary_Key"` + Referenceno string `json:"Referenceno"` + Referencedate string `json:"referencedate"` + Billstatus int `json:"billstatus"` + Paymentremarks string `json:"paymentremarks"` +} diff --git a/models/order.go b/models/order.go new file mode 100644 index 0000000..eedb5a1 --- /dev/null +++ b/models/order.go @@ -0,0 +1,460 @@ +package models + +type OrderInfo struct { + Orderheaderid int `json:"orderheaderid"` + Applocationid int `json:"applocationid"` + Applocation string `json:"applocation"` + Tenantid int `json:"tenantid"` + Partnerid int `json:"partnerid"` + Locationid int `json:"locationid"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Moduleid int `json:"moduleid"` + Configid int `json:"configid"` + Orderid string `json:"orderid"` + Orderdate string `json:"orderdate"` + Deliverydate string `json:"deliverydate"` + Orderstatus string `json:"orderstatus"` + Deliverystatus string `json:"deliverystatus"` + Deliveryamt float64 `json:"deliveryamt"` + Itemcount int `json:"itemcount"` + Ordernotes string `json:"ordernotes"` + Kms string `json:"kms"` + Actualkms string `json:"actualkms"` + Pending string `json:"Pending"` + Processing string `json:"processing"` + Ready string `json:"ready"` + Cancelled string `json:"cancelled"` + Delivered string `json:"delivered"` + Assigntime string `json:"assigntime"` + Starttime string `json:"starttime"` + Arrivaltime string `json:"arrivaltime"` + Pickuptime string `json:"pickuptime"` + Deliverytime string `json:"deliverytime"` + Canceltime string `json:"canceltime"` + Deliverycharge float64 `json:"deliverycharge"` + Orderamount float64 `json:"orderamount"` + Customerid int `json:"customerid"` + Pickupcustomer string `json:"pickupcustomer"` + Pickupcontactno string `json:"pickupcontactno"` + Pickuplocationid int `json:"pickuplocationid"` + Pickupaddress string `json:"pickupaddress"` + Pickupsuburb string `json:"pickupsuburb"` + Pickupcity string `json:"pickupcity"` + Pickuplat string `json:"pickuplat"` + Pickuplong string `json:"pickuplong"` + Pickupslot string `json:"pickupslot"` + Deliveryid int `json:"deliveryid"` + Deliverycustomerid int `json:"deliverycustomerid"` + Deliverycustomer string `json:"deliverycustomer"` + Deliverycontactno string `json:"deliverycontactno"` + Deliverylocationid int `json:"deliverylocationid"` + Deliveryaddress string `json:"deliveryaddress"` + Deliverysuburb string `json:"deliverysuburb"` + Droplat string `json:"droplat"` + Droplon string `json:"droplon"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Deliverytype string `json:"deliverytype"` + Paymenttype int `json:"paymenttype"` + Tenantname string `json:"tenantname"` + Tenanttoken string `json:"tenanttoken"` + Tenantsuburb string `json:"tenantsuburb"` + Tenantcity string `json:"tenantcity"` + Tenantcontactno string `json:"tenantcontactno"` + Tenantpostcode string `json:"tenantpostcode"` + Tenantaddress string `json:"tenantaddress"` + Locationname string `json:"locationname"` + Locationsuburb string `json:"locationsuburb"` + Locationcity string `json:"locationcity"` + Locationcontactno string `json:"locationcontactno"` + Locationaddress string `json:"locationaddress"` + Rider string `json:"rider"` + Ridercontactno string `json:"ridercontactno"` + Riderkms string `json:"riderkms"` + Smsdelivery int `json:"smsdelivery"` + Customertoken string `json:"customertoken"` + Ridertoken string `json:"ridertoken"` + Previouskms int `json:"previouskms" gorm:"default:0"` + Cumulativekms int `json:"cumulativekms" gorm:"default:0"` + Step int `json:"step" gorm:"default:0"` + Eta string `json:"eta" gorm:"type:varchar(100);default:''"` + Quantity int `json:"quantity" gorm:"default:0"` + Collectionamt float64 `json:"collectionamt" gorm:"default:0"` + Slab string `json:"slab"` + Pricingdate string `json:"pricingdate"` + Baseprice float64 `json:"baseprice"` + Minkm int `json:"minkm"` + Priceperkm float64 `json:"priceperkm"` + Maxkm int `json:"maxkm"` + Orders int `json:"orders"` + Othercharges float64 `json:"othercharges"` + Surgecharges float64 `json:"surgecharges"` +} + +// type TenantOrders struct { +// Orderheaderid int `json:"orderheaderid"` +// Tenantid int `json:"tenantid"` +// Locationid int `json:"locationid"` +// Categoryid int `json:"categoryid"` +// Subcategoryid int `json:"subcategoryid"` +// Moduleid int `json:"moduleid"` +// Configid int `json:"configid"` +// Orderid string `json:"orderid"` +// Customerid int `json:"customerid"` +// Customername string `json:"customername"` +// Contactno string `json:"contactno"` +// Deliverydate string `json:"deliverydate"` +// Orderstatus string `json:"orderstatus"` +// Starttime string `json:"starttime"` +// Arrivaltime string `json:"arrivaltime"` +// Pickuptime string `json:"pickuptime"` +// Deliverytime string `json:"deliverytime"` +// Deliveryaddress string `json:"deliveryaddress"` +// Deliverylat string `json:"deliverylat"` +// Deliverylong string `json:"deliverylong"` +// Pickupaddress string `json:"pickupaddress"` +// Pickuplat string `json:"pickuplat"` +// Pickuplong string `json:"pickuplong"` +// Droplat string `json:"droplat"` +// Droplon string `json:"droplon"` +// Notes string `json:"notes"` +// Ordernotes string `json:"ordernotes"` +// Locationname string `json:"locationname"` +// } + +type Ordersequences struct { + Sequenceid int `json:"sequenceid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Orderprefix string `json:"orderprefix" gorm:"default:ORD"` + Customerprefix string `json:"customerprefix" gorm:"default:CUS"` + Appointmentprefix string `json:"appointmentprefix" gorm:"default:ORD"` + Receiptprefix string `json:"receiptprefix" gorm:"default:REC"` + Paymentprefix string `json:"paymentprefix" gorm:"default:PAY"` +} + +type Orders struct { + Orderheaderid int `json:"orderheaderid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Moduleid int `json:"moduleid"` + Partnerid int `json:"partnerid"` + Configid int `json:"configid"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Orderid string `json:"orderid"` + Orderdate string `json:"orderdate,omitempty"` + Deliverytime string `json:"deliverytime"` + Deliverytype string `json:"deliverytype"` + Orderstatus string `json:"orderstatus"` + Pending string `json:"pending"` + Processing string `json:"processing"` + Ready string `json:"ready"` + Delivered string `json:"delivered"` + Cancelled string `json:"cancelled"` + Customerid int `json:"customerid"` + Pickupcustomer string `json:"pickupcustomer"` + Pickupcontactno string `json:"pickupcontactno"` + Pickuplandmark string `json:"pickuplandmark"` + Pickuplocationid int `json:"pickuplocationid"` + Pickupaddress string `json:"pickupaddress"` + Pickuplocation string `json:"pickuplocation"` + Pickupcity string `json:"pickupcity"` + Pickuplat string `json:"pickuplat"` + Pickuplong string `json:"pickuplong"` + Pickupslot string `json:"pickupslot"` + Deliveryid int `json:"deliveryid"` + Deliverylocationid int `json:"deliverylocationid"` + Deliverycustomer string `json:"deliverycustomer"` + Deliverycontactno string `json:"deliverycontactno"` + Deliverylandmark string `json:"deliverylandmark"` + Deliverylocation string `json:"deliverylocation"` + Deliverycity string `json:"deliverycity"` + Deliveryaddress string `json:"deliveryaddress"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Promotionid int `json:"promotionid"` + Promoname string `json:"promoname"` + Promoterms string `json:"promoterms"` + Promovalue int `json:"promovalue"` + Promoamount float64 `json:"promoamount" gorm:"type:numeric"` + Orderamount float64 `json:"orderamount" gorm:"type:numeric"` + Taxamount float64 `json:"taxamount" gorm:"type:numeric"` + Ordercharges float64 `json:"ordercharges" gorm:"type:numeric"` + Ordervalue float64 `json:"ordervalue" gorm:"type:numeric"` + Itemcount int `json:"itemcount"` + Paymenttype int `json:"paymenttype"` + Paymentstatus int `json:"paymentstatus"` + Deliverycharge float64 `json:"deliverycharge" gorm:"type:numeric"` + Ordernotes string `json:"ordernotes"` + Kms string `json:"kms"` + Remarks string `json:"remarks"` + Tenantuserid int `json:"tenantuserid"` + Partneruserid int `json:"partneruserid"` + Smsdelivery int `json:"smsdelivery" gorm:"default:0"` + Quantity int `josn:"quantity"` + Collectionamt float64 `json:"collectionamt" gorm:"type:numeric"` + Items []OrderDetail `json:"items" gorm:"-"` +} + +type OrderDetail struct { + Orderdetailid int `json:"orderdetailid" gorm:"primaryKey;autoIncrement"` + Orderheaderid int `json:"orderheaderid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Productid int `json:"productid"` + Productname string `json:"productname"` + Productdescription string `json:"productdescription"` + Supplyqty float64 `json:"supplyqty"` + Balanceqty float64 `json:"balanceqty"` + Orderqty float64 `json:"orderqty"` + Price float64 `json:"price"` + Unitid int `json:"unitid"` + Unitname string `json:"unitname"` + Productaddonid int `json:"productaddonid"` + Addontypeid int `json:"addontypeid"` + Productmapid int `json:"productmapid"` + Productvariantid int `json:"productvariantid"` + Productaddondescription string `json:"productaddondescription"` + Discountid int `json:"discountid"` + Discountname string `json:"discountname"` + Discountcode string `json:"discountcode"` + Discountterms string `json:"discountterms"` + Discountpercentage float64 `json:"discountpercentage"` + Discountamount float64 `json:"discountamount"` + Landingamount float64 `json:"landingamount"` + Taxpercentage float64 `json:"taxpercentage"` + Taxamount float64 `json:"taxamount"` + Productsumprice float64 `json:"productsumprice"` + Itemstatus string `json:"itemstatus"` + Delivered string `json:"delivered"` + Orderamount float64 `json:"-" gorm:"-"` + Productimage string `json:"productimage" gorm:"-"` // only for JSON +} + +// ✅ GORM will now use `orderdetails` instead of `order_details` +func (OrderDetail) TableName() string { + return "orderdetails" +} + +type OrderDetails struct { + Orderdetailid int `json:"orderdetailid" gorm:"primaryKey;autoIncrement"` + Orderheaderid int `json:"orderheaderid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Productid int `json:"productid"` + Productname string `json:"productname"` + Productdescription string `json:"productdescription"` + Supplyqty float64 `json:"supplyqty"` + Balanceqty float64 `json:"balanceqty"` + Orderqty float64 `json:"orderqty"` + Price float64 `json:"price"` + Unitid int `json:"unitid"` + Unitname string `json:"unitname"` + Productaddonid int `json:"productaddonid"` + Addontypeid int `json:"addontypeid"` + Productmapid int `json:"productmapid"` + Productvariantid int `json:"productvariantid"` + Productaddondescription string `json:"productaddondescription"` + Discountid int `json:"discountid"` + Discountname string `json:"discountname"` + Discountcode string `json:"discountcode"` + Discountterms string `json:"discountterms"` + Discountpercentage float64 `json:"discountpercentage"` + Discountamount float64 `json:"discountamount"` + Landingamount float64 `json:"landingamount"` + Taxpercentage float64 `json:"taxpercentage"` + Taxamount float64 `json:"taxamount"` + Totaltaxamount float64 `json:"totaltaxamount"` + Productsumprice float64 `json:"productsumprice"` + Itemstatus string `json:"itemstatus"` + Delivered string `json:"delivered"` + Orderamount float64 `json:"Orderamount"` // 👈 Correct way + Productimage string `json:"productimage"` // only for JSON +} + +func (OrderDetails) TableName() string { + return "orderdetails" +} + +type Customerorder struct { + Orders Orders `json:"orders"` + Pickup Customers `json:"pickup"` + Drop Customers `json:"drop"` +} + +type CustomerorderV2 struct { + Orders Orders `json:"orders"` +} + +type Updateorderstatus struct { + Orderheaderid int `json:"Orderheaderid"` + Orderstatus string `json:"Orderstatus"` + Pending string `json:"pending"` + Processing string `json:"processing"` + Cancelled string `json:"cancelled"` + Delivered string `json:"delivered"` + Userid int `json:"userid"` +} + +type Ordersummary struct { + Total int `json:"total"` + Created int `json:"created"` + Pending int `json:"pending"` + Processing int `json:"processing"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` + Locationname string `json:"locationname"` + Applocationid string `json:"applocationid"` +} + +type Ordersummarydaily struct { + Total int `json:"total"` + Created int `json:"created"` + Pending int `json:"pending"` + Processing int `json:"processing"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` +} + +type Ordersummarylocation struct { + Total int `json:"total"` + Created int `json:"created"` + Pending int `json:"pending"` + Processing int `json:"processing"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` +} +type OrderInsight struct { + Applocationid int `json:"applocationid" gorm:"Primary_Key"` + Locationname string `json:"locationname"` + Ordermonths *Ordermonths `json:"ordermonths" gorm:"-"` +} + +type OrderInsightv1 struct { + Locationid int `json:"locationid" gorm:"Primary_Key"` + Locationname string `json:"locationname"` + Ordermonths *Ordermonths `json:"ordermonths" gorm:"-"` +} + +type Ordermonths struct { + Jan int `json:"jan"` + Feb int `json:"feb"` + Mar int `json:"mar"` + Apr int `json:"apr"` + May int `json:"may"` + Jun int `json:"jun"` + Jul int `json:"jul"` + Aug int `Json:"aug"` + Sep int `Json:"sep"` + Oct int `Json:"oct"` + Nov int `Json:"nov"` + Dece int `Json:"dece"` + // Applocationid int `json:"applocationid"` +} + +type CustomerOrder struct { + Orderheaderid int `json:"orderheaderid" gorm:"Primary_Key"` + Applocationid int `json:"applocationid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Partnerid int `json:"partnerid"` + Configid int `json:"configid"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Moduleid int `json:"moduleid"` + Orderid string `json:"orderid"` + Orderstatus string `json:"orderstatus"` + Orderdate string `json:"orderdate,omitempty"` + Ordernotes string `json:"ordernotes"` + Itemcount int `json:"itemcount"` + Deliverytime string `json:"deliverytime"` // alias for delivered + Pending string `json:"pending"` + Processing string `json:"processing"` + Ready string `json:"ready"` + Delivered string `json:"delivered"` // original delivered column + Cancelled string `json:"cancelled"` + Deliverycharge float32 `json:"deliverycharge"` + Kms string `json:"kms"` + + Customerid int `json:"customerid"` + Pickupaddress string `json:"pickupaddress"` + Pickuplat string `json:"pickuplat"` + Pickuplong string `json:"pickuplong"` + Pickupcustomer string `json:"pickupcustomer"` + Pickupcontactno string `json:"pickupcontactno"` + Pickuplocation string `json:"pickupsuburb"` // alias + Pickupcity string `json:"pickupcity"` + + Deliveryid int `json:"deliverycustomerid"` // alias + Deliveryaddress string `json:"deliveryaddress"` + Deliverylat string `json:"deliverylat"` + Deliverylong string `json:"deliverylong"` + Deliverytype string `json:"deliverytype"` + Deliverycustomer string `json:"deliverycustomer"` + Deliverycontactno string `json:"deliverycontactno"` + Deliverylocation string `json:"deliverysuburb"` // alias + Deliverycity string `json:"deliverycity"` + Paymenttype int `json:"paymenttype"` + Smsdelivery int `json:"smsdelivery"` + + // Tenant Info + Tenantname string `json:"tenantname"` + Tenanttoken string `json:"tenanttoken"` + Tenantcontactno string `json:"tenantcontactno"` + Tenantpostcode string `json:"tenantpostcode"` + Tenantsuburb string `json:"tenantsuburb"` + Tenantcity string `json:"tenantcity"` + + // Location Info + Locationname string `json:"locationname"` + Locationcontactno string `json:"locationcontactno"` + Locationpostcode string `json:"locationpostcode"` + Locationsuburb string `json:"locationsuburb"` + Locationcity string `json:"locationcity"` + + // App Location + Applocation string `json:"applocation"` + + // Delivery Info (from `deliveries` table) + Deliverystatus string `json:"deliverystatus"` + DeliveryUserID int `json:"deliveryid"` // alias + Assigntime string `json:"assigntime"` + Starttime string `json:"starttime"` + Arrivaltime string `json:"arrivaltime"` + Pickuptime string `json:"pickuptime"` + Finaldeliverytime string `json:"finaldeliverytime"` // alias for f.deliverytime + Canceltime string `json:"canceltime"` + + // Actualkms sql.NullFloat64 `json:"actualkms"` + // Riderkms sql.NullFloat64 `json:"riderkms"` + // Deliveryamt sql.NullFloat64 `json:"deliveryamt"` + Droplat string `json:"droplat"` + Droplon string `json:"droplon"` + + // Rider + Rider string `json:"rider"` + Orderamount float64 `json:"orderamount"` + Taxamount float64 `json:"taxamount"` + + // Items if applicable + OrderDetails []OrderDetails `json:"orderdetails" gorm:"-"` +} + +type CustomerOrderLocation struct { + Locationname string `json:"locationname" gorm:"column:locationname"` + Ordercount int `json:"ordercount" gorm:"column:ordercount"` + Contactno string `json:"contactno" gorm:"column:contactno"` + Address string `json:"address" gorm:"column:address"` + Suburb string `json:"suburb" gorm:"column:suburb"` + City string `json:"city" gorm:"column:city"` + State string `json:"state" gorm:"column:state"` + Postcode string `json:"postcode" gorm:"column:postcode"` + Latitude float64 `json:"latitude" gorm:"column:latitude"` + Longitude float64 `json:"longitude" gorm:"column:longitude"` +} diff --git a/models/partner.go b/models/partner.go new file mode 100644 index 0000000..b07d64b --- /dev/null +++ b/models/partner.go @@ -0,0 +1,353 @@ +package models + +import ( + "time" +) + +type RiderPoolResult struct { + Status bool `json:"status"` + Code int `json:"code"` + Message string `json:"message"` + Details []RiderPool `json:"Details"` +} + +type RiderPool struct { + Poolid int `json:"poolid"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Userfcmtoken string `json:"userfcmtoken"` + Identificationno string `json:"identificationno"` + Vehicleid int `json:"vehicleid"` + Vehiclename string `json:"vehiclename"` + Registrationno string `json:"registrationno"` + Licenseno string `json:"licenseno"` + Shiftid int `json:"shiftid"` + Starttime string `json:"starttime"` + Endtime string `json:"endtime"` + Shifthours float32 `json:"shifthours"` + Basefare float32 `json:"basefare"` + Fuelcharge float32 `json:"fuelcharge"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` +} + +type Partnerinfo struct { + Partnerid int `json:"partnerid" gorm:"Primary_Key"` + Partnertypeid int `json:"partnertypeid"` + Applocationid int `json:"applocationid"` + Partnername string `json:"partnername"` + Registrationno string `json:"registrationno"` + Primarycontact string `json:"primartcontact"` + Primaryemail string `json:"primaryemail"` + Contactno string `json:"contactno"` + Bizcategoryid int `json:"bizcategoryid"` + Bizsubcategoryid int `json:"bizsubcategoryid"` + Adderss string `json:"address"` + Suburb string `json:"suburb"` + State string `json:"state"` + City string `json:"city"` + Postcode string `json:"postcode"` + Partnerinfo string `json:"partnerinfo"` + Partnerimage string `json:"partnerimage"` +} + +type AdminToken struct { + Userid int `json:"userid"` + Userfcmtoken string `json:"userfcmtokem"` + Notify string `json:"notify"` + Applocationid int `json:"applocationid"` + Closetime string `json:"closetime"` + Opentime string `json:"opentime"` + Tenantid int `json:"tenantid"` +} + +type Rider struct { + Userid int `json:"userid" gorm:"primaryKey"` + Authname string `json:"authname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Pin int `json:"pin"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Userfcmtoken string `json:"userfcmtoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Name string `json:"name"` + Capacity int `json:"capacity"` + Rangekm int `json:"rangekm"` + Batterypercentage int `json:"batterypercentage"` + Type string `json:"type"` + Status string `json:"status" gorm:"column:status;default:InActive"` + Ridersettings Ridersettings `json:"ridersettings" gorm:"-"` +} + +type RiderDetails struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Password string `json:"password"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Pin int `json:"pin"` + Partnerid int `json:"partnerid"` + Riderid int `json:"riderid"` + Identificationno string `json:"identificationno"` + Shiftid int `json:"shiftid"` + Starttime string `json:"starttime"` + Endtime string `json:"endtime"` + Shifthours float32 `json:"shifthours"` + Basefare float32 `json:"basefare"` + Additionalcharges float32 `json:"additionalcharges"` + Additionalkm float32 `json:"additionalkm"` + Orders int `json:"orders"` + Fuelcharge float32 `json:"fuelcharge"` + Accountno string `json:"accountno"` + Accountname string `json:"accountname"` + Accounttypeid int `json:"accounttypeid"` + Accounttype string `json:"accounttype"` + Bankname string `json:"bankname"` + Ifsccode string `json:"ifsccode"` + Branch string `json:"branch"` + Vehicleid int `json:"vehicleid"` + Vehiclename string `json:"vehiclename"` + Vehicleno string `json:"vehicleno"` + Model string `json:"model"` + Color string `json:"color"` + Licenseno string `json:"licenseno"` + Insuranceno string `json:"insuranceno"` + Insurancedate string `json:"insurancedate"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` +} + +type Ridersettings struct { + Riderid int `json:"riderid" grom:"Primary_Key"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Shiftid int `json:"shiftid"` + Identificationno string `json:"identificationno"` + Basefare float32 `json:"basefare"` + Additionalkm float32 `json:"additionalkm"` + Additionalcharges float32 `json:"additionalcharges"` + Accountno string `json:"accountno"` + Accountname string `json:"accountname"` + Accounttypeid int `json:"accounttypeid"` + Accounttype string `json:"accounttype"` + Bankname string `json:"bankname"` + Ifsccode string `json:"ifsccode"` + Branch string `json:"branch"` + Vehicleid int `json:"vehicleid"` + Vehiclename string `json:"vehiclename"` + Vehicleno string `json:"vehicleno"` + Model string `json:"model"` + Color string `json:"color"` + Licenseno string `json:"licenseno"` + Insuranceno string `json:"insuranceno"` + Insurancedate string `json:"insurancedate"` + Status int `json:"status"` +} + +type Ridershifts struct { + Shiftid int `json:"shiftid" gorm:"Primary_Key"` + Shiftdate string `json:"shiftdate"` + Starttime string `json:"starttime"` + Endtime string `json:"endtime"` + Shifthours float32 `json:"shifthours"` + Basefare float32 `json:"basefare"` + Additionalkm float32 `json:"additionalkm"` + Additionalcharges float32 `json:"additionalcharges"` + Orders int `json:"orders"` + Fuelcharge float32 `json:"fuelcharge"` + Shiftname string `json:"shiftname" gorm:"<-:false"` +} + +type Riderlogs struct { + Logid int `json:"logid" gorm:"Primary_Key"` + Logdate string `json:"logdate"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Shiftid int `json:"shiftid"` + Shifthours float64 `json:"shifthours" gorm:"type:numeric"` + Login string `json:"login,omitempty" gorm:"column:login;default:NULL"` + Logout string `json:"logout,omitempty" gorm:"column:logout;default:NULL"` + Latitude string `json:"latitude"` + Workhours float64 `json:"workhours" gorm:"type:numeric"` + Shorthours float64 `json:"shorthours" gorm:"type:numeric"` + Logstatus int `json:"logstatus"` + Longitude string `json:"longitude"` + Status string `json:"status"` + Breakhours float32 `json:"breakhours" gorm:"<-:false"` + Onduty int `json:"onduty" gorm:"<-:false"` + + Firstname string `json:"firstname" gorm:"<-:false"` + Lastname string `json:"lastname" gorm:"<-:false"` + Username string `json:"username"` + Contactno string `json:"contactno"` + Userfcmtoken string `json:"userfcmtoken"` + Applocationid int `json:"applocationid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Orderid string `json:"orderid"` + + Raw_latitude string `json:"raw_latitude"` + Raw_longitude string `json:"raw_longitude"` + Velocity_lat string `json:"velocity_lat"` + Velocity_long string `json:"velocity_long"` + Speed string `json:"speed"` + Heading string `json:"heading"` +} + +type Riderlogsv1 struct { + Logid int `json:"logid" gorm:"Primary_Key"` + Logdate string `json:"logdate"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Shiftid int `json:"shiftid"` + Shifthours float32 `json:"shifthours"` + Login string `json:"login,omitempty" gorm:"column:login;default:NULL"` + Logout string `json:"logout,omitempty" gorm:"column:logout;default:NULL"` + Latitude string `json:"latitude"` + Workhours float32 `json:"workhours"` + Shorthours float32 `json:"shorthours"` + Logstatus int `json:"logstatus"` + Longitude string `json:"longitude"` + Breakhours float32 `json:"breakhours" ` + Onduty int `json:"onduty" ` + Status string `json:"status"` + Firstname string `json:"firstname" ` + Lastname string `json:"lastname" ` + Contactno string `json:"contactno" ` + Username string `json:"username" ` + Userfcmtoken string `json:"userfcmtoken"` + Applocationid int `json:"applocationid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Orderid string `json:"orderid"` +} + +type Riderbreaks struct { + Breakid int `json:"breakid" gorm:"Primary_Key"` + Logid int `json:"logid"` + Breakdate string `json:"breakdate"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Shiftid int `json:"shiftid"` + Breakstart string `json:"breakstart,omitempty" gorm:"column:breakstart;default:NULL"` + Breakend string `json:"breakend,omitempty" gorm:"column:breakend;default:NULL"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Breakhours float32 `json:"breakhours"` +} + +type RiderlogDetails struct { + Logid int `json:"logid"` + Logdate string `json:"logdate"` + Userid int `json:"userid"` + Username string `json:"username"` + Partnerid int `json:"partnerid"` + Shiftid int `json:"shiftid"` + Shifthours float32 `json:"shifthours"` + Login string `json:"login"` + Logout string `json:"logout"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Workhours float32 `json:"workhours"` + Shorthours float32 `json:"shorthours"` + Breakhours float32 `json:"breakhours"` + Logstatus int `json:"logstatus"` +} + +type Userpools struct { + Poolid int `json:"poolid" gorm:"primaryKey;autoIncrement"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Vehicleid int `json:"vehicleid"` + Onduty int `json:"onduty" gorm:"column:onduty;default:0"` + Status string `json:"status" gorm:"column:status;default:idle"` +} + +type Locationconfigs struct { + Applocationid int `json:"applocationid"` + Configid int `json:"configid"` + Locationname string `json:"locationname"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Status string `json:"status"` +} + +type DeliveryStats struct { + Today int `json:"today"` + Week int `json:"week"` + Month int `json:"month"` + Total int `json:"total"` + Cancelled int `json:"cancelled"` +} + +type RiderPricing struct { + Shiftid int `json:"shiftid"` + Basefare float32 `json:"basefare"` + Additionalcharges float32 `json:"additionalcharges"` + Fuelcharge float32 `json:"fuelcharge"` + Applocationid int `json:"applocationid"` + Locationname string `json:"locationname"` +} + +type RiderKms struct { + Day string `json:"day"` + Kms float64 `json:"kms"` +} + +type RiderWeeklyKms struct { + Details []RiderKms `json:"details"` + TotalKms float64 `json:"totalkms"` + OverallKms float64 `json:"overallkms"` +} + +type RiderSupport struct { + Ridersupportid int `json:"ridersupportid" gorm:"primaryKey;autoIncrement"` + Userid int `json:"userid"` + Category string `json:"category"` + Priority string `json:"priority"` + Subject string `json:"subject"` + Issue string `json:"issue"` + Image string `json:"image"` + Created time.Time `json:"created" gorm:"autoCreateTime"` + Updated time.Time `json:"updated" gorm:"autoUpdateTime"` +} + +type RidersSummary struct { + Active int `json:"active"` + Inactive int `json:"inactive"` + Total int `json:"total"` +} diff --git a/models/payments.go b/models/payments.go new file mode 100644 index 0000000..2d66a31 --- /dev/null +++ b/models/payments.go @@ -0,0 +1,46 @@ +package models + +type Paymentrequests struct { + Requestid int `json:"requestid" gorm:"Primary_Key"` + Partnerid int `json:"partnerid"` + Requestdate string `json:"requestdate"` + Referenceno string `json:"referenceno"` + Apptypeid int `json:"apptypeid"` + Requesttype int `json:"requestype"` + Reason string `json:"reason"` + Userid int `json:"userid"` + Requestor string `json:"requestor"` + Amount float32 `json:"amount"` + Accountno string `json:"accountno"` + Bankname string `json:"bankname"` + Ifsccode string `json:"ifsccode"` + Status int `json:"status"` +} + +type RequestInfo struct { + Requestid int `json:"requestid"` + Requestdate string `json:"requestdate"` + Referenceno string `json:"referenceno"` + Apptypeid int `json:"apptypeid"` + Requesttype int `json:"requestype"` + Reason string `json:"reason"` + Userid int `json:"userid"` + Requestor string `json:"requestor"` + Amount float32 `json:"amount"` + Accountno string `json:"accountno"` + Bankname string `json:"bankname"` + Ifsccode string `json:"ifsccode"` + Paymentref string `json:"paymentref"` + Paymentdate string `json:"paymentdate"` + Status int `json:"status"` +} + +type Paymentdetails struct { + Paymentdetailsid int `json:"Paymentdetailsid" gorm:"Primary_Key"` + Requestid int `json:"requestid"` + Userid int `json:"userid"` + Paymentdate string `json:"paymentdate"` + Referenceno string `json:"referenceno"` + Amount float32 `json:"amount"` + Status int `json:"status"` +} diff --git a/models/platform.go b/models/platform.go new file mode 100644 index 0000000..14e8fd6 --- /dev/null +++ b/models/platform.go @@ -0,0 +1,23 @@ +package models + +type AppModules struct { + Moduleid int `json:"moduleid"` + Modulename string `json:"modulename"` + Logourl string `json:"logourl"` + Business int `json:"business"` + Orders int `json:"orders"` +} + +type Smsproviders struct { + Providerid int `json:"providerid"` + Templatetypeid int `json:"templatetypeid"` + Templatename string `json:"templatename"` + Templateid int `json:"templateid"` + Providerapi string `json:"providerapi"` + Content string `json:"content"` + Defaultprovider int `json:"defaultprovider"` + Passkey int `json:"passkey"` +} + + +// https://msg.lionsms.com/api/smsapi?key=e57f5c9679af26077be1a7eadabb1b2a&route=7&sender=NEARLE&number=&templateid=1107168725266104678&sms= \ No newline at end of file diff --git a/models/product.go b/models/product.go new file mode 100644 index 0000000..fed0bbc --- /dev/null +++ b/models/product.go @@ -0,0 +1,258 @@ +package models + +import ( + "time" +) + +type Products struct { + Productid int `json:"productid"` + AppLocationid int `json:"applocationid" gorm:"column:applocationid"` + Tenantid *int `json:"tenantid,omitempty"` + Categoryid int `json:"categoryid"` + Categoryname string `json:"categoryname" gorm:"->"` + Subcategoryid *int `json:"subcategoryid,omitempty"` + Subcategoryname string `json:"Subcategoryname" gorm:"->"` + Catalogueid *int `json:"catalogueid,omitempty"` + Addonid *int `json:"addonid,omitempty"` + Discountid *int `json:"discountid,omitempty"` + Pricingid *int `json:"pricingid,omitempty"` + Productname *string `json:"productname,omitempty"` + Productimage *string `json:"productimage,omitempty"` + Productdesc *string `json:"productdesc,omitempty"` + Productsku *string `json:"productsku,omitempty"` + Brandid *int `json:"brandid,omitempty"` + Productbrand *string `json:"productbrand,omitempty"` + Productunit *string `json:"productunit,omitempty"` + Unitvalue *string `json:"unitvalue,omitempty"` + Toppicks *string `json:"toppicks,omitempty"` + Productcost *float64 `json:"productcost,omitempty"` + Taxamount *float64 `json:"taxamount,omitempty"` + Taxpercent *float64 `json:"taxpercent,omitempty"` + Producttax *int `json:"producttax" gorm:"default:0"` + Productstock *int `json:"productstock" gorm:"default:0"` + Productcombo *int `json:"productcombo" gorm:"default:0"` + Variants int `json:"variants" gorm:"default:0"` + Quantity int `json:"quantity"` + Retailprice float64 `json:"retailprice,omitempty"` + Diffprice float64 `json:"diffprice,omitempty"` + Diffpercent float64 `json:"diffpercent,omitempty"` + Othercost float64 `json:"othercost,omitempty"` + Approve *int `json:"approve" gorm:"default:0"` + Productstatus string `json:"productstatus" gorm:"default:available"` +} + +type ProductCategory struct { + Categoryid int `json:"categoryid"` + Moduleid int `json:"moduleid"` + Tenantid *int `json:"tenantid,omitempty"` + Categorytypeid *int `json:"categorytypeid,omitempty"` + Categoryname *string `json:"categoryname,omitempty"` + Image *string `json:"image,omitempty"` + Catalougecategoryid *int `json:"catalougecategoryid,omitempty"` + Sortorder *int `json:"sortorder,omitempty"` + Status string `json:"status"` + Createdby *int `json:"createdby,omitempty"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +type Productstockstatement struct { + Productid int `json:"productid"` + Productname string `json:"productname"` + Productimage string `json:"productimage"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Productunit string `json:"productunit"` + Unitvalue string `json:"unitvalue"` + Productcost float32 `json:"productcost"` + Taxpercent float32 `json:"taxpercent"` + Taxamount float32 `json:"taxamount"` + Retailprice float32 `json:"retailprice"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Opening int `json:"opening"` + Credit int `json:"credit"` + Debit int `json:"debit"` + Closing int `json:"closing"` +} + +type Productcount struct { + Total int `json:"total"` + Available int `json:"available"` + Outofstock int `json:"outofstock"` +} + +type ProductSubCategory struct { + Subcatid int `json:"subcatid"` + Categoryid int `json:"categoryid,omitempty"` + Tenantid int `json:"tenantid,omitempty"` + Subcatname string `json:"subcatname,omitempty"` + Image string `json:"image,omitempty"` + Status string `json:"status,omitempty"` + Sortorder int `json:"sortorder,omitempty"` + Createdby int `json:"createdby,omitempty"` + Created time.Time `json:"created,omitempty"` + Updated time.Time `json:"updated,omitempty"` +} + +// Struct to match the `productsubcategories` table +type Subcategory struct { + Subcategoryid int `json:"subcategoryid" gorm:"column:subcatid"` + Subcategoryname string `json:"subcategoryname" gorm:"column:subcatname"` + Categoryid int `json:"categoryid" gorm:"column:categoryid"` +} + +// Response format +type SubcategoryProductResponse struct { + SubcategoryID int `json:"subcategoryid"` + SubcategoryName string `json:"subcategoryname"` + Products []Products `json:"products"` +} +type TenantInfo struct { + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Address string `json:"address"` + Licenseno string `json:"licenseno"` + Primaryemail string `json:"primaryemail"` + Primarycontact string `json:"primarycontact"` + Pickuplocationid int `json:"pickuplocationid"` + Applocationid int `json:"applocationid"` + Suburb string `json:"suburb"` + City string `json:"city"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Postcode string `json:"postcode"` + Tenantimage string `json:"tenantimage"` + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + Subcategoryid int `json:"subcategoryid"` + Categoryid int `json:"categoryid"` + Registrationno string `json:"registrationno"` +} + +type ProductListResponse struct { + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Address string `json:"address"` + Licenseno string `json:"licenseno"` + Primaryemail string `json:"primaryemail"` + Pickupcontactno string `json:"primarycontact"` + Applocationid int `json:"applocationid"` + Pickuplocationid int `json:"pickuplocationid"` + Pickupsuburb string `json:"suburb"` + Pickupcity string `json:"city"` + Pickuplat string `json:"pickuplat"` + Pickuplong string `json:"pickuplong"` + Tenantpostcode string `json:"postcode"` + Locationname string `json:"locationname"` + Categoryname string `json:"categoryname"` + Products []Products `json:"products"` +} + +type Tenantproducts struct { + Tenant TenantInfo `json:"tenant"` + Products []Products `json:"products"` +} + +type Productvariant struct { + Variantid int `json:"variantid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Variantname string `json:"variantname"` + Categoryid int `json:"categoryid" gorm:"default:0"` + Categoryname string `json:"categoryname" gorm:"-"` + Subcategoryid int `json:"subcategoryid"` + Status string `json:"status" gorm:"default:active"` +} + +type Productlocations struct { + Productlocationid int `json:"productlocationid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Productid int `json:"productid"` + Catlougeid int `json:"catlougeid"` + Minquantity int `json:"minquantity" gorm:"default:0"` + Maxquantity int `json:"maxquantity" gorm:"default:0"` + Price float32 `json:"price" gorm:"default:0.0"` + Quantity int `json:"quantity" gorm:"<-:false"` + Stocktype string `json:"stocktype" gorm:"<-:false"` + Status string `json:"status"` +} + +type Productstock struct { + Productstockid int `json:"productstockid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Stockdate time.Time `json:"stockdate"` + Locationid int `json:"locationid"` + Productid int `json:"productid"` + Quantity int `json:"quantity"` + Stocktype string `json:"stocktype"` + Status string `json:"status"` +} + +type Productstocks struct { + Productstockid int `json:"productstockid" gorm:"Primary_Key"` + Locationid int `json:"locationid"` + Tenantid int `json:"tenantid"` + Stockdate string `json:"stockdate"` + Productid int `json:"productid"` + Quantity int `json:"quantity"` + Stocktype string `json:"stocktype"` + Status string `json:"status"` + AppLocationid int `json:"applocationid"` + Categoryid int `json:"categoryid"` + Categoryname string `json:"categoryname" gorm:"->"` + Subcategoryid int `json:"subcategoryid,omitempty"` + Subcategoryname string `json:"Subcategoryname" ` + Productname string `json:"productname,omitempty"` + Productimage string `json:"productimage,omitempty"` + Productdesc *string `json:"productdesc,omitempty"` + Productsku *string `json:"productsku,omitempty"` + Brandid *int `json:"brandid,omitempty"` + Productbrand *string `json:"productbrand,omitempty"` + Productunit *string `json:"productunit,omitempty"` + Unitvalue *string `json:"unitvalue,omitempty"` + Toppicks *string `json:"toppicks,omitempty"` + Productcost *float64 `json:"productcost,omitempty"` + Taxamount *float64 `json:"taxamount,omitempty"` + Taxpercent *float64 `json:"taxpercent,omitempty"` + Producttax *int `json:"producttax,omitempty"` + Productstock *int `json:"productstock,omitempty"` + Productcombo *int `json:"productcombo,omitempty"` + Variants int `json:"variants"` + Retailprice float64 `json:"retailprice,omitempty"` + Diffprice float64 `json:"diffprice,omitempty"` + Diffpercent float64 `json:"diffpercent,omitempty"` + Othercost float64 `json:"othercost,omitempty"` + Approve *int `json:"approve"` +} + +type SubCategorySummary struct { + SubCategoryID int `json:"subcategoryid"` + SubCategoryName string `json:"subcatname"` + Image string `json:"image"` + ProductCount int `json:"productcount"` +} + +type ProductSummary struct { + Subcategoryid int `json:"subcategoryid"` + Subcategroyname string `json:"subcategroyname"` + Image string `json:"image"` + Productcount int `json:"productcount"` +} + +type ProductDiscount struct { + Discountid int `json:"discountid" gorm:"column:discountid;primaryKey"` + Discounttypeid int `json:"discounttypeid" gorm:"column:discounttypeid"` + Tenantid int `json:"tenantid" gorm:"column:tenantid"` + Moduleid int `json:"moduleid" gorm:"column:moduleid"` + Productid int `json:"productid" gorm:"column:productid"` + Locationid []string `json:"locationid" gorm:"-"` + LocationidStr string `json:"-" gorm:"column:locationid"` + Discountname string `json:"discountname" gorm:"column:discountname"` + Discountcode string `json:"discountcode" gorm:"column:discountcode"` + Discountterms string `json:"discountterms" gorm:"column:discountterms"` + Discountvalue float64 `json:"discountvalue" gorm:"column:discountvalue"` + Startdate string `json:"startdate" gorm:"column:startdate"` + Enddate string `json:"enddate" gorm:"column:enddate"` + Status string `json:"status" gorm:"column:status"` +} diff --git a/models/tenant.go b/models/tenant.go new file mode 100644 index 0000000..bd8816a --- /dev/null +++ b/models/tenant.go @@ -0,0 +1,410 @@ +package models + +import "time" + +type TenantResult struct { + Status bool `json:"status"` + Code int `json:"code"` + Message string `json:"message"` + Details Tenants `json:"Details"` +} + +type Tenants struct { + Tenantid int `json:"tenantid" gorm:"Primary_Key"` + Tenantname string `json:"tenantname"` + Configid int `json:"configid"` + Partnerid int `json:"partnerid"` + Moduleid int `json:"moduleid"` + Tenanttype string `json:"tenanttype"` + Registrationno string `json:"registrationno"` + Tenanttoken string `json:"tenanttoken"` + Companyname string `json:"companyname"` + Devicetype string `json:"devicetype"` + Deviceid string `json:"deviceid"` + Firstname string `json:"firstname"` + Primaryemail string `json:"primaryemail"` + Primarycontact string `json:"primarycontact"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Tenantimage string `json:"tenantimage"` + Tenantinfo string `json:"tenantinfo"` + Paymode1 int `json:"paymode1"` + Paymode2 int `json:"paymode2"` + Promotion int `json:"promotion"` + Minorder int `json:"minorder"` + Applocationid int `json:"applocationid"` + Approved *int `json:"approved" gorm:"default:0"` + Status string `json:"status" gorm:"default:Active"` + Partneruserid int `json:"partneruserid"` + Tenantlocations Tenantlocations `json:"tenantlocations" gorm:"ForeignKey:tenantid"` + Tenantsubscriptions Tenantsubscriptions `json:"tenantsubscriptions" gorm:"ForeignKey:tenantid"` +} + +type TenantUser struct { + Tenantid int `json:"tenantid" gorm:"Primary_Key"` + Tenantname string `json:"tenantname"` + Tenanttype string `json:"tenanttype"` + Registrationno string `json:"registrationno"` + Tenanttoken string `json:"tenanttoken"` + Companyname string `json:"companyname"` + Primaryperson string `json:"primaryperson"` + Primaryemail string `json:"primaryemail"` + Primarycontact string `json:"primarycontact"` + Bizcategoryid int `json:"Bizcategoryid"` + Bizsubcategoryid int `json:"Bizsubcategoryid"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Tenantimage string `json:"tenantimage"` + Tenantinfo string `json:"tenantinfo"` + Paymode1 int `json:"paymode1"` + Paymode2 int `json:"paymode2"` + Promotion int `json:"promotion"` + Partnerid int `json:"partnerid"` + Minorder int `json:"minorder"` + Applocationid int `json:"applocationid"` + Approved int `json:"approved"` + Moduleid int `json:"moduleid"` + Tenantlocations Tenantlocations `json:"tenantlocations" gorm:"ForeignKey:tenantid"` + Tenantsubscriptions Tenantsubscriptions `json:"tenantsubscriptions" gorm:"ForeignKey:tenantid"` +} + +type Tenantinfo struct { + Tenantid int `json:"tenantid" gorm:"Primary_Key"` + Locationid int `json:"locationid"` + Tenantname string `json:"tenantname"` + Tenanttype string `json:"tenanttype"` + Registrationno string `json:"registrationno"` + Tenanttoken string `json:"tenanttoken"` + Companyname string `json:"companyname"` + Primaryemail string `json:"primaryemail"` + Primarycontact string `json:"primarycontact"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Tenantimage string `json:"tenantimage"` + Tenantinfo string `json:"tenantinfo"` + Paymenttype int `json:"paymenttype"` + Paymode1 int `json:"paymode1"` + Paymode2 int `json:"paymode2"` + Promotion int `json:"promotion"` + Partnerid int `json:"partnerid"` + Minorder int `json:"minorder"` + Applocationid int `json:"applolcationid"` + Applocation string `json:"applocation"` + Approved int `json:"approved"` + Moduleid int `json:"moduleid"` + Categoryname string `json:"categoryname"` + Subcategoryname string `json:"subcategoryname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Accountname string `json:"Accountname"` + Status string `json:"status"` + Allocationid int `json:"allocationid"` + Allocationtype string `json:"allocationtype"` + Allocationmode int `json:"allocationmode"` + Slab string `json:"slab"` + Pricingdate string `json:"pricingdate"` + Baseprice float32 `json:"baseprice"` + Minkm int `json:"minkm"` + Priceperkm float32 `json:"priceperkm"` + Maxkm int `json:"maxkm"` + Orders int `json:"orders"` + Othercharges float32 `json:"othercharges"` + Surgecharges float32 `json:"surgecharges"` +} + +type Tenantsubscriptions struct { + Subscriptionid int `json:"subscriptionid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Transactiondate string `json:"transactiondate"` + Moduleid int `json:"moduleid"` + Applocationid int `json:"applocationid"` + Promoid int `json:"promoid"` + Categoryid int `json:"categoryid"` + Subcategoryid int `json:"subcategoryid"` + Promoprice float32 `json:"float32"` + Subscriptionprice float32 `json:"subscriptionprice"` + Quantity int `json:"quantity"` + Taxpercent int `json:"taxpercent"` + Taxamount float32 `json:"taxamount"` + Totalamount float32 `json:"totalamount"` + Validitydate string `json:"validitydate"` +} + +type Tenantlocationinfo struct { + Locationid int `json:"locationid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Moduleid int `json:"moduleid"` + Locationname string `json:"locationname"` + Locationemail string `json:"locationemail" gorm:"column:email"` + Locationcontact string `json:"locationcontact" gorm:"column:contactno"` + Locationlatitude string `json:"locationlatitude" gorm:"column:latitude"` + Locationlong string `json:"locationlong" gorm:"column:longitude"` + Locationaddress string `json:"locationaddress" gorm:"column:address"` + Locationsuburb string `json:"locationsuburb" gorm:"column:suburb"` + Locationstate string `json:"locationstate" gorm:"column:state"` + Locationcity string `json:"locationcity" gorm:"column:city"` + Locationpostcode string `json:"locationpostcode" gorm:"column:postcode"` + Opentime string `json:"opentime"` + Closetime string `json:"closetime"` + Partnerid int `json:"partnerid"` + Deliveryradius int `json:"deliveryradius"` + Deliverymins int `json:"deliverymins"` + Cancelsecs int `json:"cancelsecs"` + Applocationid int `json:"applocationid"` + Slot1 string `json:"slot1"` + Slot2 string `json:"slot2"` + Slot3 string `json:"slot3"` + Status string `json:"status"` + Roleid int `json:"roleid"` +} + +func (Tenantlocationinfo) TableName() string { + return "tenantlocations" +} + +type Tenantslot struct { + // Slotid int `json:"slotid" gorm:"Primary_Key"` + Slot1 string `json:"slot1"` + Slot2 string `json:"slot2"` + Slot3 string `json:"slot3"` +} + +type TenantLocationSummary struct { + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + Locationsuburb string `json:"locationsuburb"` + Total int `json:"total"` + Created int `json:"created"` + Pending int `json:"pending"` + Delivered int `json:"delivered"` + Cancelled int `json:"cancelled"` +} + +type Tenantlocations struct { + Locationid int `json:"locationid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Applocationid int `json:"applocationid"` + Moduleid int `json:"moduleid"` + Locationname string `json:"locationname"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Opentime string `json:"opentime"` + Closetime string `json:"closetime"` + Partnerid int `json:"partnerid"` + Deliveryradius int `json:"deliveryradius"` + Deliverymins int `json:"deliverymins"` + Cancelsecs int `json:"cancelsecs"` + Slot1 string `json:"slot1"` + Slot2 string `json:"slot2"` + Slot3 string `json:"slot3"` + Slot4 string `json:"slot4"` + Slot5 string `json:"slot5"` + Status string `json:"status" gorm:"default:Active"` +} + +type Tenantlocation struct { + Locationid int `json:"locationid" gorm:"Primary_Key"` + Tenantid int `json:"tenantid"` + Applocationid int `json:"applocationid"` + Moduleid int `json:"moduleid"` + Locationname string `json:"locationname"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Opentime string `json:"opentime"` + Closetime string `json:"closetime"` + Partnerid int `json:"partnerid"` + Deliveryradius int `json:"deliveryradius"` + Deliverymins int `json:"deliverymins"` + Cancelsecs int `json:"cancelsecs"` + Status string `json:"status" gorm:"default:Active"` +} + +type Tenantpricing struct { + Tenantpricingid int `json:"tenantpricingid" gorm:"Primary_Key"` + Pricingid int `json:"pricingid"` + Tenantid int `json:"tenantid"` + Applocationid int `json:"applocationid"` + Pricingtypeid int `json:"pricingtypeid"` + Slab string `json:"slab"` + Configid int `json:"configid"` + Pricingdate string `json:"pricingdate"` + Baseprice float32 `json:"baseprice"` + Priceperkm float32 `json:"priceperkm"` + Minkm int `json:"minkm"` + Maxkm int `json:"maxkm"` + Orders int `json:"orders"` + Othercharges float32 `json:"othercharges"` + Surgecharges float32 `json:"surgecharges"` +} + +type TenantCustomer struct { + TenantCustomerID int `json:"tenantcustomerid" gorm:"primaryKey;column:tenantcustomerid"` + ModuleID int `json:"moduleid" gorm:"column:moduleid"` + TenantID int `json:"tenantid" gorm:"column:tenantid"` + LocationID int `json:"locationid" gorm:"column:locationid"` + CustomerID int `json:"customerid" gorm:"column:customerid"` + CustomerLocationID int `json:"customerlocationid" gorm:"column:customerlocationid"` + Status int `json:"status" gorm:"column:status"` + Created time.Time `json:"created" gorm:"column:created;autoCreateTime"` + Updated time.Time `json:"updated" gorm:"column:updated;autoUpdateTime"` +} + +// If needed, define the table name explicitly +func (TenantCustomer) TableName() string { + return "tenantcustomers" +} + +type CreateTenantCustomerRequest struct { + ModuleID int `json:"moduleid"` + TenantID int `json:"tenantid"` + LocationID int `json:"locationid"` + CustomerID int `json:"customerid"` + CustomerLocationID int `json:"customerlocationid"` + Status int `json:"status"` +} + +// type CustomerInfo struct { +// Customerid int `json:"customerid"` +// Customername string `json:"customername"` +// Contactno string `json:"contactno"` +// } + +// type TenantInfo struct { +// Tenantid int `json:"tenantid"` +// Tenantname string `json:"tenantname"` +// } + +type CustomerTenantResponse struct { + Customer Customers `json:"customer"` + // Tenants []TenantInfo `json:"tenants"` + Tenantlocations []LocationDetails `json:"locations"` +} + +type Tenantuser struct { + Userid int `json:"userid" gorm:"Primary_Key"` + Authname string `json:"authname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` + Email string `json:"email"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Pin int `json:"pin"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Userfcmtoken string `json:"userfcmtoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` +} + +type LocationSummary struct { + ActiveCount int `json:"active"` + InactiveCount int `json:"inactive"` + TotalCount int `json:"total"` +} + +type TenantUserSummary struct { + ActiveCount int `json:"active"` + InactiveCount int `json:"inactive"` + TotalCount int `json:"total"` +} + +type TenantRequest struct { + Requestid int `json:"requestid"` + Requestdate time.Time `json:"requestdate"` + Requesttype int `json:"requesttype"` + Subject string `json:"subject"` + Apptypeid int `json:"apptypeid"` + Userid int `json:"userid"` + Tenantid int `json:"tenantid"` + Referenceid int `json:"referenceid"` + Referencetype string `json:"referencetype"` + Remarks string `json:"remarks"` + Resolution string `json:"resolution"` + Orderheaderid int `json:"orderheaderid"` + Eventname string `json:"eventname"` + Status int `json:"status"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +type LocationDetails struct { + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Address string `json:"address"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Applocationid int `json:"applocationid"` + Suburb string `json:"suburb"` + City string `json:"city"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Postcode string `json:"postcode"` +} + +type Tenantpromotions struct { + Promotionid int `json:"promotionid" gorm:"primaryKey"` + Promotiontypeid int `json:"promotiontypeid"` + Tenantid int `json:"tenantid"` + Locationid []string `json:"locationid" gorm:"-"` + LocationidStr string `json:"-" gorm:"column:locationid"` + Applocationid int `json:"applocationid"` + Moduleid int `json:"moduleid"` + Categoryid int `json:"categoryid"` + Promoname string `json:"promoname"` + Promocode string `json:"promocode"` + Description string `json:"description"` + Product string `json:"product"` + Productimage string `json:"productimage"` + Promoamount float32 `json:"promoamount"` + Promovalue float32 `json:"promovalue"` + Purchasevalue float32 `json:"purchasevalue"` + Startdate string `json:"startdate"` + Enddate string `json:"enddate"` +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..e56a380 --- /dev/null +++ b/models/user.go @@ -0,0 +1,333 @@ +package models + +type User struct { + Userid int `json:"userid" gorm:"Primary_Key"` + Authname string `json:"authname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` + Email string `json:"email"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Rolename string `json:"rolename"` + Pin int `json:"pin"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Userfcmtoken string `json:"userfcmtoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` + Shiftid int `json:"shiftid"` + Bonuspts int `json:"bonuspts" gorm:"-"` + Hashsalt string `json:"hashsalt" gorm:"-"` +} + +type Users struct { + Userid int `json:"userid" gorm:"Primary_Key"` + Email string `json:"email"` + Username string `json:"username"` + Contactno string `json:"contactno"` + Roleid int `json:"roleid"` + Configid int `json:"configid"` + Password string `json:"password"` +} + +type AppLocation struct { + Applocationid int `json:"applocationid"` + Applocationname string `json:"applocationname"` + Status string `json:"status"` +} + +type UserInfo struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Rolename string `json:"rolename"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Shiftid int `json:"shiftid"` + Shiftname string `json:"shiftname"` + Pin int `json:"pin"` + Tenantid int `json:"tenantid"` + Status string `json:"status"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + + Applocations []AppLocation `json:"applocations" gorm:"-"` +} + +type UserInfov2 struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Shiftid int `json:"shiftid"` + Shiftname string `json:"shiftname"` + Pin int `json:"pin"` + Tenantid int `json:"tenantid"` + + ApplocationidsRaw string `json:"-"` + ApplocationnamesRaw string `json:"-"` + + Applocationids []int `json:"applocationids"` + Applocationnames []string `json:"applocationnames"` + + Status string `json:"status"` +} + +type TenantUserInfo struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Password string `json:"password"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Pin int `json:"pin"` + Partnerid int `json:"partnerid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Tenantid int `json:"tenantid"` + Tenantname string `json:"tenantname"` + Tenantaddress string `json:"tenantaddress"` + Tenantcity string `json:"tenantcity"` + Tenantpostcode string `json:"tenantpostcode"` + Tenantlat string `json:"tenantlat"` + Tenantlong string `json:"tenantlong"` + Locationname string `json:"locationname"` + Applocation string `json:"applocation"` + Applatitude string `json:"applatitude"` + Applongitude string `json:"applongitude"` + Appradius int `json:"appradius"` + Moduleid int `json:"moduleid"` + Categoryid int `json:"categoryid"` + Categoryname string `json:"categoryname"` + Subcategoryid int `json:"subcategoryid"` +} + +type StaffInfo struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Fullname string `json:"fullname"` + Password string `json:"password"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Pin int `json:"pin"` + Applocationid int `json:"applocationid"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Locationname string `json:"locationname"` +} + +type RiderInfo struct { + Userid int `json:"userid"` + Authname string `json:"authname"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` + Contactno string `json:"contactno"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Userfcmtoken string `json:"userfcmtoken"` + Pin int `json:"pin"` + Partnerid int `json:"partnerid"` + Identificationno string `json:"identificationno"` + Vehiclename string `json:"vehiclename"` + Vehicleno string `json:"vehicleno"` + Licenseno string `json:"licenseno"` + Insuranceno string `json:"insoranceno"` + Insurancedate string `json:"insurancedate"` + Shiftid int `json:"shiftid"` + Firstmilecharge float32 `json:"firstmilecharge"` + Starttime string `json:"starttime"` + Endtime string `json:"endtime"` + Shifthours float32 `json:"shifthours"` + Basefare float32 `json:"basefare"` + Additionalcharges float32 `json:"additionalcharges"` + Orders int `json:"orders"` + Fuelcharge float32 `json:"fuelcharge"` + Logdate string `json:"logdate"` + Applocationid int `json:"applocationid"` + Applocation string `json:"applocation"` + Logseconds int `json:"logseconds"` + Deliveryradius int `json:"deliveryradius"` + Riderid int `json:"riderid"` + Status string `json:"status"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Logid int `json:"logid"` + Onduty int `json:"onduty"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Bonuspts int `json:"bonuspts"` + Name string `json:"name"` + Capacity int `json:"capacity"` + Rangekm int `json:"rangekm"` + Batterypercentage int `json:"batterypercentage"` + Type string `json:"type"` +} + +type AppUserpools struct { + Poolid int `json:"poolid" gorm:"Primary_key"` + Userid int `json:"userid"` + Partnerid int `json:"partnerid"` + Vehicleid int `json:"vehicleid"` + Onduty int `json:"onduty"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Status string `json:"status"` +} + +type Tenantstaffs struct { + Tenantstaffid int `json:"tenantstaffid" gorm:"Primary_key"` + Tenantid int `json:"tenantid"` + Moduleid int `json:"moduleid"` + Locationid int `json:"locationid"` + Userid int `json:"userid"` + Status string `json:"status"` +} + +type Riderinfo struct { + Userid int `json:"userid" gorm:"Primary_Key"` + Authname string `json:"authname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` + Email string `json:"email"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Pin int `json:"pin"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Userfcmtoken string `json:"userfcmtoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` + Shiftid int `json:"shiftid"` + Bonuspts int `json:"bonuspts"` + Hashsalt string `json:"hashsalt" gorm:"-"` +} + +type AppLocationConfig struct { + Applocationconfigid int `gorm:"primaryKey"` + Applocationid int + Configid int + Userid int + Partnerid int + Notify string + Status string +} + +type ConsoleUser struct { + Userid int `json:"userid" gorm:"Primary_Key"` + Authname string `json:"authname"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` + Email string `json:"email"` + Dialcode string `json:"dialcode"` + Contactno string `json:"contactno"` + Configid int `json:"configid"` + Authmode int `json:"authmode"` + Roleid int `json:"roleid"` + Pin int `json:"pin"` + Deviceid string `json:"deviceid"` + Devicetype string `json:"devicetype"` + Userfcmtoken string `json:"userfcmtoken"` + Address string `json:"address"` + Suburb string `json:"suburb"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Partnerid int `json:"partnerid"` + Tenantid int `json:"tenantid"` + Locationid int `json:"locationid"` + Applocationid int `json:"applocationid"` + Status string `json:"status"` + Shiftid int `json:"shiftid"` + Bonuspts int `json:"bonuspts" gorm:"-"` + Hashsalt string `json:"hashsalt" gorm:"-"` + // Applocationids []int `json:"applocationids" gorm:"-"` +} + +type AppLocationConfigRequest struct { + Userid int `json:"userid"` + Configid int `json:"configid"` + Partnerid int `json:"partnerid"` + Applocationids []int `json:"applocationids"` + Notify string `json:"notify"` + Status string `json:"status"` +} diff --git a/models/utils.go b/models/utils.go new file mode 100644 index 0000000..63e6d4b --- /dev/null +++ b/models/utils.go @@ -0,0 +1,156 @@ +package models + +import "time" + +type Applocations struct { + Applocationid int `json:"applocationid" gorm:"Primary_Key"` + Locationname string `json:"locationname"` + Image string `json:"image"` + City string `json:"city"` + State string `json:"state"` + Postcode string `json:"postcode"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Opentime string `json:"opentime"` + Closetime string `json:"closetime"` + Radius int `json:"radius"` + Applocationadmins []Applocationadmins `json:"applocationadmins" gorm:"ForeignKey:applocationid"` +} + +type Applocationadmins struct { + Applocationconfigid int `json:"applocationconfigid" gorm:"Primary_Key"` + Applocationid int `json:"applocationid"` + Userid int `json:"userid"` + Userfcmtoken string `json:"userfcmtokem"` + Notify string `json:"notify"` +} + +type ApplocationsResult struct { + Code int `json:"code"` + Status bool `json:"status"` + Message string `json:"message"` + Details []Applocations `json:"details"` +} + +type Apppricing struct { + Pricingid int `json:"pricingid"` + Pricingdate string `json:"pricingdate"` + Pricingtypeid int `json:"pricingtypeid"` + Applocationid int `json:"applocationid"` + Configid int `json:"configid"` + Slab string `json:"slab"` + Baseprice float32 `json:"baseprice"` + Minkm int `json:"minkm"` + Priceperkm float32 `json:"priceperkm"` + Maxkm int `json:"maxkm"` + Minorder int `json:"minorder"` + Status int `json:"status" gorm:"default:0"` + Applocation string `json:"applocation" gorm:"<-:false"` + Appname string `json:"appname" gorm:"<-:false"` +} + +type Appconfig struct { + Configid int `json:"configid"` + Appname string `json:"appname"` + Paymentdevkey string `json:"paymentdevkey"` + Paymentlivekey string `json:"paymentlivekey"` + Fcmkey string `json:"fcmkey"` + Googleapikey string `json:"googleapikey"` + Applocationradius int `json:"applocationradius"` + Smsproviderid int `json:"smsproviderid"` + Providerapi string `json:"providerapi"` + Providerkey string `json:"providerkey"` + Sender string `json:"sender"` + Templateid string `json:"templateid"` + Defaultprovider int `json:"defaultprovider"` +} + +type Apptypes struct { + Apptypeid int `json:"apptypeid"` + Typename string `json:"typename"` + Mapid int `json:"mapid"` + Tag string `json:"tag"` + Status string `json:"status"` +} + +type Appsubcategories struct { + Subcategoryid int `json:"subcategoryid"` + Categoryid int `json:"categoryid"` + Subcategoryname string `json:"subcategoryname"` + Categoryname string `json:"catgeoryname"` + Moduleid int `json:"moduleid"` + Status string `json:"status"` +} + +type AppCategory struct { + Categoryid int `json:"categoryid"` + Categoryname string `json:"categoryname"` + Categorytype string `json:"categorytype"` + Moduleid int `json:"moduleid"` + Sortorder int `json:"sortorder"` + Imageurl string `json:"imageurl"` + Iconurl string `json:"iconurl"` + Profileurl string `json:"profileurl"` + Crossaxis int `json:"crossaxis"` + Mainaxis int `json:"mainaxis"` + Status string `json:"status"` +} + +type AppModule struct { + Moduleid int `json:"moduleid"` + Modulename string `json:"modulename"` + Categoryid int `json:"categoryid"` + Baseprice float32 `json:"baseprice"` + Taxpercent float32 `json:"taxpercent"` + Taxamount float32 `json:"taxamount"` + Amount float32 `json:"amount"` + Commisiontype string `json:"commisiontype"` + Commisionvalue float32 `json:"commisionvalue"` + Content string `json:"content"` + Logourl string `json:"logourl"` + Iconurl string `json:"iconurl"` + Sortorder int `json:"sortorder"` + Status string `json:"status"` + Createdby int `json:"createdby"` +} + +type TenantNotification struct { + Notificationid int `json:"notificationid"` + Title string `json:"title"` + Message string `json:"message"` + Tenantid int `json:"tenantid"` + Moduleid int `json:"moduleid"` + Locationid int `json:"locationid"` + Customerid int `json:"customerid"` + Notificationdate time.Time `json:"notificationdate"` + Successcode int `json:"successcode"` +} + +type UserRoles struct { + Roleid int `json:"roleid"` + Rolename string `json:"rolename"` + Configid int `json:"configid"` +} + +type RiderLog struct { + UserID int `json:"userid"` + Username string `json:"username"` + LogDate string `json:"logdate"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + Speed string `json:"speed"` + Heading string `json:"heading"` + Accuracy string `json:"accuracy"` + Status string `json:"status"` + OrderID string `json:"orderid"` + Battery string `json:"battery"` + IsCharging bool `json:"is_charging"` + Connection string `json:"connection"` + LocationService string `json:"location_service"` + IsBackground bool `json:"is_background"` +} + +type RiderStatus struct { + UserID int `json:"userid"` + Status string `json:"status"` +} diff --git a/nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json b/nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json new file mode 100644 index 0000000..be1e9fe --- /dev/null +++ b/nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "nearle-gear", + "private_key_id": "23ca3b3609076b93b29a730f63e1da76d68e4baa", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfsSE/Otsp0LTJ\nrTBUoOcdd0MF3rRZtcL4/WCekyEUh8tTh0YtSqaaDoO2lpg2Ym9jS031z1U0UOqg\n83L9IwyQD0UV0rYFkOm9LWNU60ZvL/KjE8YcRf0j8nj/Sv6S9rrrQmQKKDXINs2t\noXyBi7R4WTGxJmRzyAr9jTgS/aghGpvo57z7aln2veYXUVxw1kVDEou9dFEu2eHC\nPY4Bb6ACDFvxtGNAClwHy3pYHv+6QirF5O2z+JPjGuYJXSOqiT6SOhLHnv9P7W4y\nUY5/wFiRpvK6iYjRFEnpw3c950UH3y19njiPyZN+pS6tCqyE6w0tQPIaxX7c39dg\nmq9esOGbAgMBAAECggEAVzfIP9Ah/fbbVrtJWeX21x1WK6a+6S+emtioYIv7XPbp\nh2L6MNSniu/X2Ux0gtf0mGGXPx2dGi3mneTuU9bjohPiGvUydY8gI9vbnBO0PcwT\nLhSarRF49xgmp1vFUOYU0X/sY61z8uGzZlzNn/Ep57bXUjwm9KHt7xit4XG3qVfs\n84+hYWGl6vy15LfgmRb5MzTWexYMfQnaxk0we0mUzSIXmOCvI/OrkQwKenkgkR2F\n7r/0LZQBbVhZ/kB9avWBNnsj89nDNld9MtuKHqjcwFfHFgBzSz+3Qy2LolCVtN5Z\nMEjJ3/t1cocDAnKRBJeA9B3Hj2ud1BCxSQceTnb22QKBgQD3tOa/KWQbgmfoFh/w\nni2EJM0KDKvibxJfIC+p6z4/EiXENmJDQx32yqURCO75mwtBX/VYxGSLb1wLTfIn\nGVRu5KWs5oLV8FqyAQPYh5Urz6DVqg6CQRQadCz0emMMbjRk2XDE2j7f18VOB4DV\nZCxpx1Bv1wiU2ITNxczlteH6zwKBgQDnLmXTw1jfZ1DS69idby5L8PpcnP3GWV4t\nxAHH6QJhqUrMo5/eFKOL6aaSR4cIg47NdtP4yKo71TyaFl68xo4VWyoy2xJffdyd\niGf1kG2prNmHAtcU6W9QdgK4qgHpYVpqZufAn2XW/KMrsyhd4jwVR/UFNogswLI5\nXXy8Tx7vdQKBgGRHHL64n+kvEqd3BXkgX+bGCyeNV6w0MOjHm+Qa8rkPvLBBH+iA\n7ElzYf2Sc0QjCxxtH2LPJrD35PhClsxTScYW1Cc5ri+zvNOg65Cl2rLAvCijTnpW\npC/NZkGWpjBrENTe3fMjMx7lN9/N088PXZd488xC7htrx9+RutAnoJMrAoGAd8Px\nvONXB2Xe2WaVsfoHYhBVo+UxE7D4uXzx7z8nnLC3r4yVJdhLYhCJ2v5zVlXRhWAq\nMJjEmHrACpPMQMAcm7O/CNm1iwMJaBNiyDUqmtyRVQCDrLHCmUyJ3GE23FEzJixp\no8DwYZBAeEM4hmrN9bhxl2HI6mZp7o4gMO5MeIECgYAuqqv/hq9t57qnJhk2S5mi\nSmDMk+Zz/pVFYfpUlRL33k/t+8c/8+qTdSruRZzLzSAtdZY6XE85lidt2zwdCgqO\nEuviAb474iF8UOY0hf4mQkrIHmlojd2nZmUQbPjPTR+kiUvhJGvQuaJywbX54+wR\n+8tH+pbwRbkrENWHqJHxhg==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-l9oha@nearle-gear.iam.gserviceaccount.com", + "client_id": "118373484295110637143", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-l9oha%40nearle-gear.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..1022070 --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,376 @@ +package routes + +import ( + "nearle/controllers" + + "nearle/middlewares" + "net/http" + + "github.com/gofiber/fiber/v2" +) + +func DevSetup(app *fiber.App) { + + app.Get("/", func(c *fiber.Ctx) error { + return c.JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Welcome to Nearle", + "status": true, + }) + + }) + + dev := app.Group("/dev/api") + + customer := dev.Group("/v1/customers") + customer.Post("/login", controllers.CustomerLogin) + customer.Post("/create", controllers.CreateCustomer) + + util := dev.Group("/v1/utils") + util.Get("/getapplocations", controllers.GetApplocations) + util.Get("/getappconfig", controllers.GetAppConfig) + +} + +func LiveSetup(app *fiber.App) { + + live := app.Group("/live/api") + + users := live.Group("/v1/users") + users.Post("/login", controllers.Login) + users.Post("/tenant/login", controllers.TenantLogin) + users.Post("/partner/login", controllers.Partnerlogin) + users.Post("/rider/login", controllers.Riderlogin) + users.Post("/admin/login", controllers.AdminLogin) + users.Post("/console/login", controllers.AdminConsoleLogin) + users.Post("/tenant/weblogin", controllers.TenantWebLogin) + users.Post("/applogin", controllers.AppLogin) + users.Post("/create", controllers.CreateUser) + users.Post("/createconsoleuser", controllers.CreateConsoleUser) + users.Put("/update", controllers.UpdateStaff) + users.Delete("/delete", controllers.DeleteUser) + users.Get("/getusers", controllers.GetUserInfo) + users.Get("/getallusers", controllers.GetAllUsers) + + users = live.Group("/v2/users") + users.Post("/applogin", middlewares.RoleCheckMiddleware(0), controllers.TenantWebLoginv2) + users.Post("/tenant/weblogin", middlewares.RoleCheckMiddleware(1), controllers.TenantWebLoginv2) + users.Post("/create", controllers.CreateUserV2) + users.Put("/update", controllers.UpdateUserV2) + users.Post("/rider/login", controllers.Riderloginv2) + users.Get("/getallusers", controllers.GetAllUsersv2) + + customer := live.Group("/v1/customers") + customer.Get("/", controllers.BasicRoute) + customer.Post("/login", controllers.CustomerLogin) + customer.Post("/create", controllers.CreateCustomer) + customer.Put("/update", controllers.UpdateCustomer) + customer.Get("/getbyid", controllers.GetCustomer) + customer.Get("/getbyno", controllers.GetCustomer) + customer.Get("/getbytid", controllers.GetCustomersbytenent) + customer.Get("/gettenantcustomers", controllers.GetTenantCustomers) + customer.Get("/getcustomerlocation", controllers.GetCustomerLocations) + customer.Post("/createlocations", controllers.CreateCustomerLocation) + customer.Get("/getcustomersbyapplocation", controllers.GetCustomersbyapplocation) + customer.Get("/getallcustomers", controllers.GetallCustomers) + customer.Get("getcustomersummary", controllers.GetCustomerSummary) + customer.Get("/search", controllers.SearchCustomer) + customer.Delete("/delete", controllers.DeleteCustomer) + customer.Post("/createcustomerrequest", controllers.CreateCustomerRequest) + customer.Get("/getcustomerrequests", controllers.GetCustomerRequests) + + util := live.Group("/v1/utils") + util.Get("/getapplocations", controllers.GetApplocations) + util.Get("/getappconfig", controllers.GetAppConfig) + util.Get("/getallappconfig", controllers.GetAllAppConfig) + util.Get("/getapptypes", controllers.GetApptypes) + util.Get("/getappmodule", controllers.GetAppModule) + util.Get("/getsubcategories", controllers.GetSubcategories) + util.Get("/getcategories", controllers.GetCategories) + util.Post("/notifyuser", controllers.NotifyUser) + util.Post("/sendnotifications", controllers.NotifyUsers) + util.Get("/getapppricing", controllers.GetAppPricing) + util.Get("/getallpricing", controllers.GetAllAppPricing) + util.Get("/getallpricing", controllers.GetAllAppPricing) + util.Post("/createapppricing", controllers.CreateAppPricing) + util.Get("/getapplocationconfig", controllers.GetApplocationConfig) + util.Post("/notifyadmin", controllers.NotifyAdmin) + util.Post("/notifytenant", controllers.NotifyTenant) + util.Get("/gettenantnotifications", controllers.GetTenantNotifications) + util.Post("/webhooks", controllers.WebhookHandler) + util.Post("/register-webhook", controllers.RegisterWebhookHandler) + util.Post("/createwebhook", controllers.Createwebhook) + util.Post("/webhooksreceiver", controllers.WebhookReceiver) + util.Get("/getshopfrontorders", controllers.GetShopfrontOrders) + util.Post("/webhooks/inventory", controllers.RegisterInventoryWebhook) + util.Get("/getuserbonussummary", controllers.GetUserBonusSummary) + util.Post("/createapplocationconfig", controllers.CreateAppLocationConfig) + util.Put("/updateapplocationconfig", controllers.UpdateAppLocationConfig) + util.Get("/getuserroles", controllers.GetUserRoles) + util.Post("/createuserredis", controllers.CreateUserRedis) + util.Get("/getuserredis", controllers.GetUserRedis) + util.Post("/createriderperiodiclog", controllers.CreateRiderPeriodicLog) + util.Get("/getriderperiodiclogs", controllers.GetRiderPeriodicLogs) + util.Post("/createriderstatus", controllers.CreateRiderStatus) + util.Get("/getriderstatus", controllers.GetRiderStatus) + + util = live.Group("/v2/utils") + util.Get("/getapplocations", controllers.GetApplocationsv2) + + partner := live.Group("/v1/partners") + partner.Get("/getriders", controllers.GetActiveRiders) + partner.Get("/getactiveriderssummary", controllers.GetActiveRidersSummary) + partner.Get("/getridershifts", controllers.GetRiderShifts) + partner.Get("/getriderpricing", controllers.GetRiderPricing) + partner.Get("/getriderpool", controllers.GetRiderPool) + partner.Get("/getriderinfo", controllers.GetRiderInfo) + + partner.Get("/getriderdetail", controllers.GetRiderDetails) + partner.Get("/getallriders", controllers.GetAllRiders) + partner.Get("/getpartners", controllers.GetPartners) + partner.Get("/getlocations", controllers.GetLocationConfig) + partner.Get("/getpartnerusers", controllers.GetPartnerusers) + partner.Get("/getallorderbystatus", controllers.GetOrders) + partner.Get("/getadmintoken", controllers.GetAdminToken) + + partner.Post("/createshift", controllers.CreateRiderShift) + partner.Post("/createrider", controllers.CreateRider) + partner.Put("/updateridersettings", controllers.UpdateRiderSettings) + partner.Put("/updaterider", controllers.UpdateRiderInfo) + + partner.Post("/createriderlog", controllers.CreateRiderLog) + partner.Put("/updateriderlog", controllers.UpdateRiderLog) + partner.Get("/getriderlog", controllers.GetRiderLog) + partner.Get("/getriderlogs", controllers.GetRiderLogs) + partner.Get("/getridercount", controllers.GetRiderOrderCount) + partner.Post("/createbreaklog", controllers.CreateBreakLog) + partner.Put("/updatebreaklog", controllers.UpdateBreakLog) + partner.Get("/getriderweeklykms", controllers.GetRiderWeeklyKms) + partner.Post("/createridersupport", controllers.CreateRiderSupport) + partner.Get("/getridersupport", controllers.GetRiderSupport) + partner.Get("/getallridersummary", controllers.GetAllRidersSummary) + partner.Get("/getuserlocationsummary", controllers.GetUserLocationSummary) + partner.Get("/getriderpricing", controllers.GetRiderPricing) + + partner = live.Group("/v2/partners") + partner.Get("/getriderinfo", controllers.GetRiderInfov2) + partner.Get("/getriders", controllers.GetActiveRidersv2) + partner.Post("/createbreaklog", controllers.CreateBreakLogv1) + partner.Put("/updatebreaklog", controllers.UpdateBreakLogv1) + partner.Get("/getdeliverystats", controllers.GetDeliveryStats) + partner.Post("/createriderlog", controllers.CreateRiderLogv1) + partner.Put("/updateriderlog", controllers.UpdateRiderLogv1) + partner.Get("/getriderlogs", controllers.GetRiderLogsv1) + partner.Delete("/deleteriderlog", controllers.DeleteRiderLogs) + + invoice := live.Group("/v1/invoice") + invoice.Get("/getseqno", controllers.InvoiceSeqno) + invoice.Get("/getinvoiceorders", controllers.GetInvoiceOrders) + invoice.Post("/create", controllers.CreateInvoice) + invoice.Get("/getallinvoice", controllers.GetallInvoice) + invoice.Get("/getinvoiceinsight", controllers.GetInvoiceInsight) + invoice.Put("/update", controllers.UpdateInvoice) + invoice.Put("/updatestatus", controllers.UpdateInvoiceStatus) + + tenant := live.Group("/v1/tenants") + tenant.Post("/create", controllers.Createtenant) + tenant.Post("/createlocation", controllers.CreateLocation) + tenant.Post("/createtenantuser", controllers.CreatetenantUser) + tenant.Put("/update", controllers.UpdateTenant) + tenant.Put("/updatelocation", controllers.UpdateLocation) + tenant.Put("/createpartneruser", controllers.UpdateTenant) + tenant.Get("/gettenantinfo", controllers.GetTenantInfo) + tenant.Get("/search", controllers.SearchTenant) + tenant.Post("/createtenantcustomer", controllers.Createtenantcustomer) + + tenant.Post("/createstaff", controllers.CreateStaff) + tenant.Put("/updatestaff", controllers.UpdateStaff) + tenant.Get("/getstaffs", controllers.GetStaffs) + tenant.Get("/gettenants", controllers.GetTenants) + tenant.Get("/getalltenants", controllers.GetAllTenants) + tenant.Get("/gettenantsummary", controllers.GetTenantSummary) + tenant.Get("/getcloudstore", controllers.GetCloudStore) + tenant.Get("/gettenantlocations", controllers.GetTenantLocations) + tenant.Get("/gettenantslots", controllers.GetTenantSlot) + tenant.Get("/gettenantlocation", controllers.GetTenantLocation) + tenant.Post("/createpricing", controllers.CreatePricing) + tenant.Get("/gettenantpricing", controllers.GetTenantPricing) + tenant.Post("/createtenantrequest", controllers.CreateTenantRequest) + tenant.Get("/gettenantrequests", controllers.GetTenantRequests) + tenant.Get("/getpricinglist", controllers.GetPricingList) + tenant.Post("/createtenantpromotions", controllers.CreateTenantPromotion) + tenant.Get("/gettenantpromotions", controllers.GetTenantPromotions) + + tenant = live.Group("/v2/tenants") + tenant.Post("/createtenantlocation", controllers.CreateTenantLocation) + tenant.Put("/updatetenantlocation", controllers.UpdateTenantLocation) + tenant.Get("/getlocationsummary", controllers.GetLocationSummary) + tenant.Get("/gettenantstaffsummary", controllers.GetTenantStaffSummary) + tenant.Get("/gettenantridersummary", controllers.GetTenantRiderSummary) + tenant.Get("/getcustomertenants", controllers.GetCustomerTenants) + + products := live.Group("v1/products") + products.Get("/getallproducts", controllers.GetAllProducts) + products.Get("/getproductinfo", controllers.GetProductInfo) + products.Get("/getproductcategories", controllers.GetProductCategory) + products.Get("/getproductsubcategories", controllers.GetProductSubCategory) + products.Post("/create", controllers.CreateProduct) + products.Put("/update", controllers.UpdateProduct) + products.Delete("/delete", controllers.DeleteProduct) + products.Get("/getproductsbysubcategory", controllers.GetProductsBySubcategory) + products.Get("/getproductscount", controllers.GetProductCount) + products.Get("/getproductbyvariant", controllers.GetProductByVariant) + products.Post("/createproductvariant", controllers.CreateProductVariant) + products.Get("/getproductvariants", controllers.GetProductVariants) + products.Post("/createproductdiscount", controllers.CreateProductDiscount) + products.Get("/getproductdiscounts", controllers.GetProductDiscounts) + + products = live.Group("v2/products") + products.Get("/getcatalougeproducts", controllers.GetCatalougeProducts) + products.Get("/getlocationproducts", controllers.GetLocationProducts) + products.Get("/getlocationproductsummary", controllers.GetLocationProductSummary) + products.Get("/getstockstatement", controllers.GetStockstatement) + products.Post("/createproductlocation", controllers.CreateProductLocation) + products.Put("/updateproductlocation", controllers.UpdateProductLocation) + products.Post("/createproductstock", controllers.CreateProductStock) + products.Put("/updateproductstock", controllers.UpdateProductStock) + products.Get("/getproductstocks", controllers.GetProductStocks) + products.Get("/getproductcategorywisesummary", controllers.GetSubCategoryWiseSummary) + products.Get("/getstockstatementsummary", controllers.GetStockStatementSummary) + + orders := live.Group("/v1/orders") + orders.Get("partner/getorders", controllers.GetOrders) + orders.Get("tenant/getorders", controllers.GetOrders) + orders.Get("customer/getorders", controllers.GetOrders) + orders.Get("/getorders", controllers.GetOrders) + orders.Get("tenant/getlocationsummary", controllers.GetTenantLocationSummary) + orders.Get("/getordersummary", controllers.GetOrderSummary) + orders.Get("/getlocationsummary", controllers.GetlocationOrderSummary) + orders.Get("/getorderinsight", controllers.GetOrderInsight) + orders.Get("/getorderdetails", controllers.GetOrderDetails) + + orders.Post("/createorder", controllers.CreateOrder) + orders.Post("/createorders", controllers.CreateOrders) + orders.Put("/updateorder", controllers.UpdateOrder) + orders.Put("/updatemultipleorders", controllers.UpdateMultipleOrders) + + orders.Get("/getorderbylocations", controllers.GetCustomerOrderByLocation) + + ordersv2 := live.Group("/v2/orders") + ordersv2.Post("/createorder", controllers.CreateCustomerOrderv2) + ordersv2.Get("/getorders", controllers.GetOrdersv2) + ordersv2.Get("/getorderinsight", controllers.GetOrderInsightDaily) + ordersv2.Get("/getordersummary", controllers.GetOrderSummaryDaily) + ordersv2.Get("/getlocationsummary", controllers.GetLocationOrderSummaryDaily) + + ordersv3 := live.Group("/v3/orders") + ordersv3.Post("/createorder", controllers.CreateOrderv3) + ordersv3.Get("getcustomerorders", controllers.GetCustomerOrders) + + deliveries := live.Group("/v1/deliveries") + deliveries.Post("/createdelivery", controllers.CreateDelivery) + deliveries.Post("/createdeliveries", controllers.CreateDeliveries) + + deliveries.Post("/createdeliverylog", controllers.PublishLog) + deliveries.Get("/getdeliverylogs", controllers.GetDeliverylogs) + + deliveries.Get("deliverysummary", controllers.GetDeliverySummary) + deliveries.Get("/getlocationsummary", controllers.GetlocationdeliverySummary) + deliveries.Get("/getdeliveries", controllers.GetDeliveriesV2) + + deliveries.Get("/getdeliveryqueues", controllers.GetDeliveryQueues) + deliveries.Get("/getdeliveryqueuespicked", controllers.GetDeliveryQueuesPicked) + deliveries.Put("/Updatedelivery", controllers.UpdateDelivery) + deliveries.Get("/getdeliveryinsight", controllers.GetDeliveryInsight) + deliveries.Get("/getreportsummary", controllers.GetReportSummary) + deliveries.Get("/getreportlocationsummary", controllers.GetReportLocationSummary) + deliveries.Get("/getriderlocationreportsummary", controllers.GetRiderLocationReportSummary) + deliveries.Get("/getridersummary", controllers.GetRiderSummary) + deliveries.Get("/getriderlocationsummary", controllers.GetRiderLocationSummary) + deliveries.Get("/getriderbydelivery", controllers.GetRiderByDelivery) + deliveries.Get("/getlastdeliverybycontact", controllers.GetLastDeliveryByContact) + deliveries.Get("/getuserreportsummary", controllers.GetUserReportSummary) + deliveries.Get("/getuserdeliverylogs", controllers.GetUserDeliveryLogs) + + deliveriesv2 := live.Group("/v2/deliveries") + deliveriesv2.Post("/createdeliveries", controllers.CreateDeliveriesV2) + deliveriesv2.Get("/getdeliveries", controllers.GetDeliveriesV2) + deliveriesv2.Get("/getlocationsummary", controllers.GetlocationdeliverySummaryDaily) + deliveriesv2.Get("/getdeliveryinsight", controllers.GetDeliveryInsightDaily) + deliveriesv2.Post("/createdeliverylog", controllers.PublishLogv1) + deliveriesv2.Get("/getdeliverylogs", controllers.GetDeliverylogsv1) + deliveriesv2.Get("/getdeliveryqueues", controllers.GetDeliveryQueuesV1) + + deliveriesv3 := live.Group("/v3/deliveries") + deliveriesv3.Get("/getdeliveries", controllers.GetDeliveriesV3) + deliveriesv3.Get("/getdeliverylogs", controllers.GetDeliveryLogsv1) + + payments := live.Group("/v1/payments") + payments.Get("requests/getpaymentrequest", controllers.GetPaymentRequests) + payments.Post("requests/create", controllers.CreatePaymentRequest) + + admin := live.Group("/v1/admin") + admin.Post("/login", controllers.CustomerLogin) + admin.Get("/orders/getorders", controllers.GetOrders) + + platform := live.Group("/v1/platform") + platform.Get("/getmodules", controllers.GetModules) + platform.Get("/getsmsprovider", controllers.GetSmsProvider) + +} + +func LiveWebSetup(app *fiber.App) { + + liveweb := app.Group("/live/api/web") + + users := liveweb.Group("/v1/users") + users.Post("/create", controllers.CreateUserV2) + users.Get("/getallusers", controllers.GetAllUsers) + users.Put("/update", controllers.UpdateUserV2) + + tenantweb := liveweb.Group("/v1/tenants") + tenantweb.Put("/update", controllers.UpdateTenant) + tenantweb.Get("/gettenantpricing", controllers.GetTenantPricing) + tenantweb.Get("/gettenantinfo", controllers.GetTenantInfo) + tenantweb.Get("/gettenantlocations", controllers.GetTenantLocations) + tenantweb.Put("/updatetenantlocation", controllers.UpdateTenantLocation) + + ordersweb := liveweb.Group("/v1/orders") + ordersweb.Get("tenant/getorders", controllers.GetOrders) + ordersweb.Get("/getordersummary", controllers.GetOrderSummary) + ordersweb.Get("customer/getorders", controllers.GetOrders) + ordersweb.Get("tenant/getorders", controllers.GetOrders) + + utilweb := liveweb.Group("/v1/utils") + utilweb.Get("/getsubcategories", controllers.GetSubcategories) + utilweb.Get("/getapplocations", controllers.GetApplocations) + utilweb.Get("/getapplocationconfig", controllers.GetApplocationConfig) + utilweb.Get("/getapptypes", controllers.GetApptypes) + + customerweb := liveweb.Group("/v1/customers") + customerweb.Get("/gettenantcustomers", controllers.GetTenantCustomers) + + partnerweb := liveweb.Group("/v1/partners") + partnerweb.Get("/getpartners", controllers.GetPartners) + partnerweb.Get("/getlocations", controllers.GetLocationConfig) + partnerweb.Get("/getridershifts", controllers.GetRiderShifts) + partnerweb.Get("/getriderpricing", controllers.GetRiderPricing) + + productsweb := liveweb.Group("v1/products") + productsweb.Post("/create", controllers.CreateProduct) + productsweb.Post("/createproductlocation", controllers.CreateProductLocation) + productsweb.Put("/updateproductstock", controllers.UpdateProductStock) + productsweb.Get("/getallproducts", controllers.GetAllProducts) + productsweb.Get("/getproductcategories", controllers.GetProductCategory) + productsweb.Get("/getproductsubcategories", controllers.GetProductSubCategory) + productsweb.Get("/getproductscount", controllers.GetProductCount) + productsweb.Get("/getproductvariants", controllers.GetProductVariants) + + deliveries := liveweb.Group("/v1/deliveries") + deliveries.Get("/getdeliveries", controllers.GetDeliveriesV2) + + invoice := liveweb.Group("/v1/invoice") + invoice.Get("/getinvoiceinsight", controllers.GetInvoiceInsight) + invoice.Get("/getallinvoice", controllers.GetallInvoice) + +} diff --git a/tmp/check_schema.go b/tmp/check_schema.go new file mode 100644 index 0000000..4c3eff2 --- /dev/null +++ b/tmp/check_schema.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "nearle/db" + "github.com/joho/godotenv" +) + +func main() { + _ = godotenv.Load() + db.Connect() + if db.DB == nil { + fmt.Println("❌ Failed to connect to DB") + return + } + + tables := []string{"deliveries", "deliveryqueues", "deliverylogs"} + for _, table := range tables { + fmt.Printf("\n--- Schema for %s ---\n", table) + var results []struct { + ColumnName string `gorm:"column:column_name"` + DataType string `gorm:"column:data_type"` + IsNullable string `gorm:"column:is_nullable"` + ColumnDefault *string `gorm:"column:column_default"` + } + db.DB.Raw(` + SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_name = ? + AND column_name LIKE '%id' + `, table).Scan(&results) + + for _, r := range results { + def := "NULL" + if r.ColumnDefault != nil { + def = *r.ColumnDefault + } + fmt.Printf("Col: %-15s | Type: %-10s | Null: %-3s | Def: %s\n", r.ColumnName, r.DataType, r.IsNullable, def) + } + } +} diff --git a/utils/config.go b/utils/config.go new file mode 100644 index 0000000..85c4350 --- /dev/null +++ b/utils/config.go @@ -0,0 +1,46 @@ +package utils + +import ( + "github.com/spf13/viper" +) + +func DevConfig() (Port, DBname, Password, Username, Host, key, secret string) { + + viper.SetConfigName("config") // config file name without extension + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + + err := viper.ReadInConfig() + if err != nil { + Logger.Fatalf("fatal error config file: default \n %v", err) + } + + dbname := viper.GetString("DEV.DATABASE_NAME") + dbpassword := viper.GetString("DEV.DATABASE_PASSWORD") + dbusername := viper.GetString("DEV.DATABASE_USERNAME") + dbport := viper.GetString("DEV.DATABASE_PORT") + dbhost := viper.GetString("DEV.DATABASE_SERVER_HOST") + contextkey := viper.GetString("DEV.USER_CONTEXT_KEY") + jwtkey := viper.GetString("DEV.JWT_SECRET_KEY") + return dbport, dbname, dbpassword, dbusername, dbhost, contextkey, jwtkey +} + +func LiveConfig() (Port, DBname, Password, Username, Host, key, secret string) { + + viper.SetConfigName("config") // config file name without extension + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + + err := viper.ReadInConfig() + if err != nil { + Logger.Fatalf("fatal error config file: default \n %v", err) + } + dbname := viper.GetString("APP.DATABASE_NAME") + dbpassword := viper.GetString("APP.DATABASE_PASSWORD") + dbusername := viper.GetString("APP.DATABASE_USERNAME") + dbport := viper.GetString("APP.DATABASE_PORT") + dbhost := viper.GetString("APP.DATABASE_SERVER_HOST") + contextkey := viper.GetString("APP.USER_CONTEXT_KEY") + jwtkey := viper.GetString("APP.JWT_SECRET_KEY") + return dbport, dbname, dbpassword, dbusername, dbhost, contextkey, jwtkey +} diff --git a/utils/logger.go b/utils/logger.go new file mode 100644 index 0000000..8a069ac --- /dev/null +++ b/utils/logger.go @@ -0,0 +1,42 @@ +package utils + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Logger *zap.SugaredLogger + +func init() { + config := zap.NewProductionConfig() + config.EncoderConfig.TimeKey = "time" + config.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder + config.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + + l, err := config.Build() + if err != nil { + // Fallback to basic logger if zap fails + l, _ = zap.NewDevelopment() + } + Logger = l.Sugar() +} + +// Info logs messages at level info +func Info(msg string, keysAndValues ...interface{}) { + Logger.Infow(msg, keysAndValues...) +} + +// Error logs messages at level error +func Error(msg string, keysAndValues ...interface{}) { + Logger.Errorw(msg, keysAndValues...) +} + +// Debug logs messages at level debug +func Debug(msg string, keysAndValues ...interface{}) { + Logger.Debugw(msg, keysAndValues...) +} + +// Warn logs messages at level warn +func Warn(msg string, keysAndValues ...interface{}) { + Logger.Warnw(msg, keysAndValues...) +}