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.DELETE("/:id", c.deleteDeviceDetails)
|
||||
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) {
|
||||
devices, err := c.deviceDetailsUC.GetAllDeviceDetails()
|
||||
if err != nil {
|
||||
|
|
@ -133,4 +164,50 @@ func (c *DeviceDetailsController) recalculatePortUsage(ctx *gin.Context) {
|
|||
}
|
||||
|
||||
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.GET("/:uuid", fc.GetFishboneByID())
|
||||
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 {
|
||||
return func(c *gin.Context) {
|
||||
log.Println("Fetching all fishbones")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package controller
|
|||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"users_management/m/middleware"
|
||||
"users_management/m/model/dto/req"
|
||||
"users_management/m/usecase"
|
||||
|
|
@ -51,6 +52,27 @@ func (tc *TowerController) GetTower() gin.HandlerFunc {
|
|||
|
||||
func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
||||
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
|
||||
err := c.Request.ParseMultipartForm(10 << 20) // 10MB max
|
||||
if err != nil {
|
||||
|
|
@ -58,6 +80,8 @@ func (tc *TowerController) CreateTower() gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Extract form data
|
||||
deviceIDStr := c.PostForm("dev_id")
|
||||
towerCode := c.PostForm("tower_code")
|
||||
|
|
@ -148,14 +172,35 @@ func (tc *TowerController) GetTowerByID() gin.HandlerFunc {
|
|||
func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id := c.Param("uuid")
|
||||
|
||||
|
||||
tower_uuid, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
common.ErrorResponses(c, http.StatusBadRequest, err.Error())
|
||||
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
|
||||
if err != nil {
|
||||
common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form")
|
||||
|
|
@ -165,10 +210,13 @@ func (tc *TowerController) UpdateTower() gin.HandlerFunc {
|
|||
// Create update DTO
|
||||
towerUpdateDTO := req.UpdateTowerDTO{}
|
||||
|
||||
// Optional fields
|
||||
// Optional fields - handle form data
|
||||
if deviceIDStr := c.PostForm("device_id"); deviceIDStr != "" {
|
||||
if deviceID, err := uuid.Parse(deviceIDStr); err == nil {
|
||||
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 longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil {
|
||||
towerUpdateDTO.Longitude = &longitude
|
||||
} else {
|
||||
common.ErrorResponses(c, http.StatusBadRequest, "Invalid longitude format")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if latitudeStr := c.PostForm("latitude"); latitudeStr != "" {
|
||||
if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil {
|
||||
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)
|
||||
imageFile, _ := c.FormFile("image")
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ func (s *Server) setupController() {
|
|||
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
||||
rg.Use(middleware.AuthMiddleware(s.ucManager.NewUserUsecase()))
|
||||
rg.Use(middleware.ActivityLoggingMiddleware(s.ucManager.NewActivityLogUsecase()))
|
||||
rg.Use(middleware.RateLimitMiddleware())
|
||||
{
|
||||
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route()
|
||||
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route()
|
||||
|
|
|
|||
|
|
@ -10,4 +10,23 @@ type DevicePort struct {
|
|||
type UpdateDevicePort struct {
|
||||
DeviceID *uuid.UUID `json:"device_id,omitempty" validate:"omitempty,min=3"`
|
||||
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"`
|
||||
}
|
||||
|
|
@ -6,29 +6,31 @@ import (
|
|||
)
|
||||
|
||||
type DeviceDetailsResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
DeviceType string `json:"device_type"`
|
||||
Address string `json:"address"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Status string `json:"status"`
|
||||
PortAmount int `json:"port_amount"`
|
||||
PortUsed int `json:"port_used"`
|
||||
PortAvailable int `json:"port_available"`
|
||||
Region *string `json:"region,omitempty"`
|
||||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
ImageURL *string `json:"image_url,omitempty"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
DeviceType string `json:"device_type"`
|
||||
Address string `json:"address"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Status string `json:"status"`
|
||||
PortAmount int `json:"port_amount"`
|
||||
PortUsed int `json:"port_used"`
|
||||
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"`
|
||||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
ImageURL *string `json:"image_url,omitempty"`
|
||||
|
||||
// Connection details
|
||||
Backbones []BackboneConnectionInfo `json:"backbones"`
|
||||
Fishbones []FishboneConnectionInfo `json:"fishbones"`
|
||||
Towers []TowerConnectionDetail `json:"towers"`
|
||||
Backbones []BackboneConnectionInfo `json:"backbones"`
|
||||
Fishbones []FishboneConnectionInfo `json:"fishbones"`
|
||||
Towers []TowerConnectionDetail `json:"towers"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type TowerConnectionDetail struct {
|
||||
|
|
@ -37,4 +39,10 @@ type TowerConnectionDetail struct {
|
|||
Distance float64 `json:"distance_km"` // Distance from tower to device
|
||||
ExternalTower *bool `json:"external_tower"` // Indicates if this is an external tower
|
||||
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"`
|
||||
PortAmount int `json:"port_amount"`
|
||||
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
|
||||
City *string `json:"city"` // 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"`
|
||||
Address string `json:"address"`
|
||||
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"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
|
@ -1,24 +1,267 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
||||
PortUsed int `json:"port_used"`
|
||||
PortAvailable int `json:"port_available"`
|
||||
CustomerCount int `json:"customer_count"` // Add this field
|
||||
CustomerNames []string `json:"customer_names" gorm:"type:json"` // Store customer names as JSON array
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;not null"`
|
||||
PortUsed int `json:"port_used"`
|
||||
PortAvailable int `json:"port_available"`
|
||||
CustomerCount int `json:"customer_count"`
|
||||
CustomerNames StringSlice `json:"customer_names" gorm:"type:jsonb"`
|
||||
PortAssignments PortAssignments `json:"port_assignments" gorm:"type:jsonb"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Relationships
|
||||
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 {
|
||||
return "device_ports"
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"users_management/m/model/dto/req"
|
||||
"users_management/m/model/entity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -28,8 +29,11 @@ type DeviceDetailsRepo interface {
|
|||
|
||||
// Validation helpers
|
||||
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
|
||||
UpdatePortUsage(deviceID uuid.UUID, portUsed int) error
|
||||
UpdatePortAssignments(deviceID uuid.UUID, assignments []req.PortAssignmentDTO) error
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -154,111 +235,64 @@ func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.U
|
|||
}).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 {
|
||||
// Lock the device record to prevent race conditions
|
||||
var device entity.Device
|
||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Where("id = ?", deviceID).First(&device).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var portUsed int
|
||||
var customerCount int
|
||||
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
|
||||
customerCount = portUsed
|
||||
|
||||
// If we have more customers than ports used, trim the list
|
||||
if len(customerNames) > portUsed {
|
||||
customerNames = customerNames[:portUsed]
|
||||
}
|
||||
|
||||
default:
|
||||
portUsed = 0
|
||||
customerCount = 0
|
||||
}
|
||||
|
||||
// Calculate port available
|
||||
portAvailable := device.PortAmount - portUsed
|
||||
if portAvailable < 0 {
|
||||
portAvailable = 0
|
||||
|
||||
if portUsed > device.PortAmount {
|
||||
return fmt.Errorf("port_used (%d) cannot exceed port_amount (%d)", portUsed, device.PortAmount)
|
||||
}
|
||||
|
||||
// Update or create DevicePort record with locking
|
||||
result := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Model(&entity.DevicePort{}).
|
||||
Where("device_id = ?", deviceID).
|
||||
Updates(map[string]interface{}{
|
||||
"port_used": portUsed,
|
||||
"port_available": portAvailable,
|
||||
"customer_count": customerCount,
|
||||
"customer_names": customerNames,
|
||||
"updated_at": gorm.Expr("NOW()"),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
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)
|
||||
}
|
||||
|
||||
// If no record was updated, create a new one
|
||||
if result.RowsAffected == 0 {
|
||||
devicePort := entity.DevicePort{
|
||||
ID: uuid.New(),
|
||||
DeviceID: deviceID,
|
||||
PortUsed: portUsed,
|
||||
PortAvailable: portAvailable,
|
||||
CustomerCount: customerCount,
|
||||
CustomerNames: customerNames,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
// Initialize port assignments if empty
|
||||
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,
|
||||
}
|
||||
}
|
||||
return tx.Create(&devicePort).Error
|
||||
}
|
||||
|
||||
return nil
|
||||
// Update port assignments based on new port_used value
|
||||
currentCustomerCount := 0
|
||||
for _, assignment := range devicePort.PortAssignments {
|
||||
if assignment.CustomerName != nil && *assignment.CustomerName != "" {
|
||||
currentCustomerCount++
|
||||
}
|
||||
}
|
||||
|
||||
if portUsed < currentCustomerCount {
|
||||
// Need to remove some customers (keep first N customers)
|
||||
customersKept := 0
|
||||
for i := range devicePort.PortAssignments {
|
||||
if devicePort.PortAssignments[i].CustomerName != nil && *devicePort.PortAssignments[i].CustomerName != "" {
|
||||
if customersKept < portUsed {
|
||||
customersKept++
|
||||
} else {
|
||||
devicePort.PortAssignments[i].CustomerName = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update counters
|
||||
devicePort.PortUsed = portUsed
|
||||
devicePort.PortAvailable = device.PortAmount - portUsed
|
||||
r.updateDevicePortCounters(&devicePort)
|
||||
devicePort.UpdatedAt = time.Now()
|
||||
|
||||
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 {
|
||||
// Lock both device and device_port records
|
||||
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)
|
||||
}
|
||||
|
||||
// Check if there are available ports
|
||||
if devicePort.PortAvailable <= 0 {
|
||||
return fmt.Errorf("no available ports for customer assignment")
|
||||
}
|
||||
|
||||
// Check if customer is already assigned
|
||||
for _, existing := range devicePort.CustomerNames {
|
||||
if existing == customerName {
|
||||
return fmt.Errorf("customer %s is already assigned to this device", customerName)
|
||||
// Initialize port assignments if empty
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add customer to the list
|
||||
devicePort.CustomerNames = append(devicePort.CustomerNames, customerName)
|
||||
devicePort.CustomerCount = len(devicePort.CustomerNames)
|
||||
devicePort.PortAvailable = devicePort.PortAvailable - 1
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the specified port is already occupied
|
||||
portIndex := targetPortNumber - 1
|
||||
if devicePort.PortAssignments[portIndex].CustomerName != nil &&
|
||||
*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)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign customer to port
|
||||
devicePort.PortAssignments[portIndex].CustomerName = &customerName
|
||||
|
||||
// Update both PortAssignments and CustomerNames for backward compatibility
|
||||
r.updateDevicePortCounters(&devicePort)
|
||||
devicePort.UpdatedAt = time.Now()
|
||||
|
||||
return tx.Save(&devicePort).Error
|
||||
|
|
@ -392,4 +461,37 @@ func (r *deviceDetailsRepo) RemoveCustomerFromPort(deviceID uuid.UUID, customerN
|
|||
|
||||
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)
|
||||
Delete(id uuid.UUID) error
|
||||
WithTransaction(fn func(*gorm.DB) error) error
|
||||
GetByBackboneID(backboneID uuid.UUID) ([]entity.Fishbone, error)
|
||||
}
|
||||
|
||||
type fishboneRepo struct {
|
||||
|
|
@ -44,6 +45,16 @@ func (r *fishboneRepo) Post(fishbone entity.Fishbone) error {
|
|||
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) {
|
||||
log.Print("Fetching all fishbones with relations")
|
||||
var fishbones []entity.Fishbone
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func (u *backboneUseCase) GetByID(id uuid.UUID) (res.BackboneResponse, error) {
|
|||
}
|
||||
|
||||
return backboneResp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *backboneUseCase) UpdateBackbone(id uuid.UUID, backbone req.UpdateBackboneDTO) error {
|
||||
err := u.validate.Struct(backbone)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"users_management/m/model/dto/req"
|
||||
"users_management/m/model/dto/res"
|
||||
"users_management/m/model/entity"
|
||||
"users_management/m/repository"
|
||||
"users_management/m/utils/helper"
|
||||
"users_management/m/utils/service"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
"users_management/m/model/dto/req"
|
||||
"users_management/m/model/dto/res"
|
||||
"users_management/m/model/entity"
|
||||
"users_management/m/repository"
|
||||
"users_management/m/utils/helper"
|
||||
"users_management/m/utils/service"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type DeviceDetailsUseCase interface {
|
||||
|
|
@ -25,6 +25,9 @@ type DeviceDetailsUseCase interface {
|
|||
// Port management
|
||||
// ValidatePortUsage(deviceID uuid.UUID, requiredPorts int) 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 {
|
||||
|
|
@ -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 {
|
||||
err := u.validate.Struct(deviceDTO)
|
||||
if err != nil {
|
||||
|
|
@ -173,4 +202,8 @@ func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error {
|
|||
|
||||
func (u *deviceDetailsUseCase) RecalculatePortUsage(deviceID uuid.UUID) error {
|
||||
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
|
||||
DeleteFishbone(id uuid.UUID) error
|
||||
GetFishboneStats() (map[string]interface{}, error)
|
||||
GetByBackboneID(backboneID uuid.UUID) ([]res.FishboneResponse, error)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -127,6 +127,9 @@ func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, image
|
|||
if tower.Latitude != nil {
|
||||
updates["Latitude"] = *tower.Latitude
|
||||
}
|
||||
if tower.ExternalTower != nil {
|
||||
updates["ExternalTower"] = *tower.ExternalTower
|
||||
}
|
||||
|
||||
// Handle image upload
|
||||
if imageFile != nil {
|
||||
|
|
|
|||
|
|
@ -108,28 +108,108 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
|
|||
}
|
||||
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{
|
||||
ID: device.ID,
|
||||
DeviceCode: device.DeviceCode,
|
||||
DeviceType: string(device.DeviceType),
|
||||
Address: address,
|
||||
Longitude: device.Longitude,
|
||||
Latitude: device.Latitude,
|
||||
Status: string(device.Status),
|
||||
PortAmount: device.PortAmount,
|
||||
PortUsed: device.DevicePort.PortUsed,
|
||||
ID: device.ID,
|
||||
DeviceCode: device.DeviceCode,
|
||||
DeviceType: string(device.DeviceType),
|
||||
Address: address,
|
||||
Longitude: device.Longitude,
|
||||
Latitude: device.Latitude,
|
||||
Status: string(device.Status),
|
||||
PortAmount: device.PortAmount,
|
||||
PortUsed: device.DevicePort.PortUsed,
|
||||
PortAvailable: device.DevicePort.PortAvailable,
|
||||
Region: device.Region,
|
||||
Province: device.Province,
|
||||
City: device.City,
|
||||
District: device.District,
|
||||
ImageURL: device.ImageURL,
|
||||
Backbones: backboneInfos,
|
||||
Fishbones: fishboneInfos,
|
||||
Towers: towerInfos,
|
||||
CreatedAt: device.CreatedAt,
|
||||
UpdatedAt: device.UpdatedAt,
|
||||
CustomerNames: device.DevicePort.GetCustomerNamesOnly(), // Just customer names
|
||||
PortAssignments: finalPortAssignments, // Use finalPortAssignments instead of resPortAssignments
|
||||
Region: device.Region,
|
||||
Province: device.Province,
|
||||
City: device.City,
|
||||
District: device.District,
|
||||
ImageURL: device.ImageURL,
|
||||
Backbones: backboneInfos,
|
||||
Fishbones: fishboneInfos,
|
||||
Towers: towerInfos,
|
||||
CreatedAt: device.CreatedAt,
|
||||
UpdatedAt: device.UpdatedAt,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue