package usecase import ( "fmt" "mime/multipart" "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 DeviceUseCase interface { CreateDevice(device req.DeviceDTO) error GetAllDevices() ([]res.DeviceResponse, error) GetByID(id uuid.UUID) (res.DeviceResponse, error) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) error GetByType(deviceType string) ([]res.DeviceTypeResponse, error) BulkUploadImages(devices []req.BulkDeviceImageUploadDTO, 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 BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error ValidateTowerExists(towerID uuid.UUID) (bool, error) ValidateOLTExists(oltID uuid.UUID) (bool, error) // Bulk operations BulkCreateDevices(request req.BulkCreateDeviceDTO) (res.BulkDeviceOperationResponse, error) BulkCreateDevicesWithImages(request req.BulkCreateDeviceDTO, imageFiles []*multipart.FileHeader, imageIndexes []int) (res.BulkDeviceOperationResponse, error) BulkUpdateDevices(request req.BulkUpdateDeviceDTO) (res.BulkDeviceOperationResponse, error) BulkUpdateDevicesWithImages(request req.BulkUpdateDeviceDTO, imageFiles []*multipart.FileHeader, imageIndexes []int, replaceImages bool) (res.BulkDeviceOperationResponse, error) BulkDeleteDevices(request req.BulkDeleteDeviceDTO) (res.BulkDeviceOperationResponse, error) } type deviceUseCase struct { deviceRepo repository.DevicesRepo oltRepo repository.OLTRepo validate *validator.Validate geocoder service.GeocodingService } func NewDeviceUseCase(deviceRepo repository.DevicesRepo, oltRepo repository.OLTRepo, geocoder service.GeocodingService) DeviceUseCase { return &deviceUseCase{ deviceRepo: deviceRepo, oltRepo: oltRepo, geocoder: geocoder, validate: validator.New(), } } func (u *deviceUseCase) ValidateOLTExists(oltID uuid.UUID) (bool, error) { _, err := u.oltRepo.GetByID(oltID) if err != nil { return false, nil } return true, nil } 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 { return fmt.Errorf("validation error: %w", err) } if device.DeviceType == "OTB" || device.DeviceType == "ODP" { if device.PortAmount <= 0 { return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") } } 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()) } } if device.OLTID != nil { // Only ODP devices can be assigned to OLT if device.DeviceType != "ODP" { return fmt.Errorf("only ODP devices can be assigned to OLT") } oltExists, err := u.ValidateOLTExists(*device.OLTID) if err != nil { return fmt.Errorf("failed to validate OLT: %w", err) } if !oltExists { return fmt.Errorf("OLT with ID %s not found", device.OLTID.String()) } } var imageURLs []string var primaryImageURL string if len(imageFiles) > 0 { // Save all images imageURLs, err = helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return err } // First image becomes primary if len(imageURLs) > 0 && imageURLs[0] != "" { primaryImageURL = imageURLs[0] } } newDevice := entity.Device{ ID: uuid.New(), DeviceCode: device.DeviceCode, DeviceType: entity.DeviceType(device.DeviceType), Longitude: device.Longitude, Latitude: device.Latitude, PortAmount: device.PortAmount, Status: entity.DeviceStatus(device.Status), Province: device.Province, City: device.City, District: device.District, TowerID: device.TowerID, // Add TowerID field OLTID: device.OLTID, ImageURL: &primaryImageURL, // Primary image as pointer ImageURLs: entity.StringSlice(imageURLs), // All images CreatedAt: time.Now(), UpdatedAt: time.Now(), } return u.deviceRepo.Post(newDevice) } func (u *deviceUseCase) UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error { err := u.validate.Struct(device) if err != nil { return fmt.Errorf("validation error: %w", err) } updates := map[string]interface{}{} if device.DeviceCode != nil { updates["DeviceCode"] = *device.DeviceCode } if device.DeviceType != nil { updates["DeviceType"] = *device.DeviceType } if device.Longitude != nil { updates["Longitude"] = *device.Longitude } if device.Latitude != nil { updates["Latitude"] = *device.Latitude } if device.PortAmount != nil { updates["PortAmount"] = *device.PortAmount } if device.Status != nil { updates["Status"] = *device.Status } if device.Province != nil { updates["Province"] = *device.Province } if device.City != nil { updates["City"] = *device.City } 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 } // Handle OLTID validation and update if device.OLTID != nil { // Get current device to check type currentDevice, err := u.deviceRepo.GetByID(id) if err != nil { return fmt.Errorf("device not found: %w", err) } // Only ODP devices can be assigned to OLT deviceType := string(currentDevice.DeviceType) if device.DeviceType != nil { deviceType = *device.DeviceType } if deviceType != "ODP" { return fmt.Errorf("only ODP devices can be assigned to OLT") } oltExists, err := u.ValidateOLTExists(*device.OLTID) if err != nil { return fmt.Errorf("failed to validate OLT: %w", err) } if !oltExists { return fmt.Errorf("OLT with ID %s not found", device.OLTID.String()) } updates["OLTID"] = *device.OLTID } // Handle multiple image uploads if len(imageFiles) > 0 { // Get current device to handle existing images currentDevice, err := u.deviceRepo.GetByID(id) if err != nil { return err } // Save new images newImageURLs, err := helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return err } var finalImageURLs []string shouldReplace := len(replaceImages) > 0 && replaceImages[0] if shouldReplace { // Replace all images - delete old ones for _, oldImageURL := range currentDevice.GetAllImageURLs() { helper.DeleteDeviceImage(oldImageURL) } finalImageURLs = newImageURLs } else { // Append to existing images existingImages := currentDevice.GetAllImageURLs() finalImageURLs = append(existingImages, newImageURLs...) } // Update primary image (first image in the final list) if len(finalImageURLs) > 0 && finalImageURLs[0] != "" { updates["ImageURL"] = finalImageURLs[0] } // Update all images updates["ImageURLs"] = entity.StringSlice(finalImageURLs) } 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") } if len(updates) == 0 { return fmt.Errorf("no update data") } updates["UpdatedAt"] = time.Now() return u.deviceRepo.Update(id, updates) } func (u *deviceUseCase) BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error { if len(devices) == 0 { return fmt.Errorf("no devices provided") } if len(fileDistribution) != len(devices) { return fmt.Errorf("file distribution count must match device count") } // Validate all device IDs exist for i, device := range devices { _, err := u.deviceRepo.GetByID(device.DeviceID) if err != nil { return fmt.Errorf("device %d with ID %s not found", i, device.DeviceID) } } // Save all images imageURLs, err := helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return fmt.Errorf("failed to save images: %w", err) } // Distribute images to devices updates := make(map[uuid.UUID][]string) fileIndex := 0 for i, device := range devices { imageCount := fileDistribution[i] deviceImages := make([]string, 0) for j := 0; j < imageCount && fileIndex < len(imageURLs); j++ { if imageURLs[fileIndex] != "" { deviceImages = append(deviceImages, imageURLs[fileIndex]) } fileIndex++ } if len(deviceImages) > 0 { updates[device.DeviceID] = deviceImages } } // Update database return u.deviceRepo.BulkUpdateImagesMultiple(updates) } // Keep old methods for backward compatibility func (u *deviceUseCase) CreateDevice(device req.DeviceDTO) error { return u.CreateDeviceWithMultipleImages(device, nil) } func (u *deviceUseCase) GetAllDevices() ([]res.DeviceResponse, error) { devices, err := u.deviceRepo.GetAll() if err != nil { return []res.DeviceResponse{}, err } devicesResponse, err := helper.ConvertToDeviceResponse(devices, u.geocoder) if err != nil { return []res.DeviceResponse{}, err } return devicesResponse, nil } func (u *deviceUseCase) BulkUploadImages(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader) error { // Default to one image per device fileDistribution := make([]int, len(devices)) for i := range fileDistribution { fileDistribution[i] = 1 } return u.BulkUploadImagesMultiple(devices, imageFiles, fileDistribution) } func (u *deviceUseCase) GetByID(id uuid.UUID) (res.DeviceResponse, error) { device, err := u.deviceRepo.GetByID(id) if err != nil { return res.DeviceResponse{}, err } deviceResp, err := helper.ConvertToDeviceResponseId(device, u.geocoder) if err != nil { return res.DeviceResponse{}, err } return deviceResp, nil } func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) error { err := u.validate.Struct(device) if err != nil { return fmt.Errorf("validation error: %w", err) } updates := map[string]interface{}{} if device.DeviceCode != nil { updates["DeviceCode"] = *device.DeviceCode } if device.DeviceType != nil { updates["DeviceType"] = *device.DeviceType } if device.Longitude != nil { updates["Longitude"] = *device.Longitude } if device.Latitude != nil { updates["Latitude"] = *device.Latitude } if device.PortAmount != nil { updates["PortAmount"] = *device.PortAmount } if device.Status != nil { updates["Status"] = *device.Status } if device.Province != nil { updates["Province"] = *device.Province } if device.City != nil { updates["City"] = *device.City } 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 } // Handle OLTID validation and update if device.OLTID != nil { // Get current device to check type currentDevice, err := u.deviceRepo.GetByID(id) if err != nil { return fmt.Errorf("device not found: %w", err) } // Only ODP devices can be assigned to OLT deviceType := string(currentDevice.DeviceType) if device.DeviceType != nil { deviceType = *device.DeviceType } if deviceType != "ODP" { return fmt.Errorf("only ODP devices can be assigned to OLT") } oltExists, err := u.ValidateOLTExists(*device.OLTID) if err != nil { return fmt.Errorf("failed to validate OLT: %w", err) } if !oltExists { return fmt.Errorf("OLT with ID %s not found", device.OLTID.String()) } updates["OLTID"] = *device.OLTID } 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") } if len(updates) == 0 { return fmt.Errorf("no update data") } updates["UpdatedAt"] = time.Now() return u.deviceRepo.Update(id, updates) } func (u *deviceUseCase) GetByType(deviceType string) ([]res.DeviceTypeResponse, error) { devices, err := u.deviceRepo.GetByType(deviceType) if err != nil { return nil, err } deviceTypeResponses := helper.ConvertToDeviceTypeResponse(devices) return deviceTypeResponses, nil } // BulkCreateDevices creates multiple devices at once func (u *deviceUseCase) BulkCreateDevices(request req.BulkCreateDeviceDTO) (res.BulkDeviceOperationResponse, error) { startTime := time.Now() err := u.validate.Struct(request) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("validation error: %w", err) } var devices []entity.Device var errors []res.BulkDeviceError // Validate each device for i, deviceReq := range request.Devices { if err := u.validate.Struct(deviceReq); err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Validation failed", Details: err.Error(), }) continue } // Validate port amount for OTB/ODP devices if deviceReq.DeviceType == "OTB" || deviceReq.DeviceType == "ODP" { if deviceReq.PortAmount <= 0 { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Invalid port amount", Details: "port amount must be greater than 0 for OTB or ODP devices", }) continue } } // Validate tower if provided if deviceReq.TowerID != nil { towerExists, err := u.ValidateTowerExists(*deviceReq.TowerID) if err != nil || !towerExists { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Tower not found", Details: fmt.Sprintf("tower with ID %s not found", deviceReq.TowerID.String()), }) continue } } // Validate OLT if provided if deviceReq.OLTID != nil { if deviceReq.DeviceType != "ODP" { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Invalid OLT assignment", Details: "only ODP devices can be assigned to OLT", }) continue } oltExists, err := u.ValidateOLTExists(*deviceReq.OLTID) if err != nil || !oltExists { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "OLT not found", Details: fmt.Sprintf("OLT with ID %s not found", deviceReq.OLTID.String()), }) continue } } device := entity.Device{ ID: uuid.New(), DeviceCode: deviceReq.DeviceCode, DeviceType: entity.DeviceType(deviceReq.DeviceType), Longitude: deviceReq.Longitude, Latitude: deviceReq.Latitude, PortAmount: deviceReq.PortAmount, Status: entity.DeviceStatus(deviceReq.Status), Province: deviceReq.Province, City: deviceReq.City, District: deviceReq.District, TowerID: deviceReq.TowerID, OLTID: deviceReq.OLTID, } devices = append(devices, device) } // Bulk insert valid devices var createdDevices []entity.Device if len(devices) > 0 { createdDevices, _ = u.deviceRepo.BulkCreate(devices) } // Fetch created devices var responses []res.DeviceResponse for _, dev := range createdDevices { fetchedDevice, err := u.deviceRepo.GetByID(dev.ID) if err == nil { // Build address from location fields address := "" if fetchedDevice.City != nil && fetchedDevice.Province != nil { address = *fetchedDevice.City + ", " + *fetchedDevice.Province } else if fetchedDevice.City != nil { address = *fetchedDevice.City } else if fetchedDevice.Province != nil { address = *fetchedDevice.Province } responses = append(responses, res.DeviceResponse{ ID: fetchedDevice.ID, DeviceCode: fetchedDevice.DeviceCode, DeviceType: string(fetchedDevice.DeviceType), Longitude: fetchedDevice.Longitude, Latitude: fetchedDevice.Latitude, PortAmount: fetchedDevice.PortAmount, Status: string(fetchedDevice.Status), Address: address, Region: nil, Province: fetchedDevice.Province, City: fetchedDevice.City, District: fetchedDevice.District, ImageURL: fetchedDevice.ImageURL, ImageURLs: fetchedDevice.ImageURLs, CreatedAt: fetchedDevice.CreatedAt, UpdatedAt: fetchedDevice.UpdatedAt, }) } } executionTime := time.Since(startTime).String() return res.BulkDeviceOperationResponse{ TotalRequested: len(request.Devices), Successful: len(createdDevices), Failed: len(errors), Errors: errors, Results: responses, ExecutionTime: executionTime, }, nil } // BulkUpdateDevices updates multiple devices with the same values func (u *deviceUseCase) BulkUpdateDevices(request req.BulkUpdateDeviceDTO) (res.BulkDeviceOperationResponse, error) { startTime := time.Now() err := u.validate.Struct(request) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("validation error: %w", err) } // Validate that devices exist var validIDs []uuid.UUID var errors []res.BulkDeviceError for i, id := range request.DeviceIDs { _, err := u.deviceRepo.GetByID(id) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Device not found", Details: id.String(), }) continue } validIDs = append(validIDs, id) } // Build update map updates := make(map[string]interface{}) if request.Updates.DeviceCode != nil { updates["device_code"] = *request.Updates.DeviceCode } if request.Updates.DeviceType != nil { updates["device_type"] = *request.Updates.DeviceType } if request.Updates.Longitude != nil { updates["longitude"] = *request.Updates.Longitude } if request.Updates.Latitude != nil { updates["latitude"] = *request.Updates.Latitude } if request.Updates.PortAmount != nil { updates["port_amount"] = *request.Updates.PortAmount } if request.Updates.Status != nil { updates["status"] = *request.Updates.Status } if request.Updates.Province != nil { updates["province"] = *request.Updates.Province } if request.Updates.City != nil { updates["city"] = *request.Updates.City } if request.Updates.District != nil { updates["district"] = *request.Updates.District } if request.Updates.TowerID != nil { updates["tower_id"] = *request.Updates.TowerID } if request.Updates.OLTID != nil { updates["olt_id"] = *request.Updates.OLTID } // Perform bulk update var rowsAffected int64 if len(validIDs) > 0 && len(updates) > 0 { rowsAffected, err = u.deviceRepo.BulkUpdate(validIDs, updates) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("bulk update failed: %w", err) } } // Fetch updated devices var responses []res.DeviceResponse for _, id := range validIDs { device, err := u.deviceRepo.GetByID(id) if err == nil { // Build address from location fields address := "" if device.City != nil && device.Province != nil { address = *device.City + ", " + *device.Province } else if device.City != nil { address = *device.City } else if device.Province != nil { address = *device.Province } responses = append(responses, res.DeviceResponse{ ID: device.ID, DeviceCode: device.DeviceCode, DeviceType: string(device.DeviceType), Longitude: device.Longitude, Latitude: device.Latitude, PortAmount: device.PortAmount, Status: string(device.Status), Address: address, Region: nil, Province: device.Province, City: device.City, District: device.District, ImageURL: device.ImageURL, ImageURLs: device.ImageURLs, CreatedAt: device.CreatedAt, UpdatedAt: device.UpdatedAt, }) } } executionTime := time.Since(startTime).String() return res.BulkDeviceOperationResponse{ TotalRequested: len(request.DeviceIDs), Successful: int(rowsAffected), Failed: len(errors), Errors: errors, Results: responses, ExecutionTime: executionTime, }, nil } // BulkDeleteDevices deletes multiple devices func (u *deviceUseCase) BulkDeleteDevices(request req.BulkDeleteDeviceDTO) (res.BulkDeviceOperationResponse, error) { startTime := time.Now() err := u.validate.Struct(request) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("validation error: %w", err) } // Validate that devices exist var validIDs []uuid.UUID var errors []res.BulkDeviceError for i, id := range request.DeviceIDs { _, err := u.deviceRepo.GetByID(id) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Device not found", Details: id.String(), }) continue } validIDs = append(validIDs, id) } // Perform bulk delete var rowsAffected int64 if len(validIDs) > 0 { rowsAffected, err = u.deviceRepo.BulkDelete(validIDs) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("bulk delete failed: %w", err) } } executionTime := time.Since(startTime).String() return res.BulkDeviceOperationResponse{ TotalRequested: len(request.DeviceIDs), Successful: int(rowsAffected), Failed: len(errors), Errors: errors, ExecutionTime: executionTime, }, nil } // BulkCreateDevicesWithImages creates multiple devices with their images func (u *deviceUseCase) BulkCreateDevicesWithImages(request req.BulkCreateDeviceDTO, imageFiles []*multipart.FileHeader, imageIndexes []int) (res.BulkDeviceOperationResponse, error) { startTime := time.Now() err := u.validate.Struct(request) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("validation error: %w", err) } // Validate image indexes if len(imageIndexes) != len(request.Devices) { return res.BulkDeviceOperationResponse{}, fmt.Errorf("image_indexes length (%d) must match devices length (%d)", len(imageIndexes), len(request.Devices)) } // Calculate total expected images totalExpectedImages := 0 for _, count := range imageIndexes { totalExpectedImages += count } if totalExpectedImages != len(imageFiles) { return res.BulkDeviceOperationResponse{}, fmt.Errorf("total images (%d) doesn't match sum of image_indexes (%d)", len(imageFiles), totalExpectedImages) } var createdDevices []entity.Device var errors []res.BulkDeviceError var responses []res.DeviceResponse // Process each device with its images imageOffset := 0 for i, deviceReq := range request.Devices { // Validate device type if deviceReq.DeviceType != "ODP" && deviceReq.DeviceType != "OTB" && deviceReq.DeviceType != "closure" { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Invalid device type", Details: fmt.Sprintf("Type must be ODP, OTB, or closure, got: %s", deviceReq.DeviceType), }) continue } // Validate port amount for OTB and ODP if (deviceReq.DeviceType == "OTB" || deviceReq.DeviceType == "ODP") && deviceReq.PortAmount <= 0 { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Invalid port amount", Details: fmt.Sprintf("Port amount must be > 0 for %s devices", deviceReq.DeviceType), }) continue } // Validate tower if provided if deviceReq.TowerID != nil { towerExists, err := u.ValidateTowerExists(*deviceReq.TowerID) if err != nil || !towerExists { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Tower not found", Details: deviceReq.TowerID.String(), }) continue } } // Validate OLT if provided if deviceReq.OLTID != nil { if deviceReq.DeviceType != "ODP" { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Invalid OLT assignment", Details: "Only ODP devices can be assigned to OLT", }) continue } oltExists, err := u.ValidateOLTExists(*deviceReq.OLTID) if err != nil || !oltExists { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "OLT not found", Details: deviceReq.OLTID.String(), }) continue } } // Get images for this device deviceImageCount := imageIndexes[i] var deviceImages []*multipart.FileHeader if deviceImageCount > 0 { deviceImages = imageFiles[imageOffset : imageOffset+deviceImageCount] imageOffset += deviceImageCount } // Save images var imageURLs []string var primaryImageURL string if len(deviceImages) > 0 { imageURLs, err = helper.SaveDeviceImagesBulk(deviceImages) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Failed to save images", Details: err.Error(), }) continue } if len(imageURLs) > 0 && imageURLs[0] != "" { primaryImageURL = imageURLs[0] } } // Create device device := entity.Device{ ID: uuid.New(), DeviceCode: deviceReq.DeviceCode, DeviceType: entity.DeviceType(deviceReq.DeviceType), Longitude: deviceReq.Longitude, Latitude: deviceReq.Latitude, PortAmount: deviceReq.PortAmount, Status: entity.DeviceStatus(deviceReq.Status), Province: deviceReq.Province, City: deviceReq.City, District: deviceReq.District, TowerID: deviceReq.TowerID, OLTID: deviceReq.OLTID, ImageURL: &primaryImageURL, ImageURLs: entity.StringSlice(imageURLs), CreatedAt: time.Now(), UpdatedAt: time.Now(), } createdDevices = append(createdDevices, device) } // Bulk insert valid devices if len(createdDevices) > 0 { successDevices, bulkErrors := u.deviceRepo.BulkCreate(createdDevices) if len(bulkErrors) > 0 { // Add repository errors to our error list for idx, bulkErr := range bulkErrors { if bulkErr != nil { errors = append(errors, res.BulkDeviceError{ Index: idx, Error: "Failed to create device", Details: bulkErr.Error(), }) } } } // Build responses from successfully created devices for _, device := range successDevices { fetchedDevice, err := u.deviceRepo.GetByID(device.ID) if err != nil { continue } address := "" if fetchedDevice.City != nil && fetchedDevice.Province != nil { address = *fetchedDevice.City + ", " + *fetchedDevice.Province } else if fetchedDevice.City != nil { address = *fetchedDevice.City } else if fetchedDevice.Province != nil { address = *fetchedDevice.Province } responses = append(responses, res.DeviceResponse{ ID: fetchedDevice.ID, DeviceCode: fetchedDevice.DeviceCode, DeviceType: string(fetchedDevice.DeviceType), Longitude: fetchedDevice.Longitude, Latitude: fetchedDevice.Latitude, PortAmount: fetchedDevice.PortAmount, Status: string(fetchedDevice.Status), Address: address, Region: nil, Province: fetchedDevice.Province, City: fetchedDevice.City, District: fetchedDevice.District, ImageURL: fetchedDevice.ImageURL, ImageURLs: fetchedDevice.ImageURLs, CreatedAt: fetchedDevice.CreatedAt, UpdatedAt: fetchedDevice.UpdatedAt, }) } } executionTime := time.Since(startTime).String() return res.BulkDeviceOperationResponse{ TotalRequested: len(request.Devices), Successful: len(createdDevices), Failed: len(errors), Errors: errors, Results: responses, ExecutionTime: executionTime, }, nil } // BulkUpdateDevicesWithImages updates multiple devices with images func (u *deviceUseCase) BulkUpdateDevicesWithImages(request req.BulkUpdateDeviceDTO, imageFiles []*multipart.FileHeader, imageIndexes []int, replaceImages bool) (res.BulkDeviceOperationResponse, error) { startTime := time.Now() err := u.validate.Struct(request) if err != nil { return res.BulkDeviceOperationResponse{}, fmt.Errorf("validation error: %w", err) } // Validate image indexes if len(imageIndexes) != len(request.DeviceIDs) { return res.BulkDeviceOperationResponse{}, fmt.Errorf("image_indexes length (%d) must match device_ids length (%d)", len(imageIndexes), len(request.DeviceIDs)) } // Calculate total expected images totalExpectedImages := 0 for _, count := range imageIndexes { totalExpectedImages += count } if totalExpectedImages != len(imageFiles) { return res.BulkDeviceOperationResponse{}, fmt.Errorf("total images (%d) doesn't match sum of image_indexes (%d)", len(imageFiles), totalExpectedImages) } var updatedCount int var errors []res.BulkDeviceError var responses []res.DeviceResponse // Process each device with its images imageOffset := 0 for i, deviceID := range request.DeviceIDs { // Check if device exists existingDevice, err := u.deviceRepo.GetByID(deviceID) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Device not found", Details: deviceID.String(), }) continue } // Get images for this device deviceImageCount := imageIndexes[i] var deviceImages []*multipart.FileHeader if deviceImageCount > 0 { deviceImages = imageFiles[imageOffset : imageOffset+deviceImageCount] imageOffset += deviceImageCount } // Build update map updates := make(map[string]interface{}) if request.Updates.DeviceCode != nil { updates["device_code"] = *request.Updates.DeviceCode } if request.Updates.DeviceType != nil { updates["device_type"] = *request.Updates.DeviceType } if request.Updates.Longitude != nil { updates["longitude"] = *request.Updates.Longitude } if request.Updates.Latitude != nil { updates["latitude"] = *request.Updates.Latitude } if request.Updates.PortAmount != nil { updates["port_amount"] = *request.Updates.PortAmount } if request.Updates.Status != nil { updates["status"] = *request.Updates.Status } if request.Updates.Province != nil { updates["province"] = *request.Updates.Province } if request.Updates.City != nil { updates["city"] = *request.Updates.City } if request.Updates.District != nil { updates["district"] = *request.Updates.District } if request.Updates.TowerID != nil { updates["tower_id"] = *request.Updates.TowerID } if request.Updates.OLTID != nil { updates["olt_id"] = *request.Updates.OLTID } // Handle images if len(deviceImages) > 0 { imageURLs, err := helper.SaveDeviceImagesBulk(deviceImages) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Failed to save images", Details: err.Error(), }) continue } if replaceImages { // Replace all images if len(imageURLs) > 0 { updates["image_url"] = imageURLs[0] updates["image_urls"] = entity.StringSlice(imageURLs) } } else { // Append to existing images existingImageURLs := existingDevice.GetAllImageURLs() allImageURLs := append(existingImageURLs, imageURLs...) if len(allImageURLs) > 0 { updates["image_url"] = allImageURLs[0] updates["image_urls"] = entity.StringSlice(allImageURLs) } } } // Update device if len(updates) > 0 { _, err = u.deviceRepo.BulkUpdate([]uuid.UUID{deviceID}, updates) if err != nil { errors = append(errors, res.BulkDeviceError{ Index: i, Error: "Failed to update device", Details: err.Error(), }) continue } updatedCount++ } // Fetch updated device updatedDevice, err := u.deviceRepo.GetByID(deviceID) if err == nil { address := "" if updatedDevice.City != nil && updatedDevice.Province != nil { address = *updatedDevice.City + ", " + *updatedDevice.Province } else if updatedDevice.City != nil { address = *updatedDevice.City } else if updatedDevice.Province != nil { address = *updatedDevice.Province } responses = append(responses, res.DeviceResponse{ ID: updatedDevice.ID, DeviceCode: updatedDevice.DeviceCode, DeviceType: string(updatedDevice.DeviceType), Longitude: updatedDevice.Longitude, Latitude: updatedDevice.Latitude, PortAmount: updatedDevice.PortAmount, Status: string(updatedDevice.Status), Address: address, Region: nil, Province: updatedDevice.Province, City: updatedDevice.City, District: updatedDevice.District, ImageURL: updatedDevice.ImageURL, ImageURLs: updatedDevice.ImageURLs, CreatedAt: updatedDevice.CreatedAt, UpdatedAt: updatedDevice.UpdatedAt, }) } } executionTime := time.Since(startTime).String() return res.BulkDeviceOperationResponse{ TotalRequested: len(request.DeviceIDs), Successful: updatedCount, Failed: len(errors), Errors: errors, Results: responses, ExecutionTime: executionTime, }, nil }