1131 lines
33 KiB
Go
1131 lines
33 KiB
Go
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
|
|
}
|