Merge pull request 'adding for towerID response and request' (#33) from feature/responses-v2 into dev

Reviewed-on: winter-access/backend_nam#33
This commit is contained in:
areeqakbr 2025-06-24 08:16:08 +00:00
commit 90b8fcdef0
14 changed files with 362 additions and 27 deletions

View File

@ -459,6 +459,16 @@ func (c *DeviceDetailsController) updateDeviceDetails(ctx *gin.Context) {
deviceUpdateDTO.District = &district 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 // Get multiple image files
form := ctx.Request.MultipartForm form := ctx.Request.MultipartForm
imageFiles := form.File["images"] // Support multiple images imageFiles := form.File["images"] // Support multiple images

View File

@ -315,6 +315,7 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
return return
} }
// Handle multipart form request // Handle multipart form request
err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images
if err != nil { if err != nil {

View File

@ -1,6 +1,10 @@
package req package req
import "users_management/m/model/entity" import (
"users_management/m/model/entity"
"github.com/google/uuid"
)
type DeviceDetailsDTO struct { type DeviceDetailsDTO struct {
DeviceCode string `json:"device_code" validate:"required"` DeviceCode string `json:"device_code" validate:"required"`
@ -13,6 +17,7 @@ type DeviceDetailsDTO struct {
Province *string `json:"province,omitempty" validate:"omitempty,min=3"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
} }
type UpdateDeviceDetailsDTO struct { type UpdateDeviceDetailsDTO struct {
@ -26,6 +31,7 @@ type UpdateDeviceDetailsDTO struct {
Province *string `json:"province,omitempty" validate:"omitempty,min=3"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
} }
type AssignMultipleCustomersDTO struct { type AssignMultipleCustomersDTO struct {

View File

@ -12,6 +12,7 @@ type DeviceDTO struct {
Province *string `json:"province,omitempty" validate:"omitempty,min=3"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
} }
type UpdateDeviceDTO struct { type UpdateDeviceDTO struct {
@ -24,6 +25,7 @@ type UpdateDeviceDTO struct {
Province *string `json:"province,omitempty" validate:"omitempty,min=3"` Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
} }
type BulkDeviceImageUploadDTO struct { type BulkDeviceImageUploadDTO struct {

View File

@ -26,6 +26,7 @@ type DeviceDetailsResponse struct {
District *string `json:"district,omitempty"` District *string `json:"district,omitempty"`
ImageURL *string `json:"image_url,omitempty"` ImageURL *string `json:"image_url,omitempty"`
ImageURLs []string `json:"image_urls"` ImageURLs []string `json:"image_urls"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` // Add TowerID
// Connection details // Connection details
Backbones []BackboneConnectionInfo `json:"backbones"` Backbones []BackboneConnectionInfo `json:"backbones"`

View File

@ -18,16 +18,20 @@ type DeviceDetails struct {
City *string `json:"city,omitempty" gorm:"type:varchar(255)"` City *string `json:"city,omitempty" gorm:"type:varchar(255)"`
District *string `json:"district,omitempty" gorm:"type:varchar(255)"` District *string `json:"district,omitempty" gorm:"type:varchar(255)"`
ImageURL *string `json:"image_url,omitempty" gorm:"type:text"` 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"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
// Fixed Relationships - Use direct foreign keys // Relationships
DevicePort DevicePort `json:"device_port" gorm:"foreignKey:DeviceID;references:ID"` DevicePort DevicePort `json:"device_port" gorm:"foreignKey:DeviceID;references:ID"`
BackbonesStart []Backbone `json:"backbones_start" gorm:"foreignKey:DeviceStartID;references:ID"` BackbonesStart []Backbone `json:"backbones_start" gorm:"foreignKey:DeviceStartID;references:ID"`
BackbonesEnd []Backbone `json:"backbones_end" gorm:"foreignKey:DeviceEndID;references:ID"` BackbonesEnd []Backbone `json:"backbones_end" gorm:"foreignKey:DeviceEndID;references:ID"`
FishbonesStart []Fishbone `json:"fishbones_start" gorm:"foreignKey:DeviceStartID;references:ID"` FishbonesStart []Fishbone `json:"fishbones_start" gorm:"foreignKey:DeviceStartID;references:ID"`
FishbonesEnd []Fishbone `json:"fishbones_end" gorm:"foreignKey:DeviceEndID;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"` Towers []Tower `json:"towers" gorm:"foreignKey:DeviceID;references:ID"`
} }

View File

@ -33,10 +33,13 @@ type Device struct {
ImageURL *string `json:"image_url,omitempty" gorm:"type:text"` ImageURL *string `json:"image_url,omitempty" gorm:"type:text"`
ImageURLs StringSlice `json:"image_urls" gorm:"type:jsonb"` // Store multiple images as JSONB 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 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"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
OLT *OLT `json:"olt,omitempty" gorm:"foreignKey:OLTID;references:ID"` 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 { func (d *Device) GetAllImageURLs() []string {

View File

@ -39,6 +39,8 @@ type DeviceDetailsRepo interface {
RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error
GetDevicesWithoutTowers(deviceTypes []string) ([]entity.DeviceDetails, error) GetDevicesWithoutTowers(deviceTypes []string) ([]entity.DeviceDetails, error)
GetDevicesWithoutConnections(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) { func (r *deviceDetailsRepo) GetDevicesWithoutConnections(deviceTypes []string) ([]entity.DeviceDetails, error) {
var devices []entity.DeviceDetails var devices []entity.DeviceDetails
@ -401,6 +445,18 @@ func (r *deviceDetailsRepo) UpdatePortAssignments(deviceID uuid.UUID, assignment
func (r *deviceDetailsRepo) Create(device entity.Device) error { func (r *deviceDetailsRepo) Create(device entity.Device) error {
return r.db.Transaction(func(tx *gorm.DB) 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 // Create device
if err := tx.Create(&device).Error; err != nil { if err := tx.Create(&device).Error; err != nil {
return err return err
@ -416,7 +472,24 @@ func (r *deviceDetailsRepo) Create(device entity.Device) error {
UpdatedAt: device.UpdatedAt, 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.DeviceStart").
Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.DeviceEnd").
Preload("FishbonesEnd.Backbone"). Preload("FishbonesEnd.Backbone").
Preload("OLT").
Preload("Tower"). // Add Tower preload
Preload("Towers"). Preload("Towers").
Preload("Towers.Device"). Preload("Towers.Device").
Find(&devices).Error Find(&devices).Error
@ -462,6 +537,8 @@ func (r *deviceDetailsRepo) GetByID(id uuid.UUID) (entity.DeviceDetails, error)
Preload("FishbonesEnd.DeviceStart"). Preload("FishbonesEnd.DeviceStart").
Preload("FishbonesEnd.DeviceEnd"). Preload("FishbonesEnd.DeviceEnd").
Preload("FishbonesEnd.Backbone"). Preload("FishbonesEnd.Backbone").
Preload("OLT").
Preload("Tower"). // Add Tower preload
Preload("Towers"). Preload("Towers").
Preload("Towers.Device"). Preload("Towers.Device").
Where("id = ?", id). Where("id = ?", id).
@ -476,6 +553,20 @@ func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{})
return err 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 port_amount is updated, update device_port
if portAmount, exists := updates["port_amount"]; exists { if portAmount, exists := updates["port_amount"]; exists {
if err := r.updatePortAmountCascade(tx, id, portAmount.(int)); err != nil { if err := r.updatePortAmountCascade(tx, id, portAmount.(int)); err != nil {
@ -483,6 +574,15 @@ func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{})
} }
} }
// 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 return nil
}) })
} }
@ -492,7 +592,9 @@ func (r *deviceDetailsRepo) GetDevicesWithoutTowers(deviceTypes []string) ([]ent
query := r.db. query := r.db.
Preload("DevicePort"). Preload("DevicePort").
Preload("Tower"). // Add Tower preload
Where("device_type IN ?", deviceTypes). Where("device_type IN ?", deviceTypes).
Where("tower_id IS NULL"). // Check for NULL TowerID instead of dev_id
Where("id NOT IN (?)", Where("id NOT IN (?)",
r.db.Table("towers"). r.db.Table("towers").
Select("dev_id"). Select("dev_id").

View File

@ -18,6 +18,8 @@ type DevicesRepo interface {
GetByType(deviceType string) ([]entity.Device, error) GetByType(deviceType string) ([]entity.Device, error)
BulkUpdateImages(updates map[uuid.UUID]string) error BulkUpdateImages(updates map[uuid.UUID]string) error
BulkUpdateImagesMultiple(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 { 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 { func (r *devicesRepo) BulkUpdateImages(updates map[uuid.UUID]string) error {
multipleUpdates := make(map[uuid.UUID][]string) multipleUpdates := make(map[uuid.UUID][]string)
for deviceID, imageURL := range updates { for deviceID, imageURL := range updates {
@ -96,6 +140,19 @@ func (r *devicesRepo) Post(device entity.Device) error {
return err 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 return nil
}) })
} }
@ -107,12 +164,44 @@ func (r *devicesRepo) GetAll() ([]entity.Device, error) {
} }
return devices, nil return devices, nil
} }
func (r *devicesRepo) Update(id uuid.UUID,updates map[string]interface{}) error { func (r *devicesRepo) Update(id uuid.UUID, updates map[string]interface{}) error {
err := r.db.Model(&entity.Device{}).Where("id = ?", id).Updates(updates).Error return r.db.Transaction(func(tx *gorm.DB) error {
if err != nil { // Validate tower exists if TowerID is being updated
return err if towerID, exists := updates["TowerID"]; exists && towerID != nil {
} towerUUID := towerID.(uuid.UUID)
return nil 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) { func (r *devicesRepo) GetByID(id uuid.UUID) (entity.Device, error) {

View File

@ -16,6 +16,7 @@ type TowerRepo interface {
GetByID(id uuid.UUID) (entity.Tower, error) GetByID(id uuid.UUID) (entity.Tower, error)
CheckDeviceExists(deviceID uuid.UUID) (bool, error) CheckDeviceExists(deviceID uuid.UUID) (bool, error)
BulkUpdateImages(updates map[uuid.UUID]string) error BulkUpdateImages(updates map[uuid.UUID]string) error
GetDeviceLocation(deviceID uuid.UUID) (longitude, latitude float64, err error)
} }
type towerRepo struct { 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 { func (r *towerRepo) Post(tower entity.Tower) error {
err := r.db.Create(&tower).Error err := r.db.Create(&tower).Error

View File

@ -390,6 +390,17 @@ func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDT
return fmt.Errorf("validation error: %w", err) 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{ newDevice := entity.Device{
ID: uuid.New(), ID: uuid.New(),
DeviceCode: deviceDTO.DeviceCode, DeviceCode: deviceDTO.DeviceCode,
@ -401,6 +412,7 @@ func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDT
Province: deviceDTO.Province, Province: deviceDTO.Province,
City: deviceDTO.City, City: deviceDTO.City,
District: deviceDTO.District, District: deviceDTO.District,
TowerID: deviceDTO.TowerID, // Add TowerID
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
} }
@ -480,6 +492,17 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.U
if deviceDTO.District != nil { if deviceDTO.District != nil {
updates["district"] = *deviceDTO.District 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 { if len(updates) == 0 {
return errors.New("no fields to update") 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) return u.deviceDetailsRepo.Update(id, updates)
} }
func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error { func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error {
// Check if device has connections // Check if device has connections
backbones, err := u.deviceDetailsRepo.GetBackbonesByDeviceID(id) backbones, err := u.deviceDetailsRepo.GetBackbonesByDeviceID(id)

View File

@ -26,6 +26,8 @@ type DeviceUseCase interface {
CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error
UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error
BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error
ValidateTowerExists(towerID uuid.UUID) (bool, error)
} }
type deviceUseCase struct { 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 { func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error {
err := u.validate.Struct(device) err := u.validate.Struct(device)
if err != nil { if err != nil {
@ -81,6 +87,7 @@ func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, ima
Province: device.Province, Province: device.Province,
City: device.City, City: device.City,
District: device.District, District: device.District,
TowerID: device.TowerID, // Add TowerID field
ImageURL: &primaryImageURL, // Primary image as pointer ImageURL: &primaryImageURL, // Primary image as pointer
ImageURLs: entity.StringSlice(imageURLs), // All images ImageURLs: entity.StringSlice(imageURLs), // All images
CreatedAt: time.Now(), CreatedAt: time.Now(),
@ -126,6 +133,18 @@ func (u *deviceUseCase) UpdateDeviceWithMultipleImages(id uuid.UUID, device req.
updates["District"] = *device.District 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 // Handle multiple image uploads
if len(imageFiles) > 0 { if len(imageFiles) > 0 {
// Get current device to handle existing images // 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 { if device.District != nil {
updates["District"] = *device.District 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 { 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") return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices")
} }

View File

@ -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 { 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 { if err != nil {
return err // Return validation error return err // Return validation error
} }
updates := make(map[string]interface{}) updates := make(map[string]interface{})
if tower.DeviceID != nil { if tower.DeviceID != nil {
updates["DeviceID"] = *tower.DeviceID updates["DeviceID"] = *tower.DeviceID
@ -285,21 +285,42 @@ func (u *towerUsecase) UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO, image
if tower.TowerCode != nil { if tower.TowerCode != nil {
updates["TowerCode"] = *tower.TowerCode 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 // Handle image upload
if imageFile != nil { if imageFile != nil {
// Get current tower to delete old image // Get current tower to delete old image
currentTower, err := u.towerRepo.GetByID(id) if currentTower.ImageURL != "" {
if err == nil && currentTower.ImageURL != "" {
// Delete old image // Delete old image
helper.DeleteTowerImage(currentTower.ImageURL) helper.DeleteTowerImage(currentTower.ImageURL)
} }

View File

@ -82,9 +82,45 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
fishboneInfos = append(fishboneInfos, info) fishboneInfos = append(fishboneInfos, info)
} }
// Convert tower connections // COMBINE ALL TOWERS INTO A SINGLE ARRAY
towerInfos := make([]res.TowerConnectionDetail, 0) 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 { 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) distance := calculateDistance(device.Latitude, device.Longitude, tower.Latitude, tower.Longitude)
var externalTower *bool var externalTower *bool
@ -112,7 +148,6 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
towerInfos = append(towerInfos, info) towerInfos = append(towerInfos, info)
} }
// REPLACE ALL THE EXISTING PORT ASSIGNMENT CODE WITH THIS:
// Convert port assignments - only for ODP devices // Convert port assignments - only for ODP devices
var finalPortAssignments []res.PortAssignmentResponse var finalPortAssignments []res.PortAssignmentResponse
var customerNames []string var customerNames []string
@ -183,9 +218,11 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
District: device.District, District: device.District,
ImageURL: device.ImageURL, ImageURL: device.ImageURL,
ImageURLs: allImageURLs, ImageURLs: allImageURLs,
TowerID: device.TowerID, // Keep TowerID for reference
// Remove AssignedTower field - now everything is in Towers
Backbones: backboneInfos, Backbones: backboneInfos,
Fishbones: fishboneInfos, Fishbones: fishboneInfos,
Towers: towerInfos, Towers: towerInfos, // All towers combined here
CreatedAt: device.CreatedAt, CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt, UpdatedAt: device.UpdatedAt,
} }
@ -194,5 +231,3 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
} }
// ... rest of helper functions remain the same