feat: Add rider substitution system

This commit is contained in:
2026-06-22 17:38:05 +05:30
parent 6ab508560f
commit 588f27b917
6 changed files with 334 additions and 0 deletions

View File

@@ -0,0 +1,235 @@
package controllers
import (
"nearle/db"
"nearle/models"
"net/http"
"strconv"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm/clause"
)
func GetSubstitutions(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenant_id"))
if tenantID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Missing tenant_id",
"status": false,
})
}
date := c.Query("date")
fromDate := c.Query("from_date")
toDate := c.Query("to_date")
status := c.Query("status")
query := db.DB.Model(&models.RiderSubstitution{}).Where("tenant_id = ?", tenantID)
if date != "" {
query = query.Where("sub_date = ?", date)
}
if fromDate != "" && toDate != "" {
query = query.Where("sub_date >= ? AND sub_date <= ?", fromDate, toDate)
}
if status != "" {
query = query.Where("status = ?", status)
}
var results []models.RiderSubstitution
if err := query.Find(&results).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"count": len(results),
"details": results,
"status": true,
})
}
func GetSubstitutionByID(c *fiber.Ctx) error {
id := c.Params("id")
tenantID, _ := strconv.Atoi(c.Query("tenant_id"))
var result models.RiderSubstitution
if err := db.DB.Where("id = ? AND tenant_id = ?", id, tenantID).First(&result).Error; err != nil {
return c.Status(http.StatusNotFound).JSON(fiber.Map{
"code": http.StatusNotFound,
"message": "Record not found",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"details": result,
"status": true,
})
}
type CreateSubstitutionsRequest struct {
TenantID int `json:"tenant_id"`
Substitutions []models.RiderSubstitution `json:"substitutions"`
}
func CreateSubstitutions(c *fiber.Ctx) error {
var req CreateSubstitutionsRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": err.Error(),
"status": false,
})
}
for i := range req.Substitutions {
req.Substitutions[i].TenantID = req.TenantID
if req.Substitutions[i].Status == "" {
req.Substitutions[i].Status = "scheduled"
}
}
if len(req.Substitutions) == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "No substitutions provided",
"status": false,
})
}
// Upsert to handle unique constraint on (tenant_id, sub_date, absent_rider_id)
if err := db.DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "tenant_id"}, {Name: "sub_date"}, {Name: "absent_rider_id"}},
UpdateAll: true,
}).Create(&req.Substitutions).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"registered": len(req.Substitutions),
"details": req.Substitutions,
"status": true,
})
}
func UpdateSubstitution(c *fiber.Ctx) error {
id := c.Params("id")
var updateData map[string]interface{}
if err := c.BodyParser(&updateData); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": err.Error(),
"status": false,
})
}
tenantIDStr := updateData["tenant_id"]
if tenantIDStr == nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Missing tenant_id in body",
"status": false,
})
}
var tenantID int
switch v := tenantIDStr.(type) {
case float64:
tenantID = int(v)
case string:
tenantID, _ = strconv.Atoi(v)
}
if err := db.DB.Model(&models.RiderSubstitution{}).Where("id = ? AND tenant_id = ?", id, tenantID).Updates(updateData).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Updated successfully",
"status": true,
})
}
func UpdateSubstitutionStatus(c *fiber.Ctx) error {
id := c.Params("id")
var req struct {
TenantID int `json:"tenant_id"`
Status string `json:"status"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": err.Error(),
"status": false,
})
}
if req.TenantID == 0 || req.Status == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Missing tenant_id or status",
"status": false,
})
}
if err := db.DB.Model(&models.RiderSubstitution{}).Where("id = ? AND tenant_id = ?", id, req.TenantID).Update("status", req.Status).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Status updated successfully",
"status": true,
})
}
func DeleteSubstitution(c *fiber.Ctx) error {
id := c.Params("id")
tenantID, _ := strconv.Atoi(c.Query("tenant_id"))
if tenantID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Missing tenant_id",
"status": false,
})
}
// Soft delete by updating status to 'cancelled'
if err := db.DB.Model(&models.RiderSubstitution{}).Where("id = ? AND tenant_id = ?", id, tenantID).Update("status", "cancelled").Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Deleted successfully",
"status": true,
})
}

43
create_table.py Normal file
View File

@@ -0,0 +1,43 @@
import psycopg2
def run():
try:
conn = psycopg2.connect(
host="66.116.207.225",
port="6432",
database="nearledb",
user="admin",
password="Package@123#"
)
cursor = conn.cursor()
sql = """
CREATE TABLE IF NOT EXISTS rider_substitutions (
id SERIAL PRIMARY KEY,
tenant_id INT NOT NULL,
sub_date DATE NOT NULL,
absent_rider_id INT NOT NULL,
absent_rider_name VARCHAR(100) NOT NULL,
sub_rider_id INT NOT NULL,
sub_rider_name VARCHAR(100) NOT NULL,
reason VARCHAR(255) NULL,
status VARCHAR(50) NOT NULL DEFAULT 'scheduled',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, sub_date, absent_rider_id)
);
"""
cursor.execute(sql)
conn.commit()
print("Table 'rider_substitutions' successfully created in remote database!")
except Exception as e:
print(f"Error: {e}")
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
conn.close()
if __name__ == "__main__":
run()

View File

@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"nearle/models"
"nearle/utils"
"os"
@@ -88,6 +89,9 @@ func setupDB(database *gorm.DB) {
return
}
// Auto-migrate the rider_substitutions table
database.AutoMigrate(&models.RiderSubstitution{})
// 🔥 K8s SAFE CONFIG
sqlDB.SetMaxOpenConns(30) // total open connections
sqlDB.SetMaxIdleConns(5) // idle connections

26
migrate_db.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"log"
"nearle/db"
"nearle/models"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Println("No .env file found, relying on environment variables")
}
db.Connect()
fmt.Println("Migrating RiderSubstitution table...")
err := db.DB.AutoMigrate(&models.RiderSubstitution{})
if err != nil {
log.Fatalf("Failed to auto migrate: %v", err)
}
fmt.Println("Successfully created rider_substitutions table in the database!")
}

View File

@@ -0,0 +1,18 @@
package models
import "time"
// RiderSubstitution represents the rider_substitutions table
type RiderSubstitution struct {
ID int `json:"id" gorm:"primaryKey;autoIncrement"`
TenantID int `json:"tenant_id" gorm:"not null;uniqueIndex:uq_date_absent"`
SubDate string `json:"sub_date" gorm:"type:date;not null;uniqueIndex:uq_date_absent;index:idx_date;index:idx_tenant_date"`
AbsentRiderID int `json:"absent_rider_id" gorm:"not null;uniqueIndex:uq_date_absent"`
AbsentRiderName string `json:"absent_rider_name" gorm:"size:100;not null"`
SubRiderID int `json:"sub_rider_id" gorm:"not null"`
SubRiderName string `json:"sub_rider_name" gorm:"size:100;not null"`
Reason string `json:"reason" gorm:"size:255"`
Status string `json:"status" gorm:"type:enum('scheduled','active','completed','cancelled');default:'scheduled';not null;index:idx_status"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}

View File

@@ -163,6 +163,14 @@ func LiveSetup(app *fiber.App) {
partner.Get("/getriderlogs", controllers.GetRiderLogsv1)
partner.Delete("/deleteriderlog", controllers.DeleteRiderLogs)
substitutions := live.Group("/v1/substitutions")
substitutions.Get("/", controllers.GetSubstitutions)
substitutions.Get("/:id", controllers.GetSubstitutionByID)
substitutions.Post("/", controllers.CreateSubstitutions)
substitutions.Put("/:id", controllers.UpdateSubstitution)
substitutions.Patch("/:id/status", controllers.UpdateSubstitutionStatus)
substitutions.Delete("/:id", controllers.DeleteSubstitution)
invoice := live.Group("/v1/invoice")
invoice.Get("/getseqno", controllers.InvoiceSeqno)
invoice.Get("/getinvoiceorders", controllers.GetInvoiceOrders)