From 7259cd1393663599075132259904a8d9f79759a8 Mon Sep 17 00:00:00 2001 From: areeqakbr Date: Tue, 24 Jun 2025 15:11:42 +0700 Subject: [PATCH] adding for towerID response and request --- delivery/controller/device_details.go | 10 +++ delivery/controller/devices_controller.go | 1 + model/dto/req/deviceDetails.go | 8 +- model/dto/req/device_dto.go | 2 + model/dto/res/device_details.go | 1 + model/entity/device_details.go | 8 +- model/entity/devices.go | 3 + repository/device_details.go | 104 +++++++++++++++++++++- repository/devices_repo.go | 101 +++++++++++++++++++-- repository/tower_repo.go | 9 ++ usecase/device_details.go | 24 ++++- usecase/device_usecase.go | 30 +++++++ usecase/tower_usecase.go | 43 ++++++--- utils/helper/device_details.go | 45 ++++++++-- 14 files changed, 362 insertions(+), 27 deletions(-) diff --git a/delivery/controller/device_details.go b/delivery/controller/device_details.go index 060fe76..adf043c 100644 --- a/delivery/controller/device_details.go +++ b/delivery/controller/device_details.go @@ -459,6 +459,16 @@ func (c *DeviceDetailsController) updateDeviceDetails(ctx *gin.Context) { deviceUpdateDTO.District = &district } + // Handle TowerID + if towerIDStr := ctx.PostForm("tower_id"); towerIDStr != "" { + if towerID, err := uuid.Parse(towerIDStr); err == nil { + deviceUpdateDTO.TowerID = &towerID + } else { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid tower ID format") + return + } + } + // Get multiple image files form := ctx.Request.MultipartForm imageFiles := form.File["images"] // Support multiple images diff --git a/delivery/controller/devices_controller.go b/delivery/controller/devices_controller.go index 616a24c..876760a 100644 --- a/delivery/controller/devices_controller.go +++ b/delivery/controller/devices_controller.go @@ -315,6 +315,7 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc { return } + // Handle multipart form request err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images if err != nil { diff --git a/model/dto/req/deviceDetails.go b/model/dto/req/deviceDetails.go index 76127c9..aa07df6 100644 --- a/model/dto/req/deviceDetails.go +++ b/model/dto/req/deviceDetails.go @@ -1,6 +1,10 @@ package req -import "users_management/m/model/entity" +import ( + "users_management/m/model/entity" + + "github.com/google/uuid" +) type DeviceDetailsDTO struct { DeviceCode string `json:"device_code" validate:"required"` @@ -13,6 +17,7 @@ type DeviceDetailsDTO struct { Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"` + TowerID *uuid.UUID `json:"tower_id,omitempty"` } type UpdateDeviceDetailsDTO struct { @@ -26,6 +31,7 @@ type UpdateDeviceDetailsDTO struct { Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"` + TowerID *uuid.UUID `json:"tower_id,omitempty"` } type AssignMultipleCustomersDTO struct { diff --git a/model/dto/req/device_dto.go b/model/dto/req/device_dto.go index 72202ea..c98096f 100644 --- a/model/dto/req/device_dto.go +++ b/model/dto/req/device_dto.go @@ -12,6 +12,7 @@ type DeviceDTO struct { Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"` + TowerID *uuid.UUID `json:"tower_id,omitempty"` } type UpdateDeviceDTO struct { @@ -24,6 +25,7 @@ type UpdateDeviceDTO struct { Province *string `json:"province,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"` + TowerID *uuid.UUID `json:"tower_id,omitempty"` } type BulkDeviceImageUploadDTO struct { diff --git a/model/dto/res/device_details.go b/model/dto/res/device_details.go index a00dcbe..b3eebe3 100644 --- a/model/dto/res/device_details.go +++ b/model/dto/res/device_details.go @@ -26,6 +26,7 @@ type DeviceDetailsResponse struct { District *string `json:"district,omitempty"` ImageURL *string `json:"image_url,omitempty"` ImageURLs []string `json:"image_urls"` + TowerID *uuid.UUID `json:"tower_id,omitempty"` // Add TowerID // Connection details Backbones []BackboneConnectionInfo `json:"backbones"` diff --git a/model/entity/device_details.go b/model/entity/device_details.go index 4b4ac31..8c51912 100644 --- a/model/entity/device_details.go +++ b/model/entity/device_details.go @@ -18,16 +18,20 @@ type DeviceDetails struct { City *string `json:"city,omitempty" gorm:"type:varchar(255)"` District *string `json:"district,omitempty" gorm:"type:varchar(255)"` ImageURL *string `json:"image_url,omitempty" gorm:"type:text"` - ImageURLs StringSlice `json:"additional_images" gorm:"type:jsonb"` // Multiple images stored as JSONB + ImageURLs StringSlice `json:"image_urls" gorm:"type:jsonb"` + OLTID *uuid.UUID `json:"olt_id,omitempty" gorm:"type:uuid"` + TowerID *uuid.UUID `json:"tower_id,omitempty" gorm:"type:uuid"` // Add TowerID field CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - // Fixed Relationships - Use direct foreign keys + // Relationships DevicePort DevicePort `json:"device_port" gorm:"foreignKey:DeviceID;references:ID"` BackbonesStart []Backbone `json:"backbones_start" gorm:"foreignKey:DeviceStartID;references:ID"` BackbonesEnd []Backbone `json:"backbones_end" gorm:"foreignKey:DeviceEndID;references:ID"` FishbonesStart []Fishbone `json:"fishbones_start" gorm:"foreignKey:DeviceStartID;references:ID"` FishbonesEnd []Fishbone `json:"fishbones_end" gorm:"foreignKey:DeviceEndID;references:ID"` + OLT *OLT `json:"olt,omitempty" gorm:"foreignKey:OLTID;references:ID"` + Tower *Tower `json:"tower,omitempty" gorm:"foreignKey:TowerID;references:ID"` // Add Tower relationship Towers []Tower `json:"towers" gorm:"foreignKey:DeviceID;references:ID"` } diff --git a/model/entity/devices.go b/model/entity/devices.go index a20a17c..9741fe1 100644 --- a/model/entity/devices.go +++ b/model/entity/devices.go @@ -33,10 +33,13 @@ type Device struct { ImageURL *string `json:"image_url,omitempty" gorm:"type:text"` ImageURLs StringSlice `json:"image_urls" gorm:"type:jsonb"` // Store multiple images as JSONB OLTID *uuid.UUID `json:"olt_id,omitempty" gorm:"type:uuid"` // Add this field + TowerID *uuid.UUID `json:"tower_id,omitempty" gorm:"type:uuid"` // Add TowerID field + CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` OLT *OLT `json:"olt,omitempty" gorm:"foreignKey:OLTID;references:ID"` + Tower *Tower `json:"tower,omitempty" gorm:"foreignKey:TowerID;references:ID"` } func (d *Device) GetAllImageURLs() []string { diff --git a/repository/device_details.go b/repository/device_details.go index 8a38e5c..aaad4c4 100644 --- a/repository/device_details.go +++ b/repository/device_details.go @@ -39,6 +39,8 @@ type DeviceDetailsRepo interface { RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error GetDevicesWithoutTowers(deviceTypes []string) ([]entity.DeviceDetails, error) GetDevicesWithoutConnections(deviceTypes []string) ([]entity.DeviceDetails, error) + ValidateTowerExists(towerID uuid.UUID) (bool, error) + SyncTowerLocationWithDevice(deviceID uuid.UUID) error } @@ -53,6 +55,48 @@ func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo { } } +func (r *deviceDetailsRepo) ValidateTowerExists(towerID uuid.UUID) (bool, error) { + var count int64 + err := r.db.Model(&entity.Tower{}).Where("id = ?", towerID).Count(&count).Error + return count > 0, err +} + +func (r *deviceDetailsRepo) SyncTowerLocationWithDevice(deviceID uuid.UUID) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // Get device details + var device entity.Device + if err := tx.Where("id = ?", deviceID).First(&device).Error; err != nil { + return fmt.Errorf("device not found: %w", err) + } + + // Update tower location if device has a tower assigned + if device.TowerID != nil { + if err := tx.Model(&entity.Tower{}). + Where("id = ?", *device.TowerID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync tower location: %w", err) + } + } + + // Also update any towers that reference this device via dev_id + if err := tx.Model(&entity.Tower{}). + Where("dev_id = ?", deviceID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync related towers location: %w", err) + } + + return nil + }) +} + func (r *deviceDetailsRepo) GetDevicesWithoutConnections(deviceTypes []string) ([]entity.DeviceDetails, error) { var devices []entity.DeviceDetails @@ -401,6 +445,18 @@ func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignment func (r *deviceDetailsRepo) Create(device entity.Device) error { return r.db.Transaction(func(tx *gorm.DB) error { + // Validate tower exists if TowerID is provided + if device.TowerID != nil { + var towerExists bool + var err error + if towerExists, err = r.ValidateTowerExists(*device.TowerID); err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", device.TowerID.String()) + } + } + // Create device if err := tx.Create(&device).Error; err != nil { return err @@ -416,7 +472,24 @@ func (r *deviceDetailsRepo) Create(device entity.Device) error { UpdatedAt: device.UpdatedAt, } - return tx.Create(&devicePort).Error + if err := tx.Create(&devicePort).Error; err != nil { + return err + } + + // Sync tower location with device location + if device.TowerID != nil { + if err := tx.Model(&entity.Tower{}). + Where("id = ?", *device.TowerID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync tower location: %w", err) + } + } + + return nil }) } @@ -438,6 +511,8 @@ func (r *deviceDetailsRepo) GetAll() ([]entity.DeviceDetails, error) { Preload("FishbonesEnd.DeviceStart"). Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.Backbone"). + Preload("OLT"). + Preload("Tower"). // Add Tower preload Preload("Towers"). Preload("Towers.Device"). Find(&devices).Error @@ -462,6 +537,8 @@ func (r *deviceDetailsRepo) GetByID(id uuid.UUID) (entity.DeviceDetails, error) Preload("FishbonesEnd.DeviceStart"). Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.Backbone"). + Preload("OLT"). + Preload("Tower"). // Add Tower preload Preload("Towers"). Preload("Towers.Device"). Where("id = ?", id). @@ -476,12 +553,35 @@ func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{}) return err } + // If TowerID is updated, sync locations + if towerID, exists := updates["tower_id"]; exists && towerID != nil { + towerUUID := towerID.(uuid.UUID) + // Validate tower exists + var towerExists bool + var err error + if towerExists, err = r.ValidateTowerExists(towerUUID); err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", towerUUID.String()) + } + } + // If port_amount is updated, update device_port if portAmount, exists := updates["port_amount"]; exists { if err := r.updatePortAmountCascade(tx, id, portAmount.(int)); err != nil { return err } } + + // If location is updated, sync with tower + if _, hasLng := updates["longitude"]; hasLng { + if _, hasLat := updates["latitude"]; hasLat { + if err := r.SyncTowerLocationWithDevice(id); err != nil { + return err + } + } + } return nil }) @@ -492,7 +592,9 @@ func (r *deviceDetailsRepo) GetDevicesWithoutTowers(deviceTypes []string) ([]ent query := r.db. Preload("DevicePort"). + Preload("Tower"). // Add Tower preload Where("device_type IN ?", deviceTypes). + Where("tower_id IS NULL"). // Check for NULL TowerID instead of dev_id Where("id NOT IN (?)", r.db.Table("towers"). Select("dev_id"). diff --git a/repository/devices_repo.go b/repository/devices_repo.go index ce10b77..744b733 100644 --- a/repository/devices_repo.go +++ b/repository/devices_repo.go @@ -18,6 +18,8 @@ type DevicesRepo interface { GetByType(deviceType string) ([]entity.Device, error) BulkUpdateImages(updates map[uuid.UUID]string) error BulkUpdateImagesMultiple(updates map[uuid.UUID][]string) error + SyncTowerLocationWithDevice(deviceID uuid.UUID) error + ValidateTowerExists(towerID uuid.UUID) (bool, error) } type devicesRepo struct { @@ -30,6 +32,48 @@ func NewDevicesRepo(db *gorm.DB) DevicesRepo { } } +func (r *devicesRepo) SyncTowerLocationWithDevice(deviceID uuid.UUID) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // Get device details + var device entity.Device + if err := tx.Where("id = ?", deviceID).First(&device).Error; err != nil { + return fmt.Errorf("device not found: %w", err) + } + + // Update tower location if device has a tower assigned + if device.TowerID != nil { + if err := tx.Model(&entity.Tower{}). + Where("id = ?", *device.TowerID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync tower location: %w", err) + } + } + + // Also update any towers that reference this device via dev_id + if err := tx.Model(&entity.Tower{}). + Where("dev_id = ?", deviceID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync related towers location: %w", err) + } + + return nil + }) +} + +func (r *devicesRepo) ValidateTowerExists(towerID uuid.UUID) (bool, error) { + var count int64 + err := r.db.Model(&entity.Tower{}).Where("id = ?", towerID).Count(&count).Error + return count > 0, err +} + func (r *devicesRepo) BulkUpdateImages(updates map[uuid.UUID]string) error { multipleUpdates := make(map[uuid.UUID][]string) for deviceID, imageURL := range updates { @@ -96,6 +140,19 @@ func (r *devicesRepo) Post(device entity.Device) error { return err } + // Sync tower location with device location + if device.TowerID != nil { + if err := tx.Model(&entity.Tower{}). + Where("id = ?", *device.TowerID). + Updates(map[string]interface{}{ + "longitude": device.Longitude, + "latitude": device.Latitude, + "updated_at": time.Now(), + }).Error; err != nil { + return fmt.Errorf("failed to sync tower location: %w", err) + } + } + return nil }) } @@ -107,12 +164,44 @@ func (r *devicesRepo) GetAll() ([]entity.Device, error) { } return devices, nil } -func (r *devicesRepo) Update(id uuid.UUID,updates map[string]interface{}) error { - err := r.db.Model(&entity.Device{}).Where("id = ?", id).Updates(updates).Error - if err != nil { - return err - } - return nil +func (r *devicesRepo) Update(id uuid.UUID, updates map[string]interface{}) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // Validate tower exists if TowerID is being updated + if towerID, exists := updates["TowerID"]; exists && towerID != nil { + towerUUID := towerID.(uuid.UUID) + var towerExists bool + var err error + if towerExists, err = r.ValidateTowerExists(towerUUID); err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", towerUUID.String()) + } + } + + // Update device + if err := tx.Model(&entity.Device{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return err + } + + // If location is updated, sync with tower + if _, hasLng := updates["Longitude"]; hasLng { + if _, hasLat := updates["Latitude"]; hasLat { + if err := r.SyncTowerLocationWithDevice(id); err != nil { + return err + } + } + } + + // If TowerID is updated, sync location + if _, exists := updates["TowerID"]; exists { + if err := r.SyncTowerLocationWithDevice(id); err != nil { + return err + } + } + + return nil + }) } func (r *devicesRepo) GetByID(id uuid.UUID) (entity.Device, error) { diff --git a/repository/tower_repo.go b/repository/tower_repo.go index f61ea31..891a672 100644 --- a/repository/tower_repo.go +++ b/repository/tower_repo.go @@ -16,6 +16,7 @@ type TowerRepo interface { GetByID(id uuid.UUID) (entity.Tower, error) CheckDeviceExists(deviceID uuid.UUID) (bool, error) BulkUpdateImages(updates map[uuid.UUID]string) error + GetDeviceLocation(deviceID uuid.UUID) (longitude, latitude float64, err error) } type towerRepo struct { @@ -28,6 +29,14 @@ func NewTowerRepo(db *gorm.DB) TowerRepo { } } +func (r *towerRepo) GetDeviceLocation(deviceID uuid.UUID) (longitude, latitude float64, err error) { + var device entity.Device + err = r.db.Select("longitude, latitude").Where("id = ?", deviceID).First(&device).Error + if err != nil { + return 0, 0, fmt.Errorf("device not found: %w", err) + } + return device.Longitude, device.Latitude, nil +} func (r *towerRepo) Post(tower entity.Tower) error { err := r.db.Create(&tower).Error diff --git a/usecase/device_details.go b/usecase/device_details.go index 8ee41ae..95efa37 100644 --- a/usecase/device_details.go +++ b/usecase/device_details.go @@ -389,6 +389,17 @@ func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDT if err != nil { return fmt.Errorf("validation error: %w", err) } + + // Validate tower exists if TowerID is provided + if deviceDTO.TowerID != nil { + towerExists, err := u.deviceDetailsRepo.ValidateTowerExists(*deviceDTO.TowerID) + if err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", deviceDTO.TowerID.String()) + } + } newDevice := entity.Device{ ID: uuid.New(), @@ -401,6 +412,7 @@ func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDT Province: deviceDTO.Province, City: deviceDTO.City, District: deviceDTO.District, + TowerID: deviceDTO.TowerID, // Add TowerID CreatedAt: time.Now(), UpdatedAt: time.Now(), } @@ -480,6 +492,17 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.U if deviceDTO.District != nil { updates["district"] = *deviceDTO.District } + if deviceDTO.TowerID != nil { + // Validate tower exists + towerExists, err := u.deviceDetailsRepo.ValidateTowerExists(*deviceDTO.TowerID) + if err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", deviceDTO.TowerID.String()) + } + updates["tower_id"] = *deviceDTO.TowerID + } if len(updates) == 0 { return errors.New("no fields to update") @@ -489,7 +512,6 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.U return u.deviceDetailsRepo.Update(id, updates) } - func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error { // Check if device has connections backbones, err := u.deviceDetailsRepo.GetBackbonesByDeviceID(id) diff --git a/usecase/device_usecase.go b/usecase/device_usecase.go index 5177ecb..acf98ba 100644 --- a/usecase/device_usecase.go +++ b/usecase/device_usecase.go @@ -26,6 +26,8 @@ type DeviceUseCase interface { CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error + + ValidateTowerExists(towerID uuid.UUID) (bool, error) } type deviceUseCase struct { @@ -42,6 +44,10 @@ func NewDeviceUseCase(deviceRepo repository.DevicesRepo, geocoder service.Geocod } } +func (u *deviceUseCase) ValidateTowerExists(towerID uuid.UUID) (bool, error) { + return u.deviceRepo.ValidateTowerExists(towerID) +} + func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error { err := u.validate.Struct(device) if err != nil { @@ -81,6 +87,7 @@ func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, ima Province: device.Province, City: device.City, District: device.District, + TowerID: device.TowerID, // Add TowerID field ImageURL: &primaryImageURL, // Primary image as pointer ImageURLs: entity.StringSlice(imageURLs), // All images CreatedAt: time.Now(), @@ -126,6 +133,18 @@ func (u *deviceUseCase) UpdateDeviceWithMultipleImages(id uuid.UUID, device req. updates["District"] = *device.District } + // Handle TowerID validation and update + if device.TowerID != nil { + towerExists, err := u.ValidateTowerExists(*device.TowerID) + if err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", device.TowerID.String()) + } + updates["TowerID"] = *device.TowerID + } + // Handle multiple image uploads if len(imageFiles) > 0 { // Get current device to handle existing images @@ -296,6 +315,17 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e if device.District != nil { updates["District"] = *device.District } + // Handle TowerID validation and update + if device.TowerID != nil { + towerExists, err := u.ValidateTowerExists(*device.TowerID) + if err != nil { + return fmt.Errorf("failed to validate tower: %w", err) + } + if !towerExists { + return fmt.Errorf("tower with ID %s not found", device.TowerID.String()) + } + updates["TowerID"] = *device.TowerID + } if device.DeviceType != nil && (*device.DeviceType == "OTB" || *device.DeviceType == "ODP") && device.PortAmount != nil && *device.PortAmount <= 0 { return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") } diff --git a/usecase/tower_usecase.go b/usecase/tower_usecase.go index 547b909..1b4e449 100644 --- a/usecase/tower_usecase.go +++ b/usecase/tower_usecase.go @@ -273,11 +273,11 @@ func (u *towerUsecase) BulkUploadImages(towers []req.BulkTowerImageUploadDTO, im } func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, imageFile *multipart.FileHeader) error { - err := u.validate.Struct(tower) + err := u.validate.Struct(tower) if err != nil { return err // Return validation error } - updates := make(map[string]interface{}) + updates := make(map[string]interface{}) if tower.DeviceID != nil { updates["DeviceID"] = *tower.DeviceID @@ -285,21 +285,42 @@ func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, image if tower.TowerCode != nil { updates["TowerCode"] = *tower.TowerCode } - if tower.Longitude != nil { - updates["Longitude"] = *tower.Longitude + + // IMPORTANT: Ignore longitude/latitude updates if tower is assigned to a device + currentTower, err := u.towerRepo.GetByID(id) + if err != nil { + return err } - if tower.Latitude != nil { - updates["Latitude"] = *tower.Latitude + + // If tower has a device assigned, ignore location updates + if currentTower.DeviceID == nil { + // Only allow location updates for external towers (not assigned to devices) + if tower.Longitude != nil { + updates["Longitude"] = *tower.Longitude + } + if tower.Latitude != nil { + updates["Latitude"] = *tower.Latitude + } + } + // If DeviceID is being assigned, sync location with device + if tower.DeviceID != nil && currentTower.DeviceID == nil { + // Get device location using repository method + longitude, latitude, err := u.towerRepo.GetDeviceLocation(*tower.DeviceID) + if err != nil { + return fmt.Errorf("failed to get device location: %w", err) + } + updates["Longitude"] = longitude + updates["Latitude"] = latitude + } + + if tower.ExternalTower != nil { + updates["ExternalTower"] = *tower.ExternalTower } - if tower.ExternalTower != nil { - updates["ExternalTower"] = *tower.ExternalTower - } // Handle image upload if imageFile != nil { // Get current tower to delete old image - currentTower, err := u.towerRepo.GetByID(id) - if err == nil && currentTower.ImageURL != "" { + if currentTower.ImageURL != "" { // Delete old image helper.DeleteTowerImage(currentTower.ImageURL) } diff --git a/utils/helper/device_details.go b/utils/helper/device_details.go index 87cbfe9..f2c4da2 100644 --- a/utils/helper/device_details.go +++ b/utils/helper/device_details.go @@ -82,9 +82,45 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic fishboneInfos = append(fishboneInfos, info) } - // Convert tower connections + // COMBINE ALL TOWERS INTO A SINGLE ARRAY towerInfos := make([]res.TowerConnectionDetail, 0) + + // First, add the assigned tower (tower referenced by device.TowerID) + if device.TowerID != nil && device.Tower != nil { + distance := calculateDistance(device.Latitude, device.Longitude, device.Tower.Latitude, device.Tower.Longitude) + + var externalTower *bool + if device.Tower.ExternalTower != nil { + externalTower = device.Tower.ExternalTower + } + + // Get all tower image URLs + allTowerImageURLs := device.Tower.GetAllImageURLs() + + // Primary image URL + var imageURL *string + if device.Tower.ImageURL != "" { + imageURL = &device.Tower.ImageURL + } + + assignedTowerInfo := res.TowerConnectionDetail{ + ID: device.Tower.ID, + TowerCode: device.Tower.TowerCode, + Distance: distance, + ExternalTower: externalTower, + ImageURL: imageURL, + ImageURLs: allTowerImageURLs, + } + towerInfos = append(towerInfos, assignedTowerInfo) + } + + // Then, add towers that reference this device via dev_id (but avoid duplicates) for _, tower := range device.Towers { + // Skip if this tower is already added as assigned tower + if device.TowerID != nil && tower.ID == *device.TowerID { + continue + } + distance := calculateDistance(device.Latitude, device.Longitude, tower.Latitude, tower.Longitude) var externalTower *bool @@ -112,7 +148,6 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic towerInfos = append(towerInfos, info) } - // REPLACE ALL THE EXISTING PORT ASSIGNMENT CODE WITH THIS: // Convert port assignments - only for ODP devices var finalPortAssignments []res.PortAssignmentResponse var customerNames []string @@ -183,9 +218,11 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic District: device.District, ImageURL: device.ImageURL, ImageURLs: allImageURLs, + TowerID: device.TowerID, // Keep TowerID for reference + // Remove AssignedTower field - now everything is in Towers Backbones: backboneInfos, Fishbones: fishboneInfos, - Towers: towerInfos, + Towers: towerInfos, // All towers combined here CreatedAt: device.CreatedAt, UpdatedAt: device.UpdatedAt, } @@ -194,5 +231,3 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic } - -// ... rest of helper functions remain the same \ No newline at end of file