adding more response for port assignments
This commit is contained in:
parent
567f286d21
commit
ece05cee18
|
|
@ -32,9 +32,40 @@ func (c *DeviceDetailsController) Route() {
|
||||||
deviceDetails.PUT("/:id", c.updateDeviceDetails)
|
deviceDetails.PUT("/:id", c.updateDeviceDetails)
|
||||||
deviceDetails.DELETE("/:id", c.deleteDeviceDetails)
|
deviceDetails.DELETE("/:id", c.deleteDeviceDetails)
|
||||||
deviceDetails.POST("/:id/recalculate-ports", c.recalculatePortUsage)
|
deviceDetails.POST("/:id/recalculate-ports", c.recalculatePortUsage)
|
||||||
|
deviceDetails.POST("/:id/assign-customer", c.assignCustomer)
|
||||||
|
deviceDetails.PUT("/:id/port-usage", c.updatePortUsage) // New endpoint
|
||||||
|
deviceDetails.PUT("/:id/port-assignments", c.updatePortAssignments) // New endpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
deviceID, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request struct {
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
PortNumber *int `json:"port_number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.deviceDetailsUC.AssignCustomerToPort(deviceID, request.CustomerName, request.PortNumber)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Customer assigned successfully", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (c *DeviceDetailsController) getAllDeviceDetails(ctx *gin.Context) {
|
func (c *DeviceDetailsController) getAllDeviceDetails(ctx *gin.Context) {
|
||||||
devices, err := c.deviceDetailsUC.GetAllDeviceDetails()
|
devices, err := c.deviceDetailsUC.GetAllDeviceDetails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -134,3 +165,49 @@ func (c *DeviceDetailsController) recalculatePortUsage(ctx *gin.Context) {
|
||||||
|
|
||||||
common.SingleResponses(ctx, "Port usage recalculated successfully", nil)
|
common.SingleResponses(ctx, "Port usage recalculated successfully", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *DeviceDetailsController) updatePortUsage(ctx *gin.Context) {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
deviceID, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request req.UpdatePortUsageDTO
|
||||||
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.deviceDetailsUC.UpdatePortUsage(deviceID, request.PortUsed)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Port usage updated successfully", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DeviceDetailsController) updatePortAssignments(ctx *gin.Context) {
|
||||||
|
id := ctx.Param("id")
|
||||||
|
deviceID, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request req.UpdatePortAssignmentsDTO
|
||||||
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.deviceDetailsUC.UpdatePortAssignments(deviceID, request.PortAssignments)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Port assignments updated successfully", nil)
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ func (fc *FishboneController) Route() {
|
||||||
rg.POST("", fc.CreateFishbone())
|
rg.POST("", fc.CreateFishbone())
|
||||||
rg.GET("/:uuid", fc.GetFishboneByID())
|
rg.GET("/:uuid", fc.GetFishboneByID())
|
||||||
rg.PUT("/:uuid", fc.UpdateFishbone())
|
rg.PUT("/:uuid", fc.UpdateFishbone())
|
||||||
|
rg.GET("/backbone/:backbone_id", fc.GetFishboneByBackboneID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +37,30 @@ func NewFishboneController(fu usecase.FishboneUseCase, rg *gin.RouterGroup) *Fis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fc *FishboneController) GetFishboneByBackboneID() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
backboneIDStr := c.Param("backbone_id")
|
||||||
|
backboneID, err := uuid.Parse(backboneIDStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid backbone ID format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fishbones, err := fc.fu.GetByBackboneID(backboneID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fishbones) == 0 {
|
||||||
|
common.SingleResponses(c, "No fishbones found for this backbone", []interface{}{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Success", fishbones)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
log.Println("Fetching all fishbones")
|
log.Println("Fetching all fishbones")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"users_management/m/middleware"
|
"users_management/m/middleware"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
|
|
@ -51,6 +52,27 @@ func (tc *TowerController) GetTower() gin.HandlerFunc {
|
||||||
|
|
||||||
func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
contentType := c.GetHeader("Content-Type")
|
||||||
|
|
||||||
|
// Handle JSON request
|
||||||
|
if strings.Contains(contentType, "application/json") {
|
||||||
|
var towerDTO req.TowerDTO
|
||||||
|
if err := c.ShouldBindJSON(&towerDTO); err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid JSON format: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No image file for JSON requests
|
||||||
|
err := tc.tu.Post(towerDTO, nil)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Tower has been created", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
// Parse multipart form
|
// Parse multipart form
|
||||||
err := c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
err := c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -58,6 +80,8 @@ func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Extract form data
|
// Extract form data
|
||||||
deviceIDStr := c.PostForm("dev_id")
|
deviceIDStr := c.PostForm("dev_id")
|
||||||
towerCode := c.PostForm("tower_code")
|
towerCode := c.PostForm("tower_code")
|
||||||
|
|
@ -155,7 +179,28 @@ func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse multipart form
|
contentType := c.GetHeader("Content-Type")
|
||||||
|
|
||||||
|
// Handle JSON request
|
||||||
|
if strings.Contains(contentType, "application/json") {
|
||||||
|
var towerUpdateDTO req.UpdateTowerDTO
|
||||||
|
if err := c.ShouldBindJSON(&towerUpdateDTO); err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid JSON format: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No image file for JSON requests
|
||||||
|
err := tc.tu.UpdateTower(tower_uuid, towerUpdateDTO, nil)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(c, "Tower has been updated", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle multipart form request (existing logic)
|
||||||
err = c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
err = c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form")
|
common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form")
|
||||||
|
|
@ -165,10 +210,13 @@ func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
||||||
// Create update DTO
|
// Create update DTO
|
||||||
towerUpdateDTO := req.UpdateTowerDTO{}
|
towerUpdateDTO := req.UpdateTowerDTO{}
|
||||||
|
|
||||||
// Optional fields
|
// Optional fields - handle form data
|
||||||
if deviceIDStr := c.PostForm("device_id"); deviceIDStr != "" {
|
if deviceIDStr := c.PostForm("device_id"); deviceIDStr != "" {
|
||||||
if deviceID, err := uuid.Parse(deviceIDStr); err == nil {
|
if deviceID, err := uuid.Parse(deviceIDStr); err == nil {
|
||||||
towerUpdateDTO.DeviceID = &deviceID
|
towerUpdateDTO.DeviceID = &deviceID
|
||||||
|
} else {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid device ID format")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,15 +227,36 @@ func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
||||||
if longitudeStr := c.PostForm("longitude"); longitudeStr != "" {
|
if longitudeStr := c.PostForm("longitude"); longitudeStr != "" {
|
||||||
if longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil {
|
if longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil {
|
||||||
towerUpdateDTO.Longitude = &longitude
|
towerUpdateDTO.Longitude = &longitude
|
||||||
|
} else {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid longitude format")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if latitudeStr := c.PostForm("latitude"); latitudeStr != "" {
|
if latitudeStr := c.PostForm("latitude"); latitudeStr != "" {
|
||||||
if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil {
|
if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil {
|
||||||
towerUpdateDTO.Latitude = &latitude
|
towerUpdateDTO.Latitude = &latitude
|
||||||
|
} else {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid latitude format")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle external_tower field
|
||||||
|
if externalTowerStr := c.PostForm("external_tower"); externalTowerStr != "" {
|
||||||
|
if externalTower, err := strconv.ParseBool(externalTowerStr); err == nil {
|
||||||
|
towerUpdateDTO.ExternalTower = &externalTower
|
||||||
|
} else {
|
||||||
|
common.ErrorResponses(c, http.StatusBadRequest, "Invalid external_tower value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle image_url field (for updating image URL directly)
|
||||||
|
if imageURL := c.PostForm("image_url"); imageURL != "" {
|
||||||
|
towerUpdateDTO.ImageURL = &imageURL
|
||||||
|
}
|
||||||
|
|
||||||
// Get image file (optional)
|
// Get image file (optional)
|
||||||
imageFile, _ := c.FormFile("image")
|
imageFile, _ := c.FormFile("image")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ func (s *Server) setupController() {
|
||||||
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
||||||
rg.Use(middleware.AuthMiddleware(s.ucManager.NewUserUsecase()))
|
rg.Use(middleware.AuthMiddleware(s.ucManager.NewUserUsecase()))
|
||||||
rg.Use(middleware.ActivityLoggingMiddleware(s.ucManager.NewActivityLogUsecase()))
|
rg.Use(middleware.ActivityLoggingMiddleware(s.ucManager.NewActivityLogUsecase()))
|
||||||
rg.Use(middleware.RateLimitMiddleware())
|
|
||||||
{
|
{
|
||||||
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route()
|
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route()
|
||||||
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route()
|
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route()
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,22 @@ type UpdateDevicePort struct {
|
||||||
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
||||||
PortNumber *int `json:"port_number,omitempty" validate:"omitempty,min=1"`
|
PortNumber *int `json:"port_number,omitempty" validate:"omitempty,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AssignCustomerToPortDTO struct {
|
||||||
|
CustomerName string `json:"customer_name" binding:"required"`
|
||||||
|
PortNumber *int `json:"port_number,omitempty"` // Optional, will auto-assign if not provided
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePortUsageDTO struct {
|
||||||
|
PortUsed int `json:"port_used" binding:"required,min=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortAssignmentDTO struct {
|
||||||
|
PortNumber int `json:"port_number" binding:"required,min=1"`
|
||||||
|
CustomerName *string `json:"customer_name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePortAssignmentsDTO struct {
|
||||||
|
PortAssignments []PortAssignmentDTO `json:"port_assignments" binding:"required"`
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@ type DeviceDetailsResponse struct {
|
||||||
PortAmount int `json:"port_amount"`
|
PortAmount int `json:"port_amount"`
|
||||||
PortUsed int `json:"port_used"`
|
PortUsed int `json:"port_used"`
|
||||||
PortAvailable int `json:"port_available"`
|
PortAvailable int `json:"port_available"`
|
||||||
|
CustomerNames []string `json:"customer_names"` // Just customer names
|
||||||
|
PortAssignments []PortAssignmentResponse `json:"port_assignments"` // Port details with customers
|
||||||
Region *string `json:"region,omitempty"`
|
Region *string `json:"region,omitempty"`
|
||||||
Province *string `json:"province,omitempty"`
|
Province *string `json:"province,omitempty"`
|
||||||
City *string `json:"city,omitempty"`
|
City *string `json:"city,omitempty"`
|
||||||
|
|
@ -38,3 +40,9 @@ type TowerConnectionDetail struct {
|
||||||
ExternalTower *bool `json:"external_tower"` // Indicates if this is an external tower
|
ExternalTower *bool `json:"external_tower"` // Indicates if this is an external tower
|
||||||
ImageURL *string `json:"image_url,omitempty"`
|
ImageURL *string `json:"image_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PortAssignmentResponse struct {
|
||||||
|
PortNumber int `json:"port_number"`
|
||||||
|
CustomerName *string `json:"customer_name"`
|
||||||
|
IsOccupied bool `json:"is_occupied"`
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ type DeviceResponse struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
PortAmount int `json:"port_amount"`
|
PortAmount int `json:"port_amount"`
|
||||||
Status string `json:"status"` // Always include, even if null
|
Status string `json:"status"` // Always include, even if null
|
||||||
|
CustomerNames []string `json:"customer_names"` // Always include, even if empty
|
||||||
Province *string `json:"province"` // Always include, even if null
|
Province *string `json:"province"` // Always include, even if null
|
||||||
City *string `json:"city"` // Always include, even if null
|
City *string `json:"city"` // Always include, even if null
|
||||||
District *string `json:"district"` // Always include, even if null
|
District *string `json:"district"` // Always include, even if null
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ type TowerResponse struct {
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
ImageURL string `json:"image_url"`
|
ImageURL string `json:"image_url"`
|
||||||
ExternalTower *bool `json:"external_tower,omitempty"` // Make nullable
|
ExternalTower *bool `json:"external_tower"` // Make nullable
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,100 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type StringSlice []string
|
||||||
|
|
||||||
|
func (s StringSlice) Value() (driver.Value, error) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
return json.Marshal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringSlice) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*s = StringSlice{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte: // []byte and []uint8 are the same type in Go
|
||||||
|
bytes = v
|
||||||
|
case string:
|
||||||
|
bytes = []byte(v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot scan %T into StringSlice", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty string case
|
||||||
|
if len(bytes) == 0 {
|
||||||
|
*s = StringSlice{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(bytes, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortAssignment struct {
|
||||||
|
PortNumber int `json:"port_number"`
|
||||||
|
CustomerName *string `json:"customer_name"` // Nullable for empty ports
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortAssignmentResponse struct {
|
||||||
|
PortNumber int `json:"port_number"`
|
||||||
|
CustomerName *string `json:"customer_name"`
|
||||||
|
IsOccupied bool `json:"is_occupied"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortAssignments []PortAssignment
|
||||||
|
|
||||||
|
func (p PortAssignments) Value() (driver.Value, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
return json.Marshal(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PortAssignments) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*p = PortAssignments{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte: // []byte and []uint8 are the same type in Go
|
||||||
|
bytes = v
|
||||||
|
case string:
|
||||||
|
bytes = []byte(v)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot scan %T into PortAssignments", value)
|
||||||
|
}
|
||||||
|
if len(bytes) == 0 {
|
||||||
|
*p = PortAssignments{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(bytes, p)
|
||||||
|
}
|
||||||
|
|
||||||
type DevicePort struct {
|
type DevicePort struct {
|
||||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||||
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
||||||
PortUsed int `json:"port_used"`
|
PortUsed int `json:"port_used"`
|
||||||
PortAvailable int `json:"port_available"`
|
PortAvailable int `json:"port_available"`
|
||||||
CustomerCount int `json:"customer_count"` // Add this field
|
CustomerCount int `json:"customer_count"`
|
||||||
CustomerNames []string `json:"customer_names" gorm:"type:json"` // Store customer names as JSON array
|
CustomerNames StringSlice `json:"customer_names" gorm:"type:jsonb"`
|
||||||
|
PortAssignments PortAssignments `json:"port_assignments" gorm:"type:jsonb"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
|
@ -19,6 +102,166 @@ type DevicePort struct {
|
||||||
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
|
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method to get customer names with port numbers
|
||||||
|
func (dp *DevicePort) GetCustomerNamesWithPorts() []string {
|
||||||
|
var result []string
|
||||||
|
for _, assignment := range dp.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
result = append(result, fmt.Sprintf("%d: %s", assignment.PortNumber, *assignment.CustomerName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no port assignments, fall back to customer names
|
||||||
|
if len(result) == 0 {
|
||||||
|
for i, name := range dp.CustomerNames {
|
||||||
|
if name != "" {
|
||||||
|
result = append(result, fmt.Sprintf("%d: %s", i+1, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get only customer names
|
||||||
|
func (dp *DevicePort) GetCustomerNames() []string {
|
||||||
|
var result []string
|
||||||
|
for _, assignment := range dp.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
result = append(result, *assignment.CustomerName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no port assignments, fall back to customer names
|
||||||
|
if len(result) == 0 {
|
||||||
|
for _, name := range dp.CustomerNames {
|
||||||
|
if name != "" {
|
||||||
|
result = append(result, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dp *DevicePort) GetPortAssignmentsWithDetails() []PortAssignmentResponse {
|
||||||
|
// If both port_used and port_available are 0, return empty array
|
||||||
|
if dp.PortUsed == 0 && dp.PortAvailable == 0 {
|
||||||
|
return []PortAssignmentResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If port_used is 0 but port_available > 0, still return empty array
|
||||||
|
// This means the device has capacity but no ports are currently used
|
||||||
|
if dp.PortUsed == 0 {
|
||||||
|
return []PortAssignmentResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []PortAssignmentResponse
|
||||||
|
|
||||||
|
// Step 1: Create a map of all port assignments
|
||||||
|
portMap := make(map[int]*string)
|
||||||
|
maxPort := 0
|
||||||
|
|
||||||
|
if len(dp.PortAssignments) > 0 {
|
||||||
|
for _, assignment := range dp.PortAssignments {
|
||||||
|
portMap[assignment.PortNumber] = assignment.CustomerName
|
||||||
|
if assignment.PortNumber > maxPort {
|
||||||
|
maxPort = assignment.PortNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: use CustomerNames array
|
||||||
|
for i, name := range dp.CustomerNames {
|
||||||
|
if name != "" {
|
||||||
|
portMap[i+1] = &name
|
||||||
|
}
|
||||||
|
if i+1 > maxPort {
|
||||||
|
maxPort = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no ports have assignments and port_used > 0, create empty assignments up to port_used
|
||||||
|
if maxPort == 0 && dp.PortUsed > 0 {
|
||||||
|
maxPort = dp.PortUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Determine which ports should be occupied
|
||||||
|
// First, collect all ports that have customers
|
||||||
|
portsWithCustomers := make([]int, 0)
|
||||||
|
for portNum, customerName := range portMap {
|
||||||
|
if customerName != nil && *customerName != "" {
|
||||||
|
portsWithCustomers = append(portsWithCustomers, portNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort to get them in order
|
||||||
|
sort.Ints(portsWithCustomers)
|
||||||
|
|
||||||
|
// Step 3: Determine occupied ports
|
||||||
|
occupiedPorts := make(map[int]bool)
|
||||||
|
|
||||||
|
// All ports with customers are occupied
|
||||||
|
for _, portNum := range portsWithCustomers {
|
||||||
|
occupiedPorts[portNum] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill remaining slots to reach port_used
|
||||||
|
occupiedCount := len(portsWithCustomers)
|
||||||
|
if occupiedCount < dp.PortUsed {
|
||||||
|
// Need to occupy more ports (ports without customer names but still occupied)
|
||||||
|
for i := 1; i <= maxPort && occupiedCount < dp.PortUsed; i++ {
|
||||||
|
if !occupiedPorts[i] {
|
||||||
|
occupiedPorts[i] = true
|
||||||
|
occupiedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still haven't reached port_used, extend maxPort
|
||||||
|
for i := maxPort + 1; occupiedCount < dp.PortUsed; i++ {
|
||||||
|
occupiedPorts[i] = true
|
||||||
|
occupiedCount++
|
||||||
|
maxPort = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Create the result only for ports that are either occupied or have customers
|
||||||
|
for i := 1; i <= maxPort; i++ {
|
||||||
|
// Only include ports that are occupied or have customers
|
||||||
|
if occupiedPorts[i] || (portMap[i] != nil && *portMap[i] != "") {
|
||||||
|
var customerName *string
|
||||||
|
if portMap[i] != nil {
|
||||||
|
customerName = portMap[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, PortAssignmentResponse{
|
||||||
|
PortNumber: i,
|
||||||
|
CustomerName: customerName,
|
||||||
|
IsOccupied: occupiedPorts[i],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
func (dp *DevicePort) GetCustomerNamesOnly() []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
// If we have PortAssignments data, use it
|
||||||
|
if len(dp.PortAssignments) > 0 {
|
||||||
|
for _, assignment := range dp.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
result = append(result, *assignment.CustomerName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: use CustomerNames directly
|
||||||
|
for _, name := range dp.CustomerNames {
|
||||||
|
if name != "" {
|
||||||
|
result = append(result, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (DevicePort) TableName() string {
|
func (DevicePort) TableName() string {
|
||||||
return "device_ports"
|
return "device_ports"
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -28,8 +29,11 @@ type DeviceDetailsRepo interface {
|
||||||
|
|
||||||
// Validation helpers
|
// Validation helpers
|
||||||
GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error)
|
GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error)
|
||||||
AssignCustomerToPort(deviceID uuid.UUID, customerName string) error
|
AssignCustomerToPort(deviceID uuid.UUID, customerName string, portNumber *int) error
|
||||||
RemoveCustomerFromPort(deviceID uuid.UUID, customerName string) error
|
RemoveCustomerFromPort(deviceID uuid.UUID, customerName string) error
|
||||||
|
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
||||||
|
UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type deviceDetailsRepo struct {
|
type deviceDetailsRepo struct {
|
||||||
|
|
@ -42,6 +46,83 @@ func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error {
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Lock both device and device_port records
|
||||||
|
var device entity.Device
|
||||||
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
|
Where("id = ?", deviceID).First(&device).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only ODP devices can have port assignments
|
||||||
|
if device.DeviceType != "ODP" {
|
||||||
|
return fmt.Errorf("port assignments can only be updated for ODP devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
var devicePort entity.DevicePort
|
||||||
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
|
Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil {
|
||||||
|
return fmt.Errorf("device port record not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate port numbers are within device capacity
|
||||||
|
for _, assignment := range assignments {
|
||||||
|
if assignment.PortNumber < 1 || assignment.PortNumber > device.PortAmount {
|
||||||
|
return fmt.Errorf("port number %d is out of range (1-%d)", assignment.PortNumber, device.PortAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize port assignments array if needed
|
||||||
|
if len(devicePort.PortAssignments) == 0 {
|
||||||
|
devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount)
|
||||||
|
for i := 0; i < device.PortAmount; i++ {
|
||||||
|
devicePort.PortAssignments[i] = entity.PortAssignment{
|
||||||
|
PortNumber: i + 1,
|
||||||
|
CustomerName: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate customer names (if not null)
|
||||||
|
customerNames := make(map[string]int) // map customer name to port number
|
||||||
|
for _, assignment := range assignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
if existingPort, exists := customerNames[*assignment.CustomerName]; exists {
|
||||||
|
return fmt.Errorf("customer %s is assigned to multiple ports (%d and %d)",
|
||||||
|
*assignment.CustomerName, existingPort, assignment.PortNumber)
|
||||||
|
}
|
||||||
|
customerNames[*assignment.CustomerName] = assignment.PortNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update port assignments
|
||||||
|
for _, assignment := range assignments {
|
||||||
|
portIndex := assignment.PortNumber - 1
|
||||||
|
if portIndex < len(devicePort.PortAssignments) {
|
||||||
|
devicePort.PortAssignments[portIndex].PortNumber = assignment.PortNumber
|
||||||
|
devicePort.PortAssignments[portIndex].CustomerName = assignment.CustomerName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update counters and backward compatibility fields
|
||||||
|
r.updateDevicePortCounters(&devicePort)
|
||||||
|
|
||||||
|
// Calculate port usage based on assignments
|
||||||
|
portUsed := 0
|
||||||
|
for _, assignment := range devicePort.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
portUsed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devicePort.PortUsed = portUsed
|
||||||
|
devicePort.PortAvailable = device.PortAmount - portUsed
|
||||||
|
devicePort.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
return tx.Save(&devicePort).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (r *deviceDetailsRepo) Create(device entity.Device) error {
|
func (r *deviceDetailsRepo) Create(device entity.Device) error {
|
||||||
|
|
@ -154,111 +235,64 @@ func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.U
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error {
|
func (r *deviceDetailsRepo) UpdatePortUsage(deviceID uuid.UUID, portUsed int) error {
|
||||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
// Lock the device record to prevent race conditions
|
|
||||||
var device entity.Device
|
var device entity.Device
|
||||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
Where("id = ?", deviceID).First(&device).Error; err != nil {
|
Where("id = ?", deviceID).First(&device).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var portUsed int
|
if portUsed > device.PortAmount {
|
||||||
var customerCount int
|
return fmt.Errorf("port_used (%d) cannot exceed port_amount (%d)", portUsed, device.PortAmount)
|
||||||
var customerNames []string
|
|
||||||
|
|
||||||
switch device.DeviceType {
|
|
||||||
case "OTB":
|
|
||||||
// For OTB: count backbones (each backbone uses 1 port)
|
|
||||||
var backboneCount int64
|
|
||||||
if err := tx.Model(&entity.Backbone{}).
|
|
||||||
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
|
|
||||||
Count(&backboneCount).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
portUsed = int(backboneCount)
|
|
||||||
customerCount = 0 // OTB doesn't serve customers directly
|
|
||||||
|
|
||||||
case "closure":
|
|
||||||
// For closure: count fishbones where this device is the start device
|
|
||||||
var fishboneCount int64
|
|
||||||
if err := tx.Model(&entity.Fishbone{}).
|
|
||||||
Where("dev_start_id = ?", deviceID).
|
|
||||||
Count(&fishboneCount).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
portUsed = int(fishboneCount)
|
|
||||||
customerCount = 0 // Closure doesn't serve customers directly
|
|
||||||
|
|
||||||
case "ODP":
|
|
||||||
// For ODP: sum fishbone core amounts where this device is the end device
|
|
||||||
var totalCores int64
|
|
||||||
if err := tx.Model(&entity.Fishbone{}).
|
|
||||||
Where("dev_end_id = ?", deviceID).
|
|
||||||
Select("COALESCE(SUM(core_amount), 0)").
|
|
||||||
Scan(&totalCores).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
portUsed = int(totalCores)
|
|
||||||
|
|
||||||
// For ODP: customer count should equal port_used
|
|
||||||
// Get existing customer assignments
|
|
||||||
var existingDevicePort entity.DevicePort
|
|
||||||
if err := tx.Where("device_id = ?", deviceID).First(&existingDevicePort).Error; err == nil {
|
|
||||||
customerNames = existingDevicePort.CustomerNames
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure customer count matches port_used for ODP
|
var devicePort entity.DevicePort
|
||||||
customerCount = portUsed
|
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||||
|
Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil {
|
||||||
// If we have more customers than ports used, trim the list
|
return fmt.Errorf("device port record not found: %w", err)
|
||||||
if len(customerNames) > portUsed {
|
|
||||||
customerNames = customerNames[:portUsed]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
// Initialize port assignments if empty
|
||||||
portUsed = 0
|
if len(devicePort.PortAssignments) == 0 {
|
||||||
customerCount = 0
|
devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount)
|
||||||
|
for i := 0; i < device.PortAmount; i++ {
|
||||||
|
devicePort.PortAssignments[i] = entity.PortAssignment{
|
||||||
|
PortNumber: i + 1,
|
||||||
|
CustomerName: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate port available
|
// Update port assignments based on new port_used value
|
||||||
portAvailable := device.PortAmount - portUsed
|
currentCustomerCount := 0
|
||||||
if portAvailable < 0 {
|
for _, assignment := range devicePort.PortAssignments {
|
||||||
portAvailable = 0
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
currentCustomerCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update or create DevicePort record with locking
|
if portUsed < currentCustomerCount {
|
||||||
result := tx.Set("gorm:query_option", "FOR UPDATE").
|
// Need to remove some customers (keep first N customers)
|
||||||
Model(&entity.DevicePort{}).
|
customersKept := 0
|
||||||
Where("device_id = ?", deviceID).
|
for i := range devicePort.PortAssignments {
|
||||||
Updates(map[string]interface{}{
|
if devicePort.PortAssignments[i].CustomerName != nil && *devicePort.PortAssignments[i].CustomerName != "" {
|
||||||
"port_used": portUsed,
|
if customersKept < portUsed {
|
||||||
"port_available": portAvailable,
|
customersKept++
|
||||||
"customer_count": customerCount,
|
} else {
|
||||||
"customer_names": customerNames,
|
devicePort.PortAssignments[i].CustomerName = nil
|
||||||
"updated_at": gorm.Expr("NOW()"),
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no record was updated, create a new one
|
// Update counters
|
||||||
if result.RowsAffected == 0 {
|
devicePort.PortUsed = portUsed
|
||||||
devicePort := entity.DevicePort{
|
devicePort.PortAvailable = device.PortAmount - portUsed
|
||||||
ID: uuid.New(),
|
r.updateDevicePortCounters(&devicePort)
|
||||||
DeviceID: deviceID,
|
devicePort.UpdatedAt = time.Now()
|
||||||
PortUsed: portUsed,
|
|
||||||
PortAvailable: portAvailable,
|
|
||||||
CustomerCount: customerCount,
|
|
||||||
CustomerNames: customerNames,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
return tx.Create(&devicePort).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return tx.Save(&devicePort).Error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,7 +354,7 @@ func (r *deviceDetailsRepo) Delete(id uuid.UUID) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerName string) error {
|
func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerName string, portNumber *int) error {
|
||||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
// Lock both device and device_port records
|
// Lock both device and device_port records
|
||||||
var device entity.Device
|
var device entity.Device
|
||||||
|
|
@ -340,22 +374,57 @@ func (r *deviceDetailsRepo) AssignCustomerToPort(deviceID uuid.UUID, customerNam
|
||||||
return fmt.Errorf("device port record not found: %w", err)
|
return fmt.Errorf("device port record not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are available ports
|
// Initialize port assignments if empty
|
||||||
if devicePort.PortAvailable <= 0 {
|
if len(devicePort.PortAssignments) == 0 {
|
||||||
|
devicePort.PortAssignments = make(entity.PortAssignments, device.PortAmount)
|
||||||
|
for i := 0; i < device.PortAmount; i++ {
|
||||||
|
devicePort.PortAssignments[i] = entity.PortAssignment{
|
||||||
|
PortNumber: i + 1,
|
||||||
|
CustomerName: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine port number to use
|
||||||
|
var targetPortNumber int
|
||||||
|
if portNumber != nil {
|
||||||
|
targetPortNumber = *portNumber
|
||||||
|
if targetPortNumber < 1 || targetPortNumber > device.PortAmount {
|
||||||
|
return fmt.Errorf("port number must be between 1 and %d", device.PortAmount)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Auto-assign to first available port
|
||||||
|
targetPortNumber = -1
|
||||||
|
for i, assignment := range devicePort.PortAssignments {
|
||||||
|
if assignment.CustomerName == nil || *assignment.CustomerName == "" {
|
||||||
|
targetPortNumber = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if targetPortNumber == -1 {
|
||||||
return fmt.Errorf("no available ports for customer assignment")
|
return fmt.Errorf("no available ports for customer assignment")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if customer is already assigned
|
// Check if the specified port is already occupied
|
||||||
for _, existing := range devicePort.CustomerNames {
|
portIndex := targetPortNumber - 1
|
||||||
if existing == customerName {
|
if devicePort.PortAssignments[portIndex].CustomerName != nil &&
|
||||||
return fmt.Errorf("customer %s is already assigned to this device", customerName)
|
*devicePort.PortAssignments[portIndex].CustomerName != "" {
|
||||||
|
return fmt.Errorf("port %d is already occupied by %s", targetPortNumber, *devicePort.PortAssignments[portIndex].CustomerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if customer is already assigned to another port
|
||||||
|
for i, assignment := range devicePort.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName == customerName {
|
||||||
|
return fmt.Errorf("customer %s is already assigned to port %d", customerName, i+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add customer to the list
|
// Assign customer to port
|
||||||
devicePort.CustomerNames = append(devicePort.CustomerNames, customerName)
|
devicePort.PortAssignments[portIndex].CustomerName = &customerName
|
||||||
devicePort.CustomerCount = len(devicePort.CustomerNames)
|
|
||||||
devicePort.PortAvailable = devicePort.PortAvailable - 1
|
// Update both PortAssignments and CustomerNames for backward compatibility
|
||||||
|
r.updateDevicePortCounters(&devicePort)
|
||||||
devicePort.UpdatedAt = time.Now()
|
devicePort.UpdatedAt = time.Now()
|
||||||
|
|
||||||
return tx.Save(&devicePort).Error
|
return tx.Save(&devicePort).Error
|
||||||
|
|
@ -393,3 +462,36 @@ func (r *deviceDetailsRepo) RemoveCustomerFromPort(deviceID uuid.UUID, customerN
|
||||||
return tx.Save(&devicePort).Error
|
return tx.Save(&devicePort).Error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error {
|
||||||
|
// This method is required by the DeviceDetailsRepo interface
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var devicePort entity.DevicePort
|
||||||
|
if err := tx.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil {
|
||||||
|
return fmt.Errorf("device port record not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update based on port assignments
|
||||||
|
r.updateDevicePortCounters(&devicePort)
|
||||||
|
devicePort.PortUsed = devicePort.CustomerCount
|
||||||
|
devicePort.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
return tx.Save(&devicePort).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *deviceDetailsRepo) updateDevicePortCounters(devicePort *entity.DevicePort) {
|
||||||
|
// Count actual customers and create customer names list
|
||||||
|
customerCount := 0
|
||||||
|
customerNames := make([]string, 0)
|
||||||
|
|
||||||
|
for _, assignment := range devicePort.PortAssignments {
|
||||||
|
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||||
|
customerCount++
|
||||||
|
customerNames = append(customerNames, *assignment.CustomerName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devicePort.CustomerCount = customerCount
|
||||||
|
devicePort.CustomerNames = customerNames
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ type FishboneRepo interface {
|
||||||
CheckDeviceExists(id uuid.UUID) (bool, error)
|
CheckDeviceExists(id uuid.UUID) (bool, error)
|
||||||
Delete(id uuid.UUID) error
|
Delete(id uuid.UUID) error
|
||||||
WithTransaction(fn func(*gorm.DB) error) error
|
WithTransaction(fn func(*gorm.DB) error) error
|
||||||
|
GetByBackboneID(backboneID uuid.UUID) ([]entity.Fishbone, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fishboneRepo struct {
|
type fishboneRepo struct {
|
||||||
|
|
@ -44,6 +45,16 @@ func (r *fishboneRepo) Post(fishbone entity.Fishbone) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *fishboneRepo) GetByBackboneID(backboneID uuid.UUID) ([]entity.Fishbone, error) {
|
||||||
|
var fishbones []entity.Fishbone
|
||||||
|
err := r.db.Where("bb_id = ?", backboneID).
|
||||||
|
Preload("DeviceStart").
|
||||||
|
Preload("DeviceEnd").
|
||||||
|
Preload("Backbone").
|
||||||
|
Find(&fishbones).Error
|
||||||
|
return fishbones, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
||||||
log.Print("Fetching all fishbones with relations")
|
log.Print("Fetching all fishbones with relations")
|
||||||
var fishbones []entity.Fishbone
|
var fishbones []entity.Fishbone
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ type DeviceDetailsUseCase interface {
|
||||||
// Port management
|
// Port management
|
||||||
// ValidatePortUsage(deviceID uuid.UUID, requiredPorts int) error
|
// ValidatePortUsage(deviceID uuid.UUID, requiredPorts int) error
|
||||||
RecalculatePortUsage(deviceID uuid.UUID) error
|
RecalculatePortUsage(deviceID uuid.UUID) error
|
||||||
|
AssignCustomerToPort(deviceID uuid.UUID, customerName string,portNumber *int) error
|
||||||
|
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
||||||
|
UpdatePortAssignments(deviceID uuid.UUID, portAssignments []req.PortAssignmentDTO) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type deviceDetailsUseCase struct {
|
type deviceDetailsUseCase struct {
|
||||||
|
|
@ -41,6 +44,32 @@ func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, geo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (u *deviceDetailsUseCase) UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error {
|
||||||
|
// Validate the assignments
|
||||||
|
if len(assignments) == 0 {
|
||||||
|
return errors.New("port assignments cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate port numbers
|
||||||
|
portNumbers := make(map[int]bool)
|
||||||
|
for _, assignment := range assignments {
|
||||||
|
if portNumbers[assignment.PortNumber] {
|
||||||
|
return fmt.Errorf("duplicate port number: %d", assignment.PortNumber)
|
||||||
|
}
|
||||||
|
portNumbers[assignment.PortNumber] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.deviceDetailsRepo.UpdatePortAssignments(deviceID, assignments)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (u *deviceDetailsUseCase) UpdatePortUsage(deviceID uuid.UUID, portUsed int) error {
|
||||||
|
return u.deviceDetailsRepo.UpdatePortUsage(deviceID, portUsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDTO) error {
|
func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDTO) error {
|
||||||
err := u.validate.Struct(deviceDTO)
|
err := u.validate.Struct(deviceDTO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -174,3 +203,7 @@ func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error {
|
||||||
func (u *deviceDetailsUseCase) RecalculatePortUsage(deviceID uuid.UUID) error {
|
func (u *deviceDetailsUseCase) RecalculatePortUsage(deviceID uuid.UUID) error {
|
||||||
return u.deviceDetailsRepo.UpdateDevicePortUsage(deviceID)
|
return u.deviceDetailsRepo.UpdateDevicePortUsage(deviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *deviceDetailsUseCase) AssignCustomerToPort(deviceID uuid.UUID, customerName string, portNumber *int) error {
|
||||||
|
return u.deviceDetailsRepo.AssignCustomerToPort(deviceID, customerName, portNumber)
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ type FishboneUseCase interface {
|
||||||
UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error
|
UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error
|
||||||
DeleteFishbone(id uuid.UUID) error
|
DeleteFishbone(id uuid.UUID) error
|
||||||
GetFishboneStats() (map[string]interface{}, error)
|
GetFishboneStats() (map[string]interface{}, error)
|
||||||
|
GetByBackboneID(backboneID uuid.UUID) ([]res.FishboneResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fishboneUseCase struct {
|
type fishboneUseCase struct {
|
||||||
|
|
@ -40,6 +41,20 @@ func NewFishboneUseCase(fishboneRepo repository.FishboneRepo, backboneRepo repos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *fishboneUseCase) GetByBackboneID(backboneID uuid.UUID) ([]res.FishboneResponse, error) {
|
||||||
|
fishbones, err := u.fishboneRepo.GetByBackboneID(backboneID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fishboneResp, err := helper.ConvertToFishboneResponses(fishbones, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fishboneResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, image
|
||||||
if tower.Latitude != nil {
|
if tower.Latitude != nil {
|
||||||
updates["Latitude"] = *tower.Latitude
|
updates["Latitude"] = *tower.Latitude
|
||||||
}
|
}
|
||||||
|
if tower.ExternalTower != nil {
|
||||||
|
updates["ExternalTower"] = *tower.ExternalTower
|
||||||
|
}
|
||||||
|
|
||||||
// Handle image upload
|
// Handle image upload
|
||||||
if imageFile != nil {
|
if imageFile != nil {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,84 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
|
||||||
towerInfos = append(towerInfos, info)
|
towerInfos = append(towerInfos, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert entity.PortAssignmentResponse to res.PortAssignmentResponse
|
||||||
|
entityPortAssignments := device.DevicePort.GetPortAssignmentsWithDetails()
|
||||||
|
resPortAssignments := make([]res.PortAssignmentResponse, len(entityPortAssignments))
|
||||||
|
for i, pa := range entityPortAssignments {
|
||||||
|
resPortAssignments[i] = res.PortAssignmentResponse{
|
||||||
|
PortNumber: pa.PortNumber,
|
||||||
|
CustomerName: pa.CustomerName,
|
||||||
|
IsOccupied: pa.IsOccupied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
portAssignments := device.DevicePort.GetPortAssignmentsWithDetails()
|
||||||
|
|
||||||
|
// Ensure we show all ports up to PortAmount
|
||||||
|
portAssignmentMap := make(map[int]res.PortAssignmentResponse)
|
||||||
|
for _, pa := range portAssignments {
|
||||||
|
portAssignmentMap[pa.PortNumber] = res.PortAssignmentResponse{
|
||||||
|
PortNumber: pa.PortNumber,
|
||||||
|
CustomerName: pa.CustomerName,
|
||||||
|
IsOccupied: pa.IsOccupied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in missing ports
|
||||||
|
finalPortAssignments := make([]res.PortAssignmentResponse, 0)
|
||||||
|
for i := 1; i <= device.PortAmount; i++ {
|
||||||
|
if pa, exists := portAssignmentMap[i]; exists {
|
||||||
|
finalPortAssignments = append(finalPortAssignments, pa)
|
||||||
|
} else {
|
||||||
|
// Determine if this port should be occupied
|
||||||
|
isOccupied := false
|
||||||
|
|
||||||
|
// Count occupied ports before this one
|
||||||
|
occupiedBefore := 0
|
||||||
|
for j := 1; j < i; j++ {
|
||||||
|
if existingPA, exists := portAssignmentMap[j]; exists && existingPA.IsOccupied {
|
||||||
|
occupiedBefore++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total ports with customers
|
||||||
|
customersCount := 0
|
||||||
|
for _, pa := range portAssignments {
|
||||||
|
if pa.CustomerName != nil && *pa.CustomerName != "" {
|
||||||
|
customersCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This port should be occupied if we haven't reached port_used yet
|
||||||
|
if occupiedBefore + customersCount < device.DevicePort.PortUsed {
|
||||||
|
isOccupied = true
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPortAssignments = append(finalPortAssignments, res.PortAssignmentResponse{
|
||||||
|
PortNumber: i,
|
||||||
|
CustomerName: nil,
|
||||||
|
IsOccupied: isOccupied,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If port_used is 0, return empty port assignments
|
||||||
|
if device.DevicePort.PortUsed == 0 {
|
||||||
|
finalPortAssignments = []res.PortAssignmentResponse{}
|
||||||
|
} else {
|
||||||
|
// Get port assignments with details
|
||||||
|
portAssignments := device.DevicePort.GetPortAssignmentsWithDetails()
|
||||||
|
|
||||||
|
// Convert to response format
|
||||||
|
finalPortAssignments = make([]res.PortAssignmentResponse, len(portAssignments))
|
||||||
|
for i, pa := range portAssignments {
|
||||||
|
finalPortAssignments[i] = res.PortAssignmentResponse{
|
||||||
|
PortNumber: pa.PortNumber,
|
||||||
|
CustomerName: pa.CustomerName,
|
||||||
|
IsOccupied: pa.IsOccupied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response := res.DeviceDetailsResponse{
|
response := res.DeviceDetailsResponse{
|
||||||
ID: device.ID,
|
ID: device.ID,
|
||||||
DeviceCode: device.DeviceCode,
|
DeviceCode: device.DeviceCode,
|
||||||
|
|
@ -120,6 +198,8 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
|
||||||
PortAmount: device.PortAmount,
|
PortAmount: device.PortAmount,
|
||||||
PortUsed: device.DevicePort.PortUsed,
|
PortUsed: device.DevicePort.PortUsed,
|
||||||
PortAvailable: device.DevicePort.PortAvailable,
|
PortAvailable: device.DevicePort.PortAvailable,
|
||||||
|
CustomerNames: device.DevicePort.GetCustomerNamesOnly(), // Just customer names
|
||||||
|
PortAssignments: finalPortAssignments, // Use finalPortAssignments instead of resPortAssignments
|
||||||
Region: device.Region,
|
Region: device.Region,
|
||||||
Province: device.Province,
|
Province: device.Province,
|
||||||
City: device.City,
|
City: device.City,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue