Merge pull request 'adding without connection and without towers' (#22) from feature/responses-v2 into dev
Reviewed-on: winter-access/backend_nam#22
This commit is contained in:
commit
7bf13d6e1a
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"users_management/m/config"
|
"users_management/m/config"
|
||||||
"users_management/m/middleware"
|
"users_management/m/middleware"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
|
"users_management/m/model/dto/res"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
"users_management/m/utils/common"
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
|
@ -53,10 +54,108 @@ func (c *DeviceDetailsController) Route() {
|
||||||
|
|
||||||
// delete images by filename
|
// delete images by filename
|
||||||
deviceDetails.DELETE("/:id/images/:filename", c.deleteDeviceImage)
|
deviceDetails.DELETE("/:id/images/:filename", c.deleteDeviceImage)
|
||||||
|
deviceDetails.GET("/without-towers", c.getDevicesWithoutTowers)
|
||||||
|
|
||||||
|
deviceDetails.GET("/without-connections", c.getDevicesWithoutConnections)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (c *DeviceDetailsController) getDevicesWithoutConnections(ctx *gin.Context) {
|
||||||
|
// Get device types from query parameters (optional)
|
||||||
|
deviceTypesParam := ctx.Query("device_types")
|
||||||
|
var deviceTypes []string
|
||||||
|
|
||||||
|
if deviceTypesParam != "" {
|
||||||
|
// Split by comma if multiple types provided
|
||||||
|
deviceTypes = strings.Split(deviceTypesParam, ",")
|
||||||
|
// Trim whitespace and normalize case
|
||||||
|
for i, dt := range deviceTypes {
|
||||||
|
trimmed := strings.TrimSpace(dt)
|
||||||
|
if strings.ToUpper(trimmed) == "OTB" {
|
||||||
|
deviceTypes[i] = "OTB"
|
||||||
|
} else if strings.ToLower(trimmed) == "closure" {
|
||||||
|
deviceTypes[i] = "closure"
|
||||||
|
} else {
|
||||||
|
deviceTypes[i] = trimmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices, err := c.deviceDetailsUC.GetDevicesWithoutConnections(deviceTypes)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categorize devices by type for better response structure
|
||||||
|
closureDevices := make([]res.DeviceDetailsResponse, 0)
|
||||||
|
otbDevices := make([]res.DeviceDetailsResponse, 0)
|
||||||
|
|
||||||
|
for _, device := range devices {
|
||||||
|
if device.DeviceType == "closure" {
|
||||||
|
closureDevices = append(closureDevices, device)
|
||||||
|
} else if device.DeviceType == "OTB" {
|
||||||
|
otbDevices = append(otbDevices, device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"devices": devices,
|
||||||
|
"total": len(devices),
|
||||||
|
"breakdown": gin.H{
|
||||||
|
"closure": gin.H{
|
||||||
|
"count": len(closureDevices),
|
||||||
|
"devices": closureDevices,
|
||||||
|
},
|
||||||
|
"otb": gin.H{
|
||||||
|
"count": len(otbDevices),
|
||||||
|
"devices": otbDevices,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"filter": gin.H{
|
||||||
|
"device_types": deviceTypes,
|
||||||
|
"criteria": "without_fishbones_and_backbones",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Devices without connections retrieved successfully", response)
|
||||||
|
}
|
||||||
|
func (c *DeviceDetailsController) getDevicesWithoutTowers(ctx *gin.Context) {
|
||||||
|
// Get device types from query parameters (optional)
|
||||||
|
deviceTypesParam := ctx.Query("device_types")
|
||||||
|
var deviceTypes []string
|
||||||
|
|
||||||
|
if deviceTypesParam != "" {
|
||||||
|
// Split by comma if multiple types provided
|
||||||
|
deviceTypes = strings.Split(deviceTypesParam, ",")
|
||||||
|
// Trim whitespace
|
||||||
|
for i, dt := range deviceTypes {
|
||||||
|
deviceTypes[i] = strings.TrimSpace(strings.ToUpper(dt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices, err := c.deviceDetailsUC.GetDevicesWithoutTowers(deviceTypes)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"devices": devices,
|
||||||
|
"total": len(devices),
|
||||||
|
"filter": gin.H{
|
||||||
|
"device_types": deviceTypes,
|
||||||
|
"without_towers": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Devices without towers retrieved successfully", response)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *DeviceDetailsController) deleteDeviceImage(ctx *gin.Context) {
|
func (c *DeviceDetailsController) deleteDeviceImage(ctx *gin.Context) {
|
||||||
// Parse device ID
|
// Parse device ID
|
||||||
id := ctx.Param("id")
|
id := ctx.Param("id")
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ type BackboneResponse struct {
|
||||||
BackboneCode string `json:"backbone_code"`
|
BackboneCode string `json:"backbone_code"`
|
||||||
DevStart string `json:"dev_start"`
|
DevStart string `json:"dev_start"`
|
||||||
DevEnd string `json:"dev_end"`
|
DevEnd string `json:"dev_end"`
|
||||||
|
DeviceStartID uuid.UUID `json:"device_start_id"`
|
||||||
|
DeviceEndID uuid.UUID `json:"device_end_id"`
|
||||||
CoreAmount int `json:"core_amount"`
|
CoreAmount int `json:"core_amount"`
|
||||||
TotalFishbone int `json:"total_fishbone"`
|
TotalFishbone int `json:"total_fishbone"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ type DeviceDetailsRepo interface {
|
||||||
UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error
|
UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error
|
||||||
BulkUpdateCustomersByPort(deviceID uuid.UUID, updates []req.UpdateCustomerByPortDTO) error
|
BulkUpdateCustomersByPort(deviceID uuid.UUID, updates []req.UpdateCustomerByPortDTO) error
|
||||||
RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error
|
RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error
|
||||||
|
GetDevicesWithoutTowers(deviceTypes []string) ([]entity.DeviceDetails, error)
|
||||||
|
GetDevicesWithoutConnections(deviceTypes []string) ([]entity.DeviceDetails, error)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +53,34 @@ func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *deviceDetailsRepo) GetDevicesWithoutConnections(deviceTypes []string) ([]entity.DeviceDetails, error) {
|
||||||
|
var devices []entity.DeviceDetails
|
||||||
|
|
||||||
|
query := r.db.Preload("DevicePort")
|
||||||
|
|
||||||
|
// Filter by device types
|
||||||
|
if len(deviceTypes) > 0 {
|
||||||
|
query = query.Where("device_type IN ?", deviceTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For closure devices: exclude those that have fishbones (as start device)
|
||||||
|
// For OTB devices: exclude those that have backbones (as start or end device) or fishbones (as end device)
|
||||||
|
subQueryBackbone := r.db.Table("backbone").
|
||||||
|
Select("DISTINCT CASE WHEN dev_start_id IS NOT NULL THEN dev_start_id ELSE dev_end_id END as device_id").
|
||||||
|
Where("dev_start_id IS NOT NULL OR dev_end_id IS NOT NULL")
|
||||||
|
|
||||||
|
subQueryFishbone := r.db.Table("fishbone").
|
||||||
|
Select("DISTINCT CASE WHEN dev_start_id IS NOT NULL THEN dev_start_id ELSE dev_end_id END as device_id").
|
||||||
|
Where("dev_start_id IS NOT NULL OR dev_end_id IS NOT NULL")
|
||||||
|
|
||||||
|
// Combine both subqueries to exclude devices that have any connections
|
||||||
|
query = query.Where("id NOT IN (?)",
|
||||||
|
r.db.Raw("(?) UNION (?)", subQueryBackbone, subQueryFishbone))
|
||||||
|
|
||||||
|
err := query.Find(&devices).Error
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *deviceDetailsRepo) UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error {
|
func (r *deviceDetailsRepo) UpdateCustomerByPort(deviceID uuid.UUID, update req.UpdateCustomerByPortDTO) error {
|
||||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
// Lock both device and device_port records
|
// Lock both device and device_port records
|
||||||
|
|
@ -426,6 +456,21 @@ func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *deviceDetailsRepo) GetDevicesWithoutTowers(deviceTypes []string) ([]entity.DeviceDetails, error) {
|
||||||
|
var devices []entity.DeviceDetails
|
||||||
|
|
||||||
|
query := r.db.
|
||||||
|
Preload("DevicePort").
|
||||||
|
Where("device_type IN ?", deviceTypes).
|
||||||
|
Where("id NOT IN (?)",
|
||||||
|
r.db.Table("towers").
|
||||||
|
Select("dev_id").
|
||||||
|
Where("dev_id IS NOT NULL"))
|
||||||
|
|
||||||
|
err := query.Find(&devices).Error
|
||||||
|
return devices, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.UUID, newPortAmount int) error {
|
func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.UUID, newPortAmount int) error {
|
||||||
// Get current port usage
|
// Get current port usage
|
||||||
var devicePort entity.DevicePort
|
var devicePort entity.DevicePort
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ type DeviceDetailsUseCase interface {
|
||||||
RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error
|
RemoveCustomerByPort(deviceID uuid.UUID, portNumber int) error
|
||||||
UpdateDeviceDetailsWithMultipleImages(id uuid.UUID, deviceDTO req.UpdateDeviceDetailsDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error
|
UpdateDeviceDetailsWithMultipleImages(id uuid.UUID, deviceDTO req.UpdateDeviceDetailsDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error
|
||||||
DeleteDeviceImage(deviceID uuid.UUID, filename string) error
|
DeleteDeviceImage(deviceID uuid.UUID, filename string) error
|
||||||
|
GetDevicesWithoutTowers(deviceTypes []string) ([]res.DeviceDetailsResponse, error)
|
||||||
|
GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type deviceDetailsUseCase struct {
|
type deviceDetailsUseCase struct {
|
||||||
|
|
@ -52,6 +54,33 @@ func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, geo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *deviceDetailsUseCase) GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error) {
|
||||||
|
// Validate device types
|
||||||
|
validTypes := map[string]bool{"closure": true, "OTB": true}
|
||||||
|
for _, deviceType := range deviceTypes {
|
||||||
|
if !validTypes[deviceType] {
|
||||||
|
return nil, fmt.Errorf("invalid device type: %s. Only closure and OTB are allowed", deviceType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no device types provided, default to closure and OTB
|
||||||
|
if len(deviceTypes) == 0 {
|
||||||
|
deviceTypes = []string{"closure", "OTB"}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices, err := u.deviceDetailsRepo.GetDevicesWithoutConnections(deviceTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *deviceDetailsUseCase) DeleteDeviceImage(deviceID uuid.UUID, filename string) error {
|
func (u *deviceDetailsUseCase) DeleteDeviceImage(deviceID uuid.UUID, filename string) error {
|
||||||
// Get current device
|
// Get current device
|
||||||
currentDevice, err := u.deviceDetailsRepo.GetByID(deviceID)
|
currentDevice, err := u.deviceDetailsRepo.GetByID(deviceID)
|
||||||
|
|
@ -118,6 +147,33 @@ func (u *deviceDetailsUseCase) DeleteDeviceImage(deviceID uuid.UUID, filename st
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *deviceDetailsUseCase) GetDevicesWithoutTowers(deviceTypes []string) ([]res.DeviceDetailsResponse, error) {
|
||||||
|
// Validate device types
|
||||||
|
validTypes := map[string]bool{"ODP": true, "OTB": true}
|
||||||
|
for _, deviceType := range deviceTypes {
|
||||||
|
if !validTypes[deviceType] {
|
||||||
|
return nil, fmt.Errorf("invalid device type: %s. Only ODP and OTB are allowed", deviceType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no device types provided, default to ODP and OTB
|
||||||
|
if len(deviceTypes) == 0 {
|
||||||
|
deviceTypes = []string{"ODP", "OTB"}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices, err := u.deviceDetailsRepo.GetDevicesWithoutTowers(deviceTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *deviceDetailsUseCase) UpdateDeviceDetailsWithMultipleImages(id uuid.UUID, deviceDTO req.UpdateDeviceDetailsDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error {
|
func (u *deviceDetailsUseCase) UpdateDeviceDetailsWithMultipleImages(id uuid.UUID, deviceDTO req.UpdateDeviceDetailsDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error {
|
||||||
err := u.validate.Struct(deviceDTO)
|
err := u.validate.Struct(deviceDTO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package usecase
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
"users_management/m/model/dto/res"
|
"users_management/m/model/dto/res"
|
||||||
|
|
@ -78,11 +79,11 @@ func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate device types
|
// Validate device types
|
||||||
if startDevice.DeviceType != "CLOSURE" || startDevice.DeviceType != "closure" {
|
if strings.ToLower(string(startDevice.DeviceType)) != "closure" {
|
||||||
return fmt.Errorf("start device must be of type closure, got %s", startDevice.DeviceType)
|
return fmt.Errorf("start device must be of type closure, got %s", startDevice.DeviceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if endDevice.DeviceType != "ODP" {
|
if strings.ToUpper(string(endDevice.DeviceType)) != "ODP" {
|
||||||
return fmt.Errorf("end device must be of type ODP, got %s", endDevice.DeviceType)
|
return fmt.Errorf("end device must be of type ODP, got %s", endDevice.DeviceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ func ConvertToBackboneResponses(backbone []entity.Backbone, totalFishbone map[uu
|
||||||
BackboneCode: backbone.BackboneCode,
|
BackboneCode: backbone.BackboneCode,
|
||||||
DevStart: backbone.DeviceStart.DeviceCode,
|
DevStart: backbone.DeviceStart.DeviceCode,
|
||||||
DevEnd: backbone.DeviceEnd.DeviceCode,
|
DevEnd: backbone.DeviceEnd.DeviceCode,
|
||||||
|
DeviceStartID: backbone.DeviceStart.ID,
|
||||||
|
DeviceEndID: backbone.DeviceEnd.ID,
|
||||||
CoreAmount: backbone.CoreAmount,
|
CoreAmount: backbone.CoreAmount,
|
||||||
TotalFishbone: count,
|
TotalFishbone: count,
|
||||||
CreatedAt: backbone.CreatedAt,
|
CreatedAt: backbone.CreatedAt,
|
||||||
|
|
@ -35,6 +37,8 @@ func ConvertToBackboneRespId(backbone entity.Backbone, fishboneCount int) (res.B
|
||||||
ID: backbone.ID,
|
ID: backbone.ID,
|
||||||
DevStart: backbone.DeviceStart.DeviceCode,
|
DevStart: backbone.DeviceStart.DeviceCode,
|
||||||
DevEnd: backbone.DeviceEnd.DeviceCode,
|
DevEnd: backbone.DeviceEnd.DeviceCode,
|
||||||
|
DeviceStartID: backbone.DeviceStart.ID,
|
||||||
|
DeviceEndID: backbone.DeviceEnd.ID,
|
||||||
CoreAmount: backbone.CoreAmount,
|
CoreAmount: backbone.CoreAmount,
|
||||||
TotalFishbone: fishboneCount,
|
TotalFishbone: fishboneCount,
|
||||||
CreatedAt: backbone.CreatedAt,
|
CreatedAt: backbone.CreatedAt,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue