From 512565c49beb9900c90ef7e08da137554c934fd4 Mon Sep 17 00:00:00 2001 From: areeqakbr Date: Mon, 23 Jun 2025 10:21:00 +0700 Subject: [PATCH] adding olt --- delivery/controller/olt_controller.go | 229 +++++++++++++++++++++++++ delivery/server.go | 1 + manager/infra_manager.go | 1 + manager/repository_manager.go | 5 + manager/usecase_manager.go | 5 + model/dto/req/olt.go | 19 +++ model/dto/res/olt.go | 39 +++++ model/entity/devices.go | 3 + model/entity/olt.go | 20 +++ repository/olt_repo.go | 127 ++++++++++++++ usecase/olt_usecase.go | 234 ++++++++++++++++++++++++++ 11 files changed, 683 insertions(+) create mode 100644 delivery/controller/olt_controller.go create mode 100644 model/dto/req/olt.go create mode 100644 model/dto/res/olt.go create mode 100644 model/entity/olt.go create mode 100644 repository/olt_repo.go create mode 100644 usecase/olt_usecase.go diff --git a/delivery/controller/olt_controller.go b/delivery/controller/olt_controller.go new file mode 100644 index 0000000..42bec67 --- /dev/null +++ b/delivery/controller/olt_controller.go @@ -0,0 +1,229 @@ +package controller + +import ( + "net/http" + "strconv" + "users_management/m/model/dto/req" + "users_management/m/usecase" + "users_management/m/utils/common" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type OLTController struct { + oltUC usecase.OLTUsecase + rg *gin.RouterGroup +} + +func NewOLTController(oltUC usecase.OLTUsecase, rg *gin.RouterGroup) *OLTController { + return &OLTController{ + oltUC: oltUC, + rg: rg, + } +} + +func (c *OLTController) Route() { + oltGroup := c.rg.Group("/olt") + { + oltGroup.POST("", c.createOLT) + oltGroup.GET("", c.getAllOLTs) + oltGroup.GET("/:id", c.getOLTByID) + oltGroup.PUT("/:id", c.updateOLT) + oltGroup.DELETE("/:id", c.deleteOLT) + oltGroup.POST("/:id/assign-device", c.assignDeviceToOLT) + oltGroup.DELETE("/unassign-device/:deviceId", c.unassignDeviceFromOLT) + oltGroup.GET("/:id/devices", c.getDevicesByOLT) + } +} + +func (c *OLTController) createOLT(ctx *gin.Context) { + var oltDTO req.OLTDTO + if err := ctx.ShouldBindJSON(&oltDTO); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err := c.oltUC.CreateOLT(oltDTO) + if err != nil { + if err.Error() == "OLT name already exists" { + common.ErrorResponses(ctx, http.StatusConflict, err.Error()) + return + } + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + common.SingleResponses(ctx, "OLT created successfully", gin.H{ + "olt_name": oltDTO.OLTName, + }) +} + +func (c *OLTController) getAllOLTs(ctx *gin.Context) { + // Pagination parameters + page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) + if page < 1 { + page = 1 + } + + olts, err := c.oltUC.GetAllOLTs() + if err != nil { + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + response := gin.H{ + "olts": olts, + "total": len(olts), + "page": page, + } + + common.SingleResponses(ctx, "OLTs retrieved successfully", response) +} + +func (c *OLTController) getOLTByID(ctx *gin.Context) { + idStr := ctx.Param("id") + id, err := uuid.Parse(idStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID") + return + } + + olt, err := c.oltUC.GetOLTByID(id) + if err != nil { + common.ErrorResponses(ctx, http.StatusNotFound, "OLT not found") + return + } + + common.SingleResponses(ctx, "OLT details retrieved successfully", olt) +} + +func (c *OLTController) updateOLT(ctx *gin.Context) { + idStr := ctx.Param("id") + id, err := uuid.Parse(idStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID") + return + } + + var updateDTO req.UpdateOLTDTO + if err := ctx.ShouldBindJSON(&updateDTO); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err = c.oltUC.UpdateOLT(id, updateDTO) + if err != nil { + switch err.Error() { + case "OLT not found": + common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) + case "OLT name already exists": + common.ErrorResponses(ctx, http.StatusConflict, err.Error()) + default: + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + } + return + } + + common.SingleResponses(ctx, "OLT updated successfully", gin.H{ + "olt_id": id, + }) +} + +func (c *OLTController) deleteOLT(ctx *gin.Context) { + idStr := ctx.Param("id") + id, err := uuid.Parse(idStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID") + return + } + + err = c.oltUC.DeleteOLT(id) + if err != nil { + if err.Error() == "OLT not found" { + common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) + return + } + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + common.SingleResponses(ctx, "OLT deleted successfully", gin.H{ + "olt_id": id, + }) +} + +func (c *OLTController) assignDeviceToOLT(ctx *gin.Context) { + oltIDStr := ctx.Param("id") + oltID, err := uuid.Parse(oltIDStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID") + return + } + + var assignDTO req.AssignDeviceToOLTDTO + if err := ctx.ShouldBindJSON(&assignDTO); err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + + err = c.oltUC.AssignDeviceToOLT(oltID, assignDTO.DeviceID) + if err != nil { + if err.Error() == "only ODP devices can be assigned to OLT" { + common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) + return + } + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + common.SingleResponses(ctx, "Device assigned to OLT successfully", gin.H{ + "olt_id": oltID, + "device_id": assignDTO.DeviceID, + }) +} + +func (c *OLTController) unassignDeviceFromOLT(ctx *gin.Context) { + deviceIDStr := ctx.Param("deviceId") + deviceID, err := uuid.Parse(deviceIDStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") + return + } + + err = c.oltUC.UnassignDeviceFromOLT(deviceID) + if err != nil { + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + common.SingleResponses(ctx, "Device unassigned from OLT successfully", gin.H{ + "device_id": deviceID, + }) +} + +func (c *OLTController) getDevicesByOLT(ctx *gin.Context) { + idStr := ctx.Param("id") + id, err := uuid.Parse(idStr) + if err != nil { + common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID") + return + } + + devices, err := c.oltUC.GetDevicesByOLT(id) + if err != nil { + if err.Error() == "OLT not found" { + common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) + return + } + common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) + return + } + + response := gin.H{ + "devices": devices, + "total": len(devices), + } + + common.SingleResponses(ctx, "OLT devices retrieved successfully", response) +} \ No newline at end of file diff --git a/delivery/server.go b/delivery/server.go index 41a6eb8..8ce1aaa 100644 --- a/delivery/server.go +++ b/delivery/server.go @@ -90,6 +90,7 @@ func (s *Server) setupController() { controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), protected).Route() controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), protected, s.cfg).Route() controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), protected, s.cfg).Route() + controller.NewOLTController(s.ucManager.NewOLTUsecase(), protected).Route() } diff --git a/manager/infra_manager.go b/manager/infra_manager.go index 4b6b794..835c67f 100644 --- a/manager/infra_manager.go +++ b/manager/infra_manager.go @@ -47,6 +47,7 @@ func (im *infraManager) autoMigrate(db *gorm.DB) error { &entity.DevicePort{}, &entity.CountAssets{}, &entity.ActivityLog{}, + &entity.OLT{}, ) } diff --git a/manager/repository_manager.go b/manager/repository_manager.go index 084fd2f..3f9d065 100644 --- a/manager/repository_manager.go +++ b/manager/repository_manager.go @@ -16,6 +16,7 @@ type RepositoryManager interface { NewNearestDeviceRepository() repository.NearestDeviceRepo NewDeviceDetailsRepository() repository.DeviceDetailsRepo + NewOLTRepo() repository.OLTRepo } @@ -70,4 +71,8 @@ func (rm *repositoryManager) NewActivityLogRepository() repository.ActivityLogRe func (rm *repositoryManager) NewDeviceInspectionRepository() repository.DeviceInspectionRepo { return repository.NewDeviceInspectionRepo(rm.infra.Conn()) +} + +func (rm *repositoryManager) NewOLTRepo() repository.OLTRepo { + return repository.NewOLTRepo(rm.infra.Conn()) } \ No newline at end of file diff --git a/manager/usecase_manager.go b/manager/usecase_manager.go index 635ed77..2679de9 100644 --- a/manager/usecase_manager.go +++ b/manager/usecase_manager.go @@ -25,6 +25,7 @@ type UsecaseManager interface { NewNearestDeviceUsecase() usecase.NearestDeviceUseCase NewDeviceDetailsUsecase() usecase.DeviceDetailsUseCase + NewOLTUsecase() usecase.OLTUsecase } @@ -106,4 +107,8 @@ func (um *usecaseManager) NewDeviceInspectionUsecase() usecase.DeviceInspectionU um.NewActivityLogUsecase(), service.NewGeocodingService(), // Add the geocoding service ) +} + +func (um *usecaseManager) NewOLTUsecase() usecase.OLTUsecase { + return usecase.NewOLTUsecase(um.repo.NewOLTRepo(), um.repo.NewDeviceDetailsRepository(), service.NewGeocodingService()) } \ No newline at end of file diff --git a/model/dto/req/olt.go b/model/dto/req/olt.go new file mode 100644 index 0000000..6202a83 --- /dev/null +++ b/model/dto/req/olt.go @@ -0,0 +1,19 @@ +package req + +import "github.com/google/uuid" + +type OLTDTO struct { + OLTName string `json:"olt_name" validate:"required,min=3,max=100"` +} + +type UpdateOLTDTO struct { + OLTName *string `json:"olt_name,omitempty" validate:"omitempty,min=3,max=100"` +} + +type AssignDeviceToOLTDTO struct { + DeviceID uuid.UUID `json:"device_id" validate:"required"` +} + +type UnassignDeviceFromOLTDTO struct { + DeviceID uuid.UUID `json:"device_id" validate:"required"` +} \ No newline at end of file diff --git a/model/dto/res/olt.go b/model/dto/res/olt.go new file mode 100644 index 0000000..a821808 --- /dev/null +++ b/model/dto/res/olt.go @@ -0,0 +1,39 @@ +package res + +import ( + "time" + "github.com/google/uuid" +) + +type OLTResponse struct { + ID uuid.UUID `json:"id"` + OLTName string `json:"olt_name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type OLTDetailResponse struct { + ID uuid.UUID `json:"id"` + OLTName string `json:"olt_name"` + DeviceCount int `json:"device_count"` + Devices []DeviceDetailsResponse `json:"devices"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type OLTDeviceResponse struct { + ID uuid.UUID `json:"id"` + DeviceCode string `json:"device_code"` + DeviceType string `json:"device_type"` + Address string `json:"address"` + Province *string `json:"province,omitempty"` + City *string `json:"city,omitempty"` + District *string `json:"district,omitempty"` + Status string `json:"status"` + PortAmount int `json:"port_amount"` + PortUsed int `json:"port_used"` + ImageURL *string `json:"image_url,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + diff --git a/model/entity/devices.go b/model/entity/devices.go index 28a4755..a20a17c 100644 --- a/model/entity/devices.go +++ b/model/entity/devices.go @@ -32,8 +32,11 @@ type Device struct { District *string `json:"district,omitempty" gorm:"type:varchar(255)"` 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 CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + + OLT *OLT `json:"olt,omitempty" gorm:"foreignKey:OLTID;references:ID"` } func (d *Device) GetAllImageURLs() []string { diff --git a/model/entity/olt.go b/model/entity/olt.go new file mode 100644 index 0000000..91a604d --- /dev/null +++ b/model/entity/olt.go @@ -0,0 +1,20 @@ +package entity + +import ( + "time" + "github.com/google/uuid" +) + +type OLT struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` + OLTName string `json:"olt_name" gorm:"unique;not null"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + + // Relationships - One OLT can have many devices + Devices []Device `json:"devices,omitempty" gorm:"foreignKey:OLTID;references:ID"` +} + +func (OLT) TableName() string { + return "olts" +} \ No newline at end of file diff --git a/repository/olt_repo.go b/repository/olt_repo.go new file mode 100644 index 0000000..fdcddc1 --- /dev/null +++ b/repository/olt_repo.go @@ -0,0 +1,127 @@ +package repository + +import ( + "fmt" + "users_management/m/model/entity" + "users_management/m/model/dto/req" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +type OLTRepo interface { + Create(olt entity.OLT) error + GetAll() ([]entity.OLT, error) + GetByID(id uuid.UUID) (entity.OLT, error) + GetByIDWithDevices(id uuid.UUID) (entity.OLT, error) + Update(id uuid.UUID, updateDTO req.UpdateOLTDTO) error + Delete(id uuid.UUID) error + AssignDeviceToOLT(oltID, deviceID uuid.UUID) error + UnassignDeviceFromOLT(deviceID uuid.UUID) error + GetByName(name string) (entity.OLT, error) + GetDevicesByOLTID(oltID uuid.UUID) ([]entity.Device, error) +} + +type oltRepo struct { + db *gorm.DB +} + +func NewOLTRepo(db *gorm.DB) OLTRepo { + return &oltRepo{db: db} +} + +func (r *oltRepo) Create(olt entity.OLT) error { + return r.db.Create(&olt).Error +} + +func (r *oltRepo) GetAll() ([]entity.OLT, error) { + var olts []entity.OLT + err := r.db.Order("created_at DESC").Find(&olts).Error + return olts, err +} + +func (r *oltRepo) GetByID(id uuid.UUID) (entity.OLT, error) { + var olt entity.OLT + err := r.db.Where("id = ?", id).First(&olt).Error + return olt, err +} + +func (r *oltRepo) GetByIDWithDevices(id uuid.UUID) (entity.OLT, error) { + var olt entity.OLT + err := r.db.Preload("Devices"). + Preload("Devices.DevicePort"). + Where("id = ?", id). + First(&olt).Error + return olt, err +} + +func (r *oltRepo) Update(id uuid.UUID, updateDTO req.UpdateOLTDTO) error { + updates := make(map[string]interface{}) + + if updateDTO.OLTName != nil { + updates["olt_name"] = *updateDTO.OLTName + } + + if len(updates) == 0 { + return fmt.Errorf("no fields to update") + } + + return r.db.Model(&entity.OLT{}).Where("id = ?", id).Updates(updates).Error +} + +func (r *oltRepo) Delete(id uuid.UUID) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // First, unassign all devices from this OLT + if err := tx.Model(&entity.Device{}). + Where("olt_id = ?", id). + Update("olt_id", nil).Error; err != nil { + return err + } + + // Then delete the OLT + return tx.Where("id = ?", id).Delete(&entity.OLT{}).Error + }) +} + +func (r *oltRepo) AssignDeviceToOLT(oltID, deviceID uuid.UUID) error { + return r.db.Transaction(func(tx *gorm.DB) error { + // Check if OLT exists + var olt entity.OLT + if err := tx.Where("id = ?", oltID).First(&olt).Error; err != nil { + return fmt.Errorf("OLT not found: %w", err) + } + + // Check if device exists and is ODP type + var device entity.Device + if err := tx.Where("id = ?", deviceID).First(&device).Error; err != nil { + return fmt.Errorf("device not found: %w", err) + } + + if device.DeviceType != entity.ODP { + return fmt.Errorf("only ODP devices can be assigned to OLT") + } + + // Assign device to OLT + return tx.Model(&device).Update("olt_id", oltID).Error + }) +} + +func (r *oltRepo) UnassignDeviceFromOLT(deviceID uuid.UUID) error { + return r.db.Model(&entity.Device{}). + Where("id = ?", deviceID). + Update("olt_id", nil).Error +} + +func (r *oltRepo) GetByName(name string) (entity.OLT, error) { + var olt entity.OLT + err := r.db.Where("olt_name = ?", name).First(&olt).Error + return olt, err +} + +func (r *oltRepo) GetDevicesByOLTID(oltID uuid.UUID) ([]entity.Device, error) { + var devices []entity.Device + err := r.db.Preload("DevicePort"). + Where("olt_id = ? AND device_type = ?", oltID, entity.ODP). + Find(&devices).Error + return devices, err +} \ No newline at end of file diff --git a/usecase/olt_usecase.go b/usecase/olt_usecase.go new file mode 100644 index 0000000..f3bd760 --- /dev/null +++ b/usecase/olt_usecase.go @@ -0,0 +1,234 @@ +package usecase + +import ( + "errors" + "fmt" + "log" + "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 OLTUsecase interface { + CreateOLT(oltDTO req.OLTDTO) error + GetAllOLTs() ([]res.OLTResponse, error) + GetOLTByID(id uuid.UUID) (res.OLTDetailResponse, error) + UpdateOLT(id uuid.UUID, updateDTO req.UpdateOLTDTO) error + DeleteOLT(id uuid.UUID) error + AssignDeviceToOLT(oltID, deviceID uuid.UUID) error + UnassignDeviceFromOLT(deviceID uuid.UUID) error + GetDevicesByOLT(oltID uuid.UUID) ([]res.OLTDeviceResponse, error) +} + +type oltUsecase struct { + oltRepo repository.OLTRepo + deviceRepo repository.DeviceDetailsRepo + validate *validator.Validate + geocoder service.GeocodingService +} + +func NewOLTUsecase(oltRepo repository.OLTRepo, deviceRepo repository.DeviceDetailsRepo, geocoder service.GeocodingService) OLTUsecase { + return &oltUsecase{ + oltRepo: oltRepo, + deviceRepo: deviceRepo, + validate: validator.New(), + geocoder: geocoder, + } +} + +func (u *oltUsecase) CreateOLT(oltDTO req.OLTDTO) error { + if err := u.validate.Struct(oltDTO); err != nil { + return fmt.Errorf("validation error: %w", err) + } + + // Check if OLT name already exists + existingOLT, err := u.oltRepo.GetByName(oltDTO.OLTName) + if err == nil && existingOLT.ID != uuid.Nil { + return errors.New("OLT name already exists") + } + + olt := entity.OLT{ + ID: uuid.New(), + OLTName: oltDTO.OLTName, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + return u.oltRepo.Create(olt) +} + +func (u *oltUsecase) GetAllOLTs() ([]res.OLTResponse, error) { + olts, err := u.oltRepo.GetAll() + if err != nil { + return nil, err + } + + var responses []res.OLTResponse + for _, olt := range olts { + response := res.OLTResponse{ + ID: olt.ID, + OLTName: olt.OLTName, + CreatedAt: olt.CreatedAt, + UpdatedAt: olt.UpdatedAt, + } + responses = append(responses, response) + } + + return responses, nil +} + +func (u *oltUsecase) GetOLTByID(id uuid.UUID) (res.OLTDetailResponse, error) { + // First, check if OLT exists using the basic GetByID method + olt, err := u.oltRepo.GetByID(id) + if err != nil { + log.Printf("OLT not found: %v", err) + return res.OLTDetailResponse{}, errors.New("OLT not found") + } + + log.Printf("OLT found: %+v", olt) + + // Try to get OLT with devices, but fallback to basic info if it fails + oltWithDevices, err := u.oltRepo.GetByIDWithDevices(id) + if err != nil { + log.Printf("Error getting OLT with devices, using basic OLT info: %v", err) + // Use the basic OLT info we already retrieved + oltWithDevices = olt + oltWithDevices.Devices = []entity.Device{} // Ensure devices is empty slice, not nil + } + + log.Printf("OLT retrieved with devices: %+v", oltWithDevices) + + // Convert devices to device details responses + var deviceResponses []res.DeviceDetailsResponse + for _, device := range oltWithDevices.Devices { + log.Printf("Processing device: %s", device.DeviceCode) + + // Get the device details using the existing method which properly loads DevicePort + deviceDetails, err := u.deviceRepo.GetByID(device.ID) + if err != nil { + log.Printf("Error getting device details for %s: %v", device.DeviceCode, err) + continue + } + + deviceResponse, err := helper.ConvertToDeviceDetailsResponse(deviceDetails, u.geocoder) + if err != nil { + log.Printf("Error converting device response for %s: %v", device.DeviceCode, err) + continue + } + deviceResponses = append(deviceResponses, deviceResponse) + } + + // Initialize empty slice if nil to ensure JSON response shows empty array instead of null + if deviceResponses == nil { + deviceResponses = []res.DeviceDetailsResponse{} + } + + response := res.OLTDetailResponse{ + ID: oltWithDevices.ID, + OLTName: oltWithDevices.OLTName, + DeviceCount: len(deviceResponses), + Devices: deviceResponses, + CreatedAt: oltWithDevices.CreatedAt, + UpdatedAt: oltWithDevices.UpdatedAt, + } + + return response, nil +} + +func (u *oltUsecase) UpdateOLT(id uuid.UUID, updateDTO req.UpdateOLTDTO) error { + if err := u.validate.Struct(updateDTO); err != nil { + return fmt.Errorf("validation error: %w", err) + } + + // Check if OLT exists + _, err := u.oltRepo.GetByID(id) + if err != nil { + return errors.New("OLT not found") + } + + // Check if new name already exists (if name is being updated) + if updateDTO.OLTName != nil { + existingOLT, err := u.oltRepo.GetByName(*updateDTO.OLTName) + if err == nil && existingOLT.ID != id { + return errors.New("OLT name already exists") + } + } + + return u.oltRepo.Update(id, updateDTO) +} + +func (u *oltUsecase) DeleteOLT(id uuid.UUID) error { + // Check if OLT exists + _, err := u.oltRepo.GetByID(id) + if err != nil { + return errors.New("OLT not found") + } + + return u.oltRepo.Delete(id) +} + +func (u *oltUsecase) AssignDeviceToOLT(oltID, deviceID uuid.UUID) error { + return u.oltRepo.AssignDeviceToOLT(oltID, deviceID) +} + +func (u *oltUsecase) UnassignDeviceFromOLT(deviceID uuid.UUID) error { + return u.oltRepo.UnassignDeviceFromOLT(deviceID) +} + +func (u *oltUsecase) GetDevicesByOLT(oltID uuid.UUID) ([]res.OLTDeviceResponse, error) { + // Check if OLT exists + _, err := u.oltRepo.GetByID(oltID) + if err != nil { + return nil, errors.New("OLT not found") + } + + devices, err := u.oltRepo.GetDevicesByOLTID(oltID) + if err != nil { + return nil, err + } + + var responses []res.OLTDeviceResponse + for _, device := range devices { + // Get address using geocoder + address := "" + if u.geocoder != nil { + addr, err := u.geocoder.GetAddressFromCoordinates(device.Latitude, device.Longitude) + if err == nil { + address = addr + } + } + + // Get port usage from device port repository + portUsed := 0 + portUsageInfo, _, err := u.deviceRepo.GetPortUsageByDevice(device.ID) + if err == nil { + portUsed = portUsageInfo + } + + response := res.OLTDeviceResponse{ + ID: device.ID, + DeviceCode: device.DeviceCode, + DeviceType: string(device.DeviceType), + Address: address, + Province: device.Province, + City: device.City, + District: device.District, + Status: string(device.Status), + PortAmount: device.PortAmount, + PortUsed: portUsed, + ImageURL: device.ImageURL, + CreatedAt: device.CreatedAt, + UpdatedAt: device.UpdatedAt, + } + responses = append(responses, response) + } + + return responses, nil +} \ No newline at end of file