feat: Add rider substitution system
This commit is contained in:
235
controllers/substitutionController.go
Normal file
235
controllers/substitutionController.go
Normal 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
43
create_table.py
Normal 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()
|
||||
@@ -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
26
migrate_db.go
Normal 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!")
|
||||
}
|
||||
18
models/rider_substitutions.go
Normal file
18
models/rider_substitutions.go
Normal 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"`
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user