adding nearest device and some addtion feature more complex

This commit is contained in:
areeqakbr 2025-06-11 22:48:11 +07:00
parent d5f611e390
commit 32c775ab24
36 changed files with 2991 additions and 226 deletions

View File

@ -0,0 +1,137 @@
package controller
import (
"net/http"
"users_management/m/middleware"
"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 DeviceDetailsController struct {
deviceDetailsUC usecase.DeviceDetailsUseCase
rg *gin.RouterGroup
}
func NewDeviceDetailsController(deviceDetailsUC usecase.DeviceDetailsUseCase, rg *gin.RouterGroup) *DeviceDetailsController {
return &DeviceDetailsController{
deviceDetailsUC: deviceDetailsUC,
rg: rg,
}
}
func (c *DeviceDetailsController) Route() {
deviceDetails := c.rg.Group("/device-details")
deviceDetails.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"))
{
deviceDetails.GET("", c.getAllDeviceDetails)
deviceDetails.POST("", c.createDeviceDetails)
deviceDetails.GET("/:id", c.getDeviceDetailsByID)
deviceDetails.PUT("/:id", c.updateDeviceDetails)
deviceDetails.DELETE("/:id", c.deleteDeviceDetails)
deviceDetails.POST("/:id/recalculate-ports", c.recalculatePortUsage)
}
}
func (c *DeviceDetailsController) getAllDeviceDetails(ctx *gin.Context) {
devices, err := c.deviceDetailsUC.GetAllDeviceDetails()
if err != nil {
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
return
}
common.SingleResponses(ctx, "Device details retrieved successfully", devices)
}
func (c *DeviceDetailsController) createDeviceDetails(ctx *gin.Context) {
var request req.DeviceDetailsDTO
if err := ctx.ShouldBindJSON(&request); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
err := c.deviceDetailsUC.CreateDeviceDetails(request)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Device details created successfully", nil)
}
func (c *DeviceDetailsController) getDeviceDetailsByID(ctx *gin.Context) {
id := ctx.Param("id")
deviceID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
return
}
device, err := c.deviceDetailsUC.GetDeviceDetailsByID(deviceID)
if err != nil {
common.ErrorResponses(ctx, http.StatusNotFound, err.Error())
return
}
common.SingleResponses(ctx, "Device details retrieved successfully", device)
}
func (c *DeviceDetailsController) updateDeviceDetails(ctx *gin.Context) {
id := ctx.Param("id")
deviceID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
return
}
var request req.UpdateDeviceDetailsDTO
if err := ctx.ShouldBindJSON(&request); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
err = c.deviceDetailsUC.UpdateDeviceDetails(deviceID, request)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Device details updated successfully", nil)
}
func (c *DeviceDetailsController) deleteDeviceDetails(ctx *gin.Context) {
id := ctx.Param("id")
deviceID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
return
}
err = c.deviceDetailsUC.DeleteDeviceDetails(deviceID)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Device details deleted successfully", nil)
}
func (c *DeviceDetailsController) recalculatePortUsage(ctx *gin.Context) {
id := ctx.Param("id")
deviceID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
return
}
err = c.deviceDetailsUC.RecalculatePortUsage(deviceID)
if err != nil {
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
return
}
common.SingleResponses(ctx, "Port usage recalculated successfully", nil)
}

View File

@ -0,0 +1,255 @@
package controller
import (
"net/http"
"strconv"
"users_management/m/middleware"
"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 DeviceInspectionController struct {
inspectionUC usecase.DeviceInspectionUseCase
rg *gin.RouterGroup
}
func NewDeviceInspectionController(inspectionUC usecase.DeviceInspectionUseCase, rg *gin.RouterGroup) *DeviceInspectionController {
return &DeviceInspectionController{
inspectionUC: inspectionUC,
rg: rg,
}
}
func (c *DeviceInspectionController) Route() {
inspections := c.rg.Group("/device-inspections")
{
// Teknisi can create and read their own inspections
inspections.POST("", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), c.createInspection)
inspections.GET("/my-inspections", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), c.getMyInspections)
// Admin and Superadmin can see all inspections
inspections.GET("", middleware.RequireAdminRole(), c.getAllInspections)
inspections.GET("/:id", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), c.getInspectionByID)
// Update inspections - teknisi can update their own, admin can update any
inspections.PUT("/:id", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), c.updateInspection)
// Only admin can approve inspections
inspections.PATCH("/:id/approve", middleware.RequireAdminRole(), c.approveInspection)
}
}
func (c *DeviceInspectionController) createInspection(ctx *gin.Context) {
userID, exists := ctx.Get("userID")
if !exists {
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
return
}
uid, ok := userID.(uuid.UUID)
if !ok {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
return
}
var inspectionDTO req.DeviceInspectionDTO
if err := ctx.ShouldBindJSON(&inspectionDTO); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
err := c.inspectionUC.CreateInspection(uid, inspectionDTO)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Device inspection created successfully", nil)
}
func (c *DeviceInspectionController) getAllInspections(ctx *gin.Context) {
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
inspections, total, err := c.inspectionUC.GetAllInspections(page, limit)
if err != nil {
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
return
}
response := gin.H{
"inspections": inspections,
"total": total,
"page": page,
"limit": limit,
"total_pages": (total + int64(limit) - 1) / int64(limit),
}
common.SingleResponses(ctx, "All inspections retrieved successfully", response)
}
func (c *DeviceInspectionController) getMyInspections(ctx *gin.Context) {
userID, exists := ctx.Get("userID")
if !exists {
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
return
}
uid, ok := userID.(uuid.UUID)
if !ok {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
return
}
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
if page < 1 {
page = 1
}
if limit < 1 || limit > 100 {
limit = 10
}
inspections, total, err := c.inspectionUC.GetUserInspections(uid, page, limit)
if err != nil {
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
return
}
response := gin.H{
"inspections": inspections,
"total": total,
"page": page,
"limit": limit,
"total_pages": (total + int64(limit) - 1) / int64(limit),
}
common.SingleResponses(ctx, "User inspections retrieved successfully", response)
}
func (c *DeviceInspectionController) getInspectionByID(ctx *gin.Context) {
id := ctx.Param("id")
inspectionID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid inspection ID")
return
}
userID, exists := ctx.Get("userID")
if !exists {
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
return
}
uid, ok := userID.(uuid.UUID)
if !ok {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
return
}
userRole, exists := ctx.Get("userRole")
if !exists {
common.ErrorResponses(ctx, http.StatusUnauthorized, "User role not found")
return
}
role, ok := userRole.(string)
if !ok {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user role")
return
}
// Get the inspection first
inspection, err := c.inspectionUC.GetInspectionByID(inspectionID)
if err != nil {
common.ErrorResponses(ctx, http.StatusNotFound, err.Error())
return
}
// For teknisi, check if they own this inspection
if role == "Teknisi" {
isOwner, err := c.inspectionUC.CheckInspectionOwnership(inspectionID, uid)
if err != nil {
common.ErrorResponses(ctx, http.StatusInternalServerError, "Error checking inspection ownership")
return
}
if !isOwner {
common.ErrorResponses(ctx, http.StatusForbidden, "Unauthorized: you can only view your own inspections")
return
}
}
common.SingleResponses(ctx, "Inspection retrieved successfully", inspection)
}
func (c *DeviceInspectionController) updateInspection(ctx *gin.Context) {
id := ctx.Param("id")
inspectionID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid inspection ID")
return
}
userID, exists := ctx.Get("userID")
if !exists {
common.ErrorResponses(ctx, http.StatusUnauthorized, "User ID not found")
return
}
uid, ok := userID.(uuid.UUID)
if !ok {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
return
}
userRole, _ := ctx.Get("userRole")
role, _ := userRole.(string)
var inspectionDTO req.UpdateDeviceInspectionDTO
if err := ctx.ShouldBindJSON(&inspectionDTO); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
err = c.inspectionUC.UpdateInspection(inspectionID, uid, inspectionDTO, role)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Inspection updated successfully", nil)
}
func (c *DeviceInspectionController) approveInspection(ctx *gin.Context) {
id := ctx.Param("id")
inspectionID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid inspection ID")
return
}
var approvalDTO req.ApproveInspectionDTO
if err := ctx.ShouldBindJSON(&approvalDTO); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
err = c.inspectionUC.ApproveInspection(inspectionID, approvalDTO)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
common.SingleResponses(ctx, "Inspection approval updated successfully", nil)
}

View File

@ -0,0 +1,101 @@
package controller
import (
"net/http"
"strconv"
"users_management/m/middleware"
"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 NearestDeviceController struct {
nearestDeviceUC usecase.NearestDeviceUseCase
rg *gin.RouterGroup
}
func NewNearestDeviceController(nearestDeviceUC usecase.NearestDeviceUseCase, rg *gin.RouterGroup) *NearestDeviceController {
return &NearestDeviceController{
nearestDeviceUC: nearestDeviceUC,
rg: rg,
}
}
func (c *NearestDeviceController) Route() {
nearestDevices := c.rg.Group("/nearest-devices")
nearestDevices.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"))
{
nearestDevices.POST("/search", c.getNearestDevices)
nearestDevices.GET("/:id", c.getNearestDeviceByID)
}
}
func (c *NearestDeviceController) getNearestDevices(ctx *gin.Context) {
var request req.NearestDeviceDTO
if err := ctx.ShouldBindJSON(&request); err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
devices, err := c.nearestDeviceUC.GetNearestDevices(request)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
return
}
response := gin.H{
"devices": devices,
"total": len(devices),
"search_params": gin.H{
"latitude": request.Latitude,
"longitude": request.Longitude,
"radius": request.Radius,
"province": request.Province,
"city": request.City,
"district": request.District,
},
}
common.SingleResponses(ctx, "Nearest devices retrieved successfully", response)
}
func (c *NearestDeviceController) getNearestDeviceByID(ctx *gin.Context) {
id := ctx.Param("id")
deviceID, err := uuid.Parse(id)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID")
return
}
// Get user coordinates from query params
latStr := ctx.Query("lat")
lngStr := ctx.Query("lng")
if latStr == "" || lngStr == "" {
common.ErrorResponses(ctx, http.StatusBadRequest, "User coordinates (lat, lng) are required")
return
}
userLat, err := strconv.ParseFloat(latStr, 64)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid latitude")
return
}
userLng, err := strconv.ParseFloat(lngStr, 64)
if err != nil {
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid longitude")
return
}
device, err := c.nearestDeviceUC.GetNearestDeviceByID(deviceID, userLat, userLng)
if err != nil {
common.ErrorResponses(ctx, http.StatusNotFound, err.Error())
return
}
common.SingleResponses(ctx, "Device details retrieved successfully", device)
}

View File

@ -60,6 +60,9 @@ func (s *Server) setupController() {
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), rg).Route()
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), rg).Route()
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), rg).Route()
controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), rg).Route()
controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), rg).Route()
controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), rg).Route()
}
}

View File

@ -12,6 +12,10 @@ type RepositoryManager interface {
NewDevicePortRepository() repository.DevicePortRepo
NewCountAssetsRepository() repository.CountAssetsRepo
NewActivityLogRepository() repository.ActivityLogRepo
NewDeviceInspectionRepository() repository.DeviceInspectionRepo
NewNearestDeviceRepository() repository.NearestDeviceRepo
NewDeviceDetailsRepository() repository.DeviceDetailsRepo
}
@ -25,6 +29,13 @@ func NewRepositoryManager(infra InfraManager) RepositoryManager {
}
}
func (rm *repositoryManager) NewDeviceDetailsRepository() repository.DeviceDetailsRepo {
return repository.NewDeviceDetailsRepo(rm.infra.Conn())
}
func (rm *repositoryManager) NewNearestDeviceRepository() repository.NearestDeviceRepo {
return repository.NewNearestDeviceRepo(rm.infra.Conn())
}
func (rm *repositoryManager) NewUserRepository() repository.UsersRepo {
return repository.NewUsersRepo(rm.infra.Conn())
}
@ -56,3 +67,7 @@ func (rm *repositoryManager) NewCountAssetsRepository() repository.CountAssetsRe
func (rm *repositoryManager) NewActivityLogRepository() repository.ActivityLogRepo {
return repository.NewActivityLogRepo(rm.infra.Conn())
}
func (rm *repositoryManager) NewDeviceInspectionRepository() repository.DeviceInspectionRepo {
return repository.NewDeviceInspectionRepo(rm.infra.Conn())
}

View File

@ -20,6 +20,12 @@ type UsecaseManager interface {
NewActivityLogUsecase() usecase.ActivityLogUseCase
NewDeviceInspectionUsecase() usecase.DeviceInspectionUseCase
NewNearestDeviceUsecase() usecase.NearestDeviceUseCase
NewDeviceDetailsUsecase() usecase.DeviceDetailsUseCase
}
type usecaseManager struct {
@ -34,6 +40,20 @@ func NewUsecaseManager(repo RepositoryManager, cfg *config.Config) UsecaseManage
return &usecaseManager{repo: repo,cfg: cfg , geocoder: cachedGeocoder}
}
func (um *usecaseManager) NewDeviceDetailsUsecase() usecase.DeviceDetailsUseCase {
return usecase.NewDeviceDetailsUseCase(
um.repo.NewDeviceDetailsRepository(),
um.geocoder,
)
}
func (um *usecaseManager) NewNearestDeviceUsecase() usecase.NearestDeviceUseCase {
return usecase.NewNearestDeviceUseCase(
um.repo.NewNearestDeviceRepository(),
um.geocoder,
)
}
func (um *usecaseManager) NewUserUsecase() usecase.UsersUsecase {
return usecase.NewUsersUsecase(um.repo.NewUserRepository())
}
@ -47,11 +67,20 @@ func (um *usecaseManager) NewDeviceUsecase() usecase.DeviceUseCase {
}
func (um *usecaseManager) NewBackboneUsecase() usecase.BackboneUseCase {
return usecase.NewBackboneUseCase(um.repo.NewBackboneRepository(),um.repo.NewFishboneRepository())
return usecase.NewBackboneUseCase(
um.repo.NewBackboneRepository(),
um.repo.NewFishboneRepository(),
um.repo.NewDeviceDetailsRepository(), // Add this
)
}
func (um *usecaseManager) NewFishboneUsecase() usecase.FishboneUseCase {
return usecase.NewFishboneUseCase(um.repo.NewFishboneRepository())
return usecase.NewFishboneUseCase(
um.repo.NewFishboneRepository(),
um.repo.NewBackboneRepository(),
um.repo.NewDeviceDetailsRepository(), // Add this line
)
}
func (um *usecaseManager) NewTowerUsecase() usecase.TowerUseCase {
@ -70,3 +99,11 @@ func (um *usecaseManager) NewCountAssetsUsecase() usecase.CountAssetsUseCase {
func (um *usecaseManager) NewActivityLogUsecase() usecase.ActivityLogUseCase {
return usecase.NewActivityLogUseCase(um.repo.NewActivityLogRepository())
}
func (um *usecaseManager) NewDeviceInspectionUsecase() usecase.DeviceInspectionUseCase {
return usecase.NewDeviceInspectionUseCase(
um.repo.NewDeviceInspectionRepository(),
um.NewActivityLogUsecase(),
service.NewGeocodingService(), // Add the geocoding service
)
}

View File

@ -0,0 +1,28 @@
package req
type DeviceDetailsDTO struct {
DeviceCode string `json:"device_code" validate:"required"`
DeviceType string `json:"device_type" validate:"required,oneof=OTB ODP"`
Longitude float64 `json:"longitude" validate:"required"`
Latitude float64 `json:"latitude" validate:"required"`
PortAmount int `json:"port_amount" validate:"required,min=1,max=100"`
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
}
type UpdateDeviceDetailsDTO struct {
DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"`
DeviceType *string `json:"device_type,omitempty" validate:"omitempty,oneof=OTB ODP"`
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=1,max=100"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
}

View File

@ -1,28 +1,25 @@
package req
type DeviceDTO struct {
DeviceCode string `json:"device_code" validate:"required"`
DeviceType string `json:"device_type" validate:"required"`
Longitude float64 `json:"longitude" validate:"required"`
Latitude float64 `json:"latitude" validate:"required"`
PortAmount int `json:"port_amount" validate:"required"`
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
Region string `json:"region" validate:"required"`
Province string `json:"province" validate:"required"`
City string `json:"city" validate:"required"`
District string `json:"district" validate:"required"`
DeviceCode string `json:"device_code" validate:"required"`
DeviceType string `json:"device_type" validate:"required"`
Longitude float64 `json:"longitude" validate:"required"`
Latitude float64 `json:"latitude" validate:"required"`
PortAmount int `json:"port_amount" validate:"required"`
Status string `json:"status" validate:"required,oneof=active inactive maintenance"`
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
}
type UpdateDeviceDTO struct {
DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"`
DeviceType *string `json:"device_type,omitempty" validate:"omitempty,min=3"`
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=1"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
Region *string `json:"region,omitempty" validate:"omitempty,min=3"`
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
DeviceCode *string `json:"device_code,omitempty" validate:"omitempty,min=3"`
DeviceType *string `json:"device_type,omitempty" validate:"omitempty,min=3"`
Longitude *float64 `json:"longitude,omitempty" validate:"omitempty,longitude"`
Latitude *float64 `json:"latitude,omitempty" validate:"omitempty,latitude"`
PortAmount *int `json:"port_amount,omitempty" validate:"omitempty,min=1"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=active inactive maintenance"`
Province *string `json:"province,omitempty" validate:"omitempty,min=3"`
City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"`
}

View File

@ -0,0 +1,38 @@
package req
import "github.com/google/uuid"
type DeviceInspectionDTO struct {
DeviceID uuid.UUID `json:"device_id" validate:"required"`
BackboneID *uuid.UUID `json:"backbone_id,omitempty"`
FishboneID *uuid.UUID `json:"fishbone_id,omitempty"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
Status string `json:"status" validate:"required,oneof=pending in_progress completed maintenance"`
PortUsed string `json:"port_used" validate:"required"`
PortAvailable string `json:"port_available"`
Description string `json:"description"`
ImageURL *string `json:"image_url,omitempty"`
InspectionPlacement struct {
Longitude float64 `json:"longitude" validate:"required"`
Latitude float64 `json:"latitude" validate:"required"`
} `json:"inspection_placement" validate:"required"`
}
type UpdateDeviceInspectionDTO struct {
BackboneID *uuid.UUID `json:"backbone_id,omitempty"`
FishboneID *uuid.UUID `json:"fishbone_id,omitempty"`
TowerID *uuid.UUID `json:"tower_id,omitempty"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending in_progress completed maintenance"`
PortUsed *string `json:"port_used,omitempty"`
Description *string `json:"description,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
InspectionPlacement *struct {
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
} `json:"inspection_placement,omitempty"`
}
type ApproveInspectionDTO struct {
InspectionApproval string `json:"inspection_approval" validate:"required,oneof=approved rejected"`
Notes string `json:"notes,omitempty"`
}

View File

@ -0,0 +1,11 @@
package req
type NearestDeviceDTO struct {
Longitude float64 `json:"longitude" validate:"required"`
Latitude float64 `json:"latitude" validate:"required"`
Radius float64 `json:"radius" validate:"omitempty,min=0.1,max=50"` // Default 5km, max 50km
Limit int `json:"limit" validate:"omitempty,min=1,max=100"` // Default 10, max 100
Province *string `json:"province,omitempty"`
City *string `json:"city,omitempty"`
District *string `json:"district,omitempty"`
}

View File

@ -0,0 +1,39 @@
package res
import (
"time"
"github.com/google/uuid"
)
type DeviceDetailsResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
Address string `json:"address"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Status string `json:"status"`
PortAmount int `json:"port_amount"`
PortUsed int `json:"port_used"`
PortAvailable int `json:"port_available"`
Region *string `json:"region,omitempty"`
Province *string `json:"province,omitempty"`
City *string `json:"city,omitempty"`
District *string `json:"district,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
// Connection details
Backbones []BackboneConnectionInfo `json:"backbones"`
Fishbones []FishboneConnectionInfo `json:"fishbones"`
Towers []TowerConnectionDetail `json:"towers"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type TowerConnectionDetail struct {
ID uuid.UUID `json:"id"`
TowerCode string `json:"tower_code"`
Distance float64 `json:"distance_km"` // Distance from tower to device
ImageURL *string `json:"image_url,omitempty"`
}

View File

@ -0,0 +1,72 @@
package res
import (
"time"
"github.com/google/uuid"
)
type DeviceInspectionResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
UserName string `json:"user_name"`
BackboneCode *string `json:"backbone_code,omitempty"`
FishboneCode *string `json:"fishbone_code,omitempty"`
TowerCode *string `json:"tower_code,omitempty"`
Status string `json:"status"`
PortUsed string `json:"port_used"`
PortAvailable string `json:"port_available"`
CableAmount int `json:"cable_amount"`
Description string `json:"description"`
ImageURL *string `json:"image_url,omitempty"`
InspectionPlacement string `json:"inspection_placement"` // Full address
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
InspectionApproval string `json:"inspection_approval"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type DeviceInspectionDetailResponse struct {
ID uuid.UUID `json:"id"`
Device DeviceInfo `json:"device"`
User UserInfo `json:"user"`
Backbone *BackboneInfo `json:"backbone,omitempty"`
Fishbone *FishboneInfo `json:"fishbone,omitempty"`
Tower *TowerInfo `json:"tower,omitempty"`
Status string `json:"status"`
PortUsed string `json:"port_used"`
PortAvailable string `json:"port_available"`
CableAmount int `json:"cable_amount"`
Description string `json:"description"`
ImageURL *string `json:"image_url,omitempty"`
InspectionPlacement string `json:"inspection_placement"` // Full address
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
InspectionApproval string `json:"inspection_approval"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type DeviceInfo struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
}
type UserInfo struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
}
type TowerInfo struct {
ID uuid.UUID `json:"id"`
TowerCode string `json:"tower_code"`
}
type FishboneInfo struct {
ID uuid.UUID `json:"id"`
FishboneCode string `json:"fishbone_code"`
}

View File

@ -7,14 +7,17 @@ import (
)
type DeviceResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Address string `json:"address"`
PortAmount int `json:"port_amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Address string `json:"address"`
PortAmount int `json:"port_amount"`
Status string `json:"status"` // Always include, even if null
Province *string `json:"province"` // Always include, even if null
City *string `json:"city"` // Always include, even if null
District *string `json:"district"` // Always include, even if null
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@ -0,0 +1,80 @@
package res
import (
"time"
"github.com/google/uuid"
)
// Simplified response for list
type NearestDeviceResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
Distance float64 `json:"distance_km"`
Address string `json:"address"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Status string `json:"status"`
PortAmount int `json:"port_amount"`
Region *string `json:"region,omitempty"`
Province *string `json:"province,omitempty"`
City *string `json:"city,omitempty"`
District *string `json:"district,omitempty"`
// Connection counts
BackboneCount int `json:"backbone_count"`
FishboneCount int `json:"fishbone_count"`
TowerCount int `json:"tower_count"`
CreatedAt time.Time `json:"created_at"`
}
// Detailed response for single device
type NearestDeviceDetailResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType string `json:"device_type"`
Distance float64 `json:"distance_km"`
Address string `json:"address"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Status string `json:"status"`
PortAmount int `json:"port_amount"`
Region *string `json:"region,omitempty"`
Province *string `json:"province,omitempty"`
City *string `json:"city,omitempty"`
District *string `json:"district,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
// Detailed connection information
Backbones []BackboneConnectionInfo `json:"backbones"`
Fishbones []FishboneConnectionInfo `json:"fishbones"`
Towers []TowerConnectionInfo `json:"towers"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type BackboneConnectionInfo struct {
ID uuid.UUID `json:"id"`
BackboneCode string `json:"backbone_code"`
CoreAmount int `json:"core_amount"`
IsStartDevice bool `json:"is_start_device"`
ConnectedTo string `json:"connected_to"` // Device code of the other end
}
type FishboneConnectionInfo struct {
ID uuid.UUID `json:"id"`
FishboneCode string `json:"fishbone_code"`
CoreAmount int `json:"core_amount"`
BackboneCode string `json:"backbone_code"`
IsStartDevice bool `json:"is_start_device"`
ConnectedTo string `json:"connected_to"` // Device code of the other end
}
type TowerConnectionInfo struct {
ID uuid.UUID `json:"id"`
TowerCode string `json:"tower_code"`
Distance float64 `json:"distance_km"` // Distance from tower to device
ImageURL *string `json:"image_url,omitempty"`
}

View File

@ -9,8 +9,8 @@ import (
type TowerResponse struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
TowerCode string `json:"tower_code"`
Longitude float64 `json:"longtitude"`
TowerCode *string `json:"tower_code"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
Address string `json:"address"`
ImageURL string `json:"image_url"`

View File

@ -0,0 +1,51 @@
package entity
import (
"time"
"github.com/google/uuid"
)
type DeviceDetails struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
DeviceCode string `json:"device_code" gorm:"unique"`
DeviceType DeviceType `json:"device_type"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
PortAmount int `json:"port_amount"`
Status DeviceStatus `json:"status"`
Region *string `json:"region,omitempty" gorm:"type:varchar(255)"`
Province *string `json:"province,omitempty" gorm:"type:varchar(255)"`
City *string `json:"city,omitempty" gorm:"type:varchar(255)"`
District *string `json:"district,omitempty" gorm:"type:varchar(255)"`
ImageURL *string `json:"image_url,omitempty" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Fixed Relationships - Use direct foreign keys
DevicePort DevicePort `json:"device_port" gorm:"foreignKey:DeviceID;references:ID"`
BackbonesStart []Backbone `json:"backbones_start" gorm:"foreignKey:DeviceStartID;references:ID"`
BackbonesEnd []Backbone `json:"backbones_end" gorm:"foreignKey:DeviceEndID;references:ID"`
FishbonesStart []Fishbone `json:"fishbones_start" gorm:"foreignKey:DeviceStartID;references:ID"`
FishbonesEnd []Fishbone `json:"fishbones_end" gorm:"foreignKey:DeviceEndID;references:ID"`
Towers []Tower `json:"towers" gorm:"foreignKey:DeviceID;references:ID"`
}
func (DeviceDetails) TableName() string {
return "devices" // Use same table as Device entity
}
// Helper method to get all backbones connected to this device
func (d *DeviceDetails) GetAllBackbones() []Backbone {
allBackbones := make([]Backbone, 0)
allBackbones = append(allBackbones, d.BackbonesStart...)
allBackbones = append(allBackbones, d.BackbonesEnd...)
return allBackbones
}
// Helper method to get all fishbones connected to this device
func (d *DeviceDetails) GetAllFishbones() []Fishbone {
allFishbones := make([]Fishbone, 0)
allFishbones = append(allFishbones, d.FishbonesStart...)
allFishbones = append(allFishbones, d.FishbonesEnd...)
return allFishbones
}

View File

@ -0,0 +1,38 @@
package entity
import (
"time"
"github.com/google/uuid"
)
type DeviceInspection struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;column:device_id"`
BackboneID *uuid.UUID `json:"backbone_id,omitempty" gorm:"type:uuid;column:backbone_id"`
FishboneID *uuid.UUID `json:"fishbone_id,omitempty" gorm:"type:uuid;column:fishbone_id"`
TowerID *uuid.UUID `json:"tower_id,omitempty" gorm:"type:uuid;column:tower_id"`
UserID uuid.UUID `json:"user_id" gorm:"type:uuid;column:user_id"`
Status string `json:"status" gorm:"type:varchar(50);column:status"`
PortUsed string `json:"port_used" gorm:"type:varchar(50);column:port_used"`
PortAvailable string `json:"port_available" gorm:"type:varchar(50);column:port_available"`
CableAmount int `json:"cable_amount" gorm:"type:int;column:cable_amount"`
Description string `json:"description" gorm:"type:text;column:description"`
ImageURL *string `json:"image_url,omitempty" gorm:"type:text;column:image_url"`
InspectionPlacement string `json:"inspection_placement" gorm:"type:text;column:inspection_placement"` // Store the full address from OpenStreetMap
Longitude float64 `json:"longitude" gorm:"type:float;column:longitude"`
Latitude float64 `json:"latitude" gorm:"type:float;column:latitude"`
InspectionApproval string `json:"inspection_approval" gorm:"type:varchar(50);column:inspection_approval"`
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;column:created_at"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;column:updated_at"`
// Relationships
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
Backbone *Backbone `json:"backbone,omitempty" gorm:"foreignKey:BackboneID"`
Fishbone *Fishbone `json:"fishbone,omitempty" gorm:"foreignKey:FishboneID"`
User User `json:"user" gorm:"foreignKey:UserID"`
Tower *Tower `json:"tower,omitempty" gorm:"foreignKey:TowerID"`
}
func (DeviceInspection) TableName() string {
return "device_inspection"
}

View File

@ -6,13 +6,15 @@ import (
)
type DevicePort struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;column:device_id"`
PortNumber int `json:"port_number"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
DeviceID uuid.UUID `json:"device_id" gorm:"type:uuid;column:device_id;unique"`
PortUsed int `json:"port_used" gorm:"default:0"` // Auto-calculated
PortAvailable int `json:"port_available" gorm:"default:0"` // Auto-calculated
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Device Device `gorm:"foreignKey:DeviceID"`
// Relationships
Device Device `json:"device" gorm:"foreignKey:DeviceID"`
}
func (DevicePort) TableName() string {

View File

@ -0,0 +1,25 @@
package entity
import (
"time"
"github.com/google/uuid"
)
// DeviceWithDistance represents a device with calculated distance
type DeviceWithDistance struct {
ID uuid.UUID `json:"id"`
DeviceCode string `json:"device_code"`
DeviceType DeviceType `json:"device_type"`
Longitude float64 `json:"longitude"`
Latitude float64 `json:"latitude"`
PortAmount int `json:"port_amount"`
Status DeviceStatus `json:"status"`
Region *string `json:"region,omitempty"`
Province *string `json:"province,omitempty"`
City *string `json:"city,omitempty"`
District *string `json:"district,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Distance float64 `json:"distance"` // Calculated distance
}

View File

@ -9,10 +9,12 @@ type DeviceType string
type DeviceStatus string
const (
Odp DeviceType = "ODP"
activeDev DeviceStatus = "active"
inactiveDev DeviceStatus = "inactive"
maintenanceDev DeviceStatus = "maintenance"
ODP DeviceType = "ODP"
OTB DeviceType = "OTB"
ActiveDev DeviceStatus = "active"
InactiveDev DeviceStatus = "inactive"
MaintenanceDev DeviceStatus = "maintenance"
)
type Device struct {
@ -23,10 +25,10 @@ type Device struct {
Latitude float64 `json:"latitude"`
PortAmount int `json:"port_amount"`
Status DeviceStatus `json:"status"`
Region string `json:"region"`
Province string `json:"province"`
City string `json:"city"`
District string `json:"district"`
Province *string `json:"province,omitempty" gorm:"type:varchar(255)"`
City *string `json:"city,omitempty" gorm:"type:varchar(255)"`
District *string `json:"district,omitempty" gorm:"type:varchar(255)"`
ImageURL *string `json:"image_url,omitempty" gorm:"type:text"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@ -0,0 +1,13 @@
package entity
import "github.com/google/uuid"
type TowerInspection struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
TowerID uuid.UUID `json:"tower_id" gorm:"type:uuid;column:tower_id"`
UserID uuid.UUID `json:"user_id" gorm:"type:uuid;column:user_id"`
Description string `json:"description" gorm:"type:text;column:description"`
ImageURL string `json:"image_url" gorm:"type:text;column:image_url"`
CreatedAt string `json:"created_at" gorm:"type:timestamp;column:created_at"`
UpdatedAt string `json:"updated_at" gorm:"type:timestamp;column:updated_at"`
}

View File

@ -0,0 +1,254 @@
package repository
import (
"errors"
"users_management/m/model/entity"
"github.com/google/uuid"
"gorm.io/gorm"
)
type DeviceDetailsRepo interface {
Create(device entity.Device) error
GetAll() ([]entity.DeviceDetails, error)
GetByID(id uuid.UUID) (entity.DeviceDetails, error)
Update(id uuid.UUID, updates map[string]interface{}) error
Delete(id uuid.UUID) error
// Port management
UpdateDevicePortUsage(deviceID uuid.UUID) error
ValidatePortAvailability(deviceID uuid.UUID, requiredPorts int) error
// Connection management
GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error)
GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error)
GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error)
// Validation helpers
CheckDeviceExists(deviceID uuid.UUID) (bool, error)
GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error)
}
type deviceDetailsRepo struct {
db *gorm.DB
}
func NewDeviceDetailsRepo(db *gorm.DB) DeviceDetailsRepo {
return &deviceDetailsRepo{
db: db,
}
}
func (r *deviceDetailsRepo) Create(device entity.Device) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Create device
if err := tx.Create(&device).Error; err != nil {
return err
}
// Create corresponding device port
devicePort := entity.DevicePort{
ID: uuid.New(),
DeviceID: device.ID,
PortUsed: 0,
PortAvailable: device.PortAmount,
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
}
return tx.Create(&devicePort).Error
})
}
func (r *deviceDetailsRepo) GetAll() ([]entity.DeviceDetails, error) {
var devices []entity.DeviceDetails
err := r.db.
Preload("DevicePort").
Preload("BackbonesStart").
Preload("BackbonesStart.DeviceStart").
Preload("BackbonesStart.DeviceEnd").
Preload("BackbonesEnd").
Preload("BackbonesEnd.DeviceStart").
Preload("BackbonesEnd.DeviceEnd").
Preload("FishbonesStart").
Preload("FishbonesStart.DeviceStart").
Preload("FishbonesStart.DeviceEnd").
Preload("FishbonesStart.Backbone").
Preload("FishbonesEnd").
Preload("FishbonesEnd.DeviceStart").
Preload("FishbonesEnd.DeviceEnd").
Preload("FishbonesEnd.Backbone").
Preload("Towers").
Find(&devices).Error
return devices, err
}
func (r *deviceDetailsRepo) GetByID(id uuid.UUID) (entity.DeviceDetails, error) {
var device entity.DeviceDetails
err := r.db.
Preload("DevicePort").
Preload("BackbonesStart").
Preload("BackbonesStart.DeviceStart").
Preload("BackbonesStart.DeviceEnd").
Preload("BackbonesEnd").
Preload("BackbonesEnd.DeviceStart").
Preload("BackbonesEnd.DeviceEnd").
Preload("FishbonesStart").
Preload("FishbonesStart.DeviceStart").
Preload("FishbonesStart.DeviceEnd").
Preload("FishbonesStart.Backbone").
Preload("FishbonesEnd").
Preload("FishbonesEnd.DeviceStart").
Preload("FishbonesEnd.DeviceEnd").
Preload("FishbonesEnd.Backbone").
Preload("Towers").
Where("id = ?", id).
First(&device).Error
return device, err
}
func (r *deviceDetailsRepo) Update(id uuid.UUID, updates map[string]interface{}) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Update device
if err := tx.Model(&entity.Device{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return err
}
// If port_amount is updated, update device_port
if portAmount, exists := updates["port_amount"]; exists {
if err := r.updatePortAmountCascade(tx, id, portAmount.(int)); err != nil {
return err
}
}
return nil
})
}
func (r *deviceDetailsRepo) updatePortAmountCascade(tx *gorm.DB, deviceID uuid.UUID, newPortAmount int) error {
// Get current port usage
var devicePort entity.DevicePort
if err := tx.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil {
return err
}
// Check if new port amount is sufficient for current usage
if newPortAmount < devicePort.PortUsed {
return errors.New("cannot reduce port amount below current usage")
}
// Update port available
newPortAvailable := newPortAmount - devicePort.PortUsed
return tx.Model(&entity.DevicePort{}).
Where("device_id = ?", deviceID).
Updates(map[string]interface{}{
"port_available": newPortAvailable,
"updated_at": gorm.Expr("NOW()"),
}).Error
}
func (r *deviceDetailsRepo) UpdateDevicePortUsage(deviceID uuid.UUID) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Get device info
var device entity.Device
if err := tx.Where("id = ?", deviceID).First(&device).Error; err != nil {
return err
}
var portUsed int
if device.DeviceType == "OTB" {
// For OTB: count backbones (each backbone uses 1 port)
var backboneCount int64
if err := tx.Model(&entity.Backbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Count(&backboneCount).Error; err != nil {
return err
}
portUsed = int(backboneCount)
} else if device.DeviceType == "ODP" {
// For ODP: sum fishbone core amounts (each core uses 1 port)
var totalCores int64
if err := tx.Model(&entity.Fishbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Select("COALESCE(SUM(core_amount), 0)").
Scan(&totalCores).Error; err != nil {
return err
}
portUsed = int(totalCores)
}
portAvailable := device.PortAmount - portUsed
return tx.Model(&entity.DevicePort{}).
Where("device_id = ?", deviceID).
Updates(map[string]interface{}{
"port_used": portUsed,
"port_available": portAvailable,
"updated_at": gorm.Expr("NOW()"),
}).Error
})
}
func (r *deviceDetailsRepo) ValidatePortAvailability(deviceID uuid.UUID, requiredPorts int) error {
var devicePort entity.DevicePort
if err := r.db.Where("device_id = ?", deviceID).First(&devicePort).Error; err != nil {
return err
}
if devicePort.PortAvailable < requiredPorts {
return errors.New("insufficient available ports")
}
return nil
}
func (r *deviceDetailsRepo) GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error) {
var backbones []entity.Backbone
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Find(&backbones).Error
return backbones, err
}
func (r *deviceDetailsRepo) GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error) {
var fishbones []entity.Fishbone
err := r.db.Preload("Backbone").Preload("DeviceStart").Preload("DeviceEnd").
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Find(&fishbones).Error
return fishbones, err
}
func (r *deviceDetailsRepo) GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error) {
var towers []entity.Tower
err := r.db.Where("dev_id = ?", deviceID).Find(&towers).Error
return towers, err
}
func (r *deviceDetailsRepo) CheckDeviceExists(deviceID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.Device{}).Where("id = ?", deviceID).Count(&count).Error
return count > 0, err
}
func (r *deviceDetailsRepo) GetPortUsageByDevice(deviceID uuid.UUID) (portUsed, portAvailable int, err error) {
var devicePort entity.DevicePort
err = r.db.Where("device_id = ?", deviceID).First(&devicePort).Error
if err != nil {
return 0, 0, err
}
return devicePort.PortUsed, devicePort.PortAvailable, nil
}
func (r *deviceDetailsRepo) Delete(id uuid.UUID) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Delete device port first
if err := tx.Where("device_id = ?", id).Delete(&entity.DevicePort{}).Error; err != nil {
return err
}
// Delete device
return tx.Delete(&entity.Device{}, id).Error
})
}

View File

@ -0,0 +1,155 @@
package repository
import (
"users_management/m/model/entity"
"github.com/google/uuid"
"gorm.io/gorm"
)
type DeviceInspectionRepo interface {
Create(inspection entity.DeviceInspection) error
GetAll() ([]entity.DeviceInspection, error)
GetByID(id uuid.UUID) (entity.DeviceInspection, error)
GetByUserID(userID uuid.UUID, limit, offset int) ([]entity.DeviceInspection, error)
Update(id uuid.UUID, updates map[string]interface{}) error
CheckDeviceExists(deviceID uuid.UUID) (bool, error)
CheckBackboneExists(backboneID uuid.UUID) (bool, error)
CheckFishboneExists(fishboneID uuid.UUID) (bool, error)
CheckTowerExists(towerID uuid.UUID) (bool, error)
GetDeviceByID(deviceID uuid.UUID) (entity.Device, error)
CountCablesByDevice(deviceID uuid.UUID, deviceType string) (int, error)
CountByUserID(userID uuid.UUID) (int64, error)
CountAll() (int64, error)
UpdateDeviceStatus(deviceID uuid.UUID, status string) error
GetDevicePortByDeviceID(deviceID uuid.UUID) (entity.DevicePort, error)
UpdateDevicePort(deviceID uuid.UUID, updates map[string]interface{}) error
CheckOwnership(inspectionID, userID uuid.UUID) (bool, error) // Add this line
}
type deviceInspectionRepo struct {
db *gorm.DB
}
func NewDeviceInspectionRepo(db *gorm.DB) DeviceInspectionRepo {
return &deviceInspectionRepo{
db: db,
}
}
func (r *deviceInspectionRepo) CheckOwnership(inspectionID, userID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.DeviceInspection{}).
Where("id = ? AND user_id = ?", inspectionID, userID).
Count(&count).Error
return count > 0, err
}
func (r *deviceInspectionRepo) Create(inspection entity.DeviceInspection) error {
return r.db.Create(&inspection).Error
}
func (r *deviceInspectionRepo) GetAll() ([]entity.DeviceInspection, error) {
var inspections []entity.DeviceInspection
err := r.db.Preload("Device").Preload("User").Preload("Backbone").
Preload("Fishbone").Preload("Tower").Find(&inspections).Error
return inspections, err
}
func (r *deviceInspectionRepo) GetByID(id uuid.UUID) (entity.DeviceInspection, error) {
var inspection entity.DeviceInspection
err := r.db.Preload("Device").Preload("User").Preload("Backbone").
Preload("Fishbone").Preload("Tower").Where("id = ?", id).First(&inspection).Error
return inspection, err
}
func (r *deviceInspectionRepo) GetByUserID(userID uuid.UUID, limit, offset int) ([]entity.DeviceInspection, error) {
var inspections []entity.DeviceInspection
err := r.db.Preload("Device").Preload("User").Preload("Backbone").
Preload("Fishbone").Preload("Tower").
Where("user_id = ?", userID).
Order("created_at DESC").
Limit(limit).Offset(offset).
Find(&inspections).Error
return inspections, err
}
func (r *deviceInspectionRepo) Update(id uuid.UUID, updates map[string]interface{}) error {
return r.db.Model(&entity.DeviceInspection{}).Where("id = ?", id).Updates(updates).Error
}
func (r *deviceInspectionRepo) CheckDeviceExists(deviceID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.Device{}).Where("id = ?", deviceID).Count(&count).Error
return count > 0, err
}
func (r *deviceInspectionRepo) CheckBackboneExists(backboneID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.Backbone{}).Where("id = ?", backboneID).Count(&count).Error
return count > 0, err
}
func (r *deviceInspectionRepo) CheckFishboneExists(fishboneID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.Fishbone{}).Where("id = ?", fishboneID).Count(&count).Error
return count > 0, err
}
func (r *deviceInspectionRepo) CheckTowerExists(towerID uuid.UUID) (bool, error) {
var count int64
err := r.db.Model(&entity.Tower{}).Where("id = ?", towerID).Count(&count).Error
return count > 0, err
}
func (r *deviceInspectionRepo) GetDeviceByID(deviceID uuid.UUID) (entity.Device, error) {
var device entity.Device
err := r.db.Where("id = ?", deviceID).First(&device).Error
return device, err
}
func (r *deviceInspectionRepo) CountCablesByDevice(deviceID uuid.UUID, deviceType string) (int, error) {
var count int64
if deviceType == "OTB" {
// Count backbones using this device
err := r.db.Model(&entity.Backbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Count(&count).Error
return int(count), err
} else if deviceType == "ODP" {
// Sum core amounts of fishbones using this device
var totalCores int64
err := r.db.Model(&entity.Fishbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Select("COALESCE(SUM(core_amount), 0)").Scan(&totalCores).Error
return int(totalCores), err
}
return 0, nil
}
func (r *deviceInspectionRepo) CountByUserID(userID uuid.UUID) (int64, error) {
var count int64
err := r.db.Model(&entity.DeviceInspection{}).Where("user_id = ?", userID).Count(&count).Error
return count, err
}
func (r *deviceInspectionRepo) CountAll() (int64, error) {
var count int64
err := r.db.Model(&entity.DeviceInspection{}).Count(&count).Error
return count, err
}
func (r *deviceInspectionRepo) UpdateDeviceStatus(deviceID uuid.UUID, status string) error {
return r.db.Model(&entity.Device{}).Where("id = ?", deviceID).Update("status", status).Error
}
func (r *deviceInspectionRepo) GetDevicePortByDeviceID(deviceID uuid.UUID) (entity.DevicePort, error) {
var devicePort entity.DevicePort
err := r.db.Where("device_id = ?", deviceID).First(&devicePort).Error
return devicePort, err
}
func (r *deviceInspectionRepo) UpdateDevicePort(deviceID uuid.UUID, updates map[string]interface{}) error {
return r.db.Model(&entity.DevicePort{}).Where("device_id = ?", deviceID).Updates(updates).Error
}

View File

@ -0,0 +1,118 @@
package repository
import (
"users_management/m/model/entity"
"github.com/google/uuid"
"gorm.io/gorm"
)
type NearestDeviceRepo interface {
GetNearestDevices(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.DeviceWithDistance, error)
GetDeviceByIDWithConnections(id uuid.UUID) (entity.Device, error)
GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error)
GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error)
GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error)
CountConnectionsByDeviceID(deviceID uuid.UUID) (backboneCount, fishboneCount, towerCount int, err error)
}
type nearestDeviceRepo struct {
db *gorm.DB
}
func NewNearestDeviceRepo(db *gorm.DB) NearestDeviceRepo {
return &nearestDeviceRepo{
db: db,
}
}
func (r *nearestDeviceRepo) GetNearestDevices(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.DeviceWithDistance, error) {
var devices []entity.DeviceWithDistance
// Build the subquery first
subQuery := r.db.Table("devices").
Select(`id, device_code, device_type, longitude, latitude, port_amount, status,
region, province, city, district, image_url, created_at, updated_at,
(6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) AS distance`,
latitude, longitude, latitude)
// Apply location filters to subquery
if province != nil && *province != "" {
subQuery = subQuery.Where("province = ?", *province)
}
if city != nil && *city != "" {
subQuery = subQuery.Where("city = ?", *city)
}
if district != nil && *district != "" {
subQuery = subQuery.Where("district = ?", *district)
}
// Use the subquery in the main query
err := r.db.Table("(?) as devices_with_distance", subQuery).
Where("distance <= ?", radius).
Order("distance ASC").
Limit(limit).
Scan(&devices).Error
return devices, err
}
func (r *nearestDeviceRepo) GetDeviceByIDWithConnections(id uuid.UUID) (entity.Device, error) {
var device entity.Device
err := r.db.Where("id = ?", id).First(&device).Error
return device, err
}
func (r *nearestDeviceRepo) GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error) {
var backbones []entity.Backbone
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Find(&backbones).Error
return backbones, err
}
func (r *nearestDeviceRepo) GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error) {
var fishbones []entity.Fishbone
err := r.db.Preload("Backbone").Preload("DeviceStart").Preload("DeviceEnd").
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Find(&fishbones).Error
return fishbones, err
}
func (r *nearestDeviceRepo) GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error) {
var towers []entity.Tower
err := r.db.Preload("Device").
Where("dev_id = ?", deviceID).
Find(&towers).Error
return towers, err
}
func (r *nearestDeviceRepo) CountConnectionsByDeviceID(deviceID uuid.UUID) (backboneCount, fishboneCount, towerCount int, err error) {
var backboneCountInt64, fishboneCountInt64, towerCountInt64 int64
// Count backbones
err = r.db.Model(&entity.Backbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Count(&backboneCountInt64).Error
if err != nil {
return 0, 0, 0, err
}
// Count fishbones
err = r.db.Model(&entity.Fishbone{}).
Where("dev_start_id = ? OR dev_end_id = ?", deviceID, deviceID).
Count(&fishboneCountInt64).Error
if err != nil {
return 0, 0, 0, err
}
// Count towers
err = r.db.Model(&entity.Tower{}).
Where("dev_id = ?", deviceID).
Count(&towerCountInt64).Error
if err != nil {
return 0, 0, 0, err
}
return int(backboneCountInt64), int(fishboneCountInt64), int(towerCountInt64), nil
}

View File

@ -19,42 +19,80 @@ type BackboneUseCase interface {
GetByID(id uuid.UUID) (res.BackboneResponse, error)
UpdateBackbone(id uuid.UUID, backbone req.UpdateBackboneDTO) error
}
type backboneUseCase struct {
backboneRepo repository.BackboneRepo
fishboneRepo repository.FishboneRepo
validate *validator.Validate
deviceDetailsRepo repository.DeviceDetailsRepo // Add this field
}
func NewBackboneUseCase(backboneRepo repository.BackboneRepo, fishboneRepo repository.FishboneRepo) BackboneUseCase {
return &backboneUseCase{
backboneRepo: backboneRepo,
fishboneRepo: fishboneRepo,
validate: validator.New(),
}
func NewBackboneUseCase(backboneRepo repository.BackboneRepo, fishboneRepo repository.FishboneRepo, deviceDetailsRepo repository.DeviceDetailsRepo) BackboneUseCase {
return &backboneUseCase{
backboneRepo: backboneRepo,
fishboneRepo: fishboneRepo,
deviceDetailsRepo: deviceDetailsRepo, // Initialize the field
validate: validator.New(),
}
}
func (u *backboneUseCase) CreateBackbone(backbone req.BackboneDTO) error {
err := u.validate.Struct(backbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
err := u.validate.Struct(backbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
newBackbone := entity.Backbone{
ID: uuid.New(),
BackboneCode: backbone.BackboneCode,
DeviceStartID: backbone.DeviceStartID,
DeviceEndID: backbone.DeviceEndID,
CoreAmount: backbone.CoreAmount,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Validate that both devices exist and are OTB type
startExists, err := u.deviceDetailsRepo.CheckDeviceExists(backbone.DeviceStartID)
if err != nil {
return fmt.Errorf("error checking start device: %w", err)
}
if !startExists {
return fmt.Errorf("start device does not exist")
}
endExists, err := u.deviceDetailsRepo.CheckDeviceExists(backbone.DeviceEndID)
if err != nil {
return fmt.Errorf("error checking end device: %w", err)
}
if !endExists {
return fmt.Errorf("end device does not exist")
}
return u.backboneRepo.Post(newBackbone)
// Validate port availability (each backbone connection uses 1 port)
if err := u.deviceDetailsRepo.ValidatePortAvailability(backbone.DeviceStartID, 1); err != nil {
return fmt.Errorf("start device: %w", err)
}
if err := u.deviceDetailsRepo.ValidatePortAvailability(backbone.DeviceEndID, 1); err != nil {
return fmt.Errorf("end device: %w", err)
}
newBackbone := entity.Backbone{
ID: uuid.New(),
BackboneCode: backbone.BackboneCode,
DeviceStartID: backbone.DeviceStartID,
DeviceEndID: backbone.DeviceEndID,
CoreAmount: backbone.CoreAmount,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Create backbone
err = u.backboneRepo.Post(newBackbone)
if err != nil {
return err
}
// Update port usage for both devices
u.deviceDetailsRepo.UpdateDevicePortUsage(backbone.DeviceStartID)
u.deviceDetailsRepo.UpdateDevicePortUsage(backbone.DeviceEndID)
return nil
}
func (u *backboneUseCase) GetAllBackbone() ([]res.BackboneResponse, error) {
backbones, err := u.backboneRepo.GetAll()
if err != nil {
@ -95,26 +133,80 @@ func (u *backboneUseCase) GetByID(id uuid.UUID) (res.BackboneResponse, error) {
}
func (u *backboneUseCase) UpdateBackbone(id uuid.UUID, backbone req.UpdateBackboneDTO) error {
err := u.validate.Struct(backbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
err := u.validate.Struct(backbone)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
updates := make(map[string]interface{})
// Get original backbone to track changes
originalBackbone, err := u.backboneRepo.GetByID(id)
if err != nil {
return err
}
if backbone.DeviceStartID != nil {
updates["DeviceStartID"] = backbone.DeviceStartID
}
updates := make(map[string]interface{})
if backbone.DeviceEndID != nil {
updates["DeviceEndID"] = backbone.DeviceEndID
}
// Track devices that need port recalculation
devicesToUpdate := make(map[uuid.UUID]bool)
devicesToUpdate[originalBackbone.DeviceStartID] = true
devicesToUpdate[originalBackbone.DeviceEndID] = true
if backbone.CoreAmount != nil {
updates["CoreAmount"] = backbone.CoreAmount
}
if backbone.DeviceStartID != nil {
// Validate new start device exists and has available ports
exists, err := u.deviceDetailsRepo.CheckDeviceExists(*backbone.DeviceStartID)
if err != nil {
return fmt.Errorf("error checking new start device: %w", err)
}
if !exists {
return fmt.Errorf("new start device does not exist")
}
updates["UpdatedAt"] = time.Now()
if err := u.deviceDetailsRepo.ValidatePortAvailability(*backbone.DeviceStartID, 1); err != nil {
return fmt.Errorf("new start device: %w", err)
}
return u.backboneRepo.Update(id, updates)
updates["dev_start_id"] = *backbone.DeviceStartID
devicesToUpdate[*backbone.DeviceStartID] = true
}
if backbone.DeviceEndID != nil {
// Validate new end device exists and has available ports
exists, err := u.deviceDetailsRepo.CheckDeviceExists(*backbone.DeviceEndID)
if err != nil {
return fmt.Errorf("error checking new end device: %w", err)
}
if !exists {
return fmt.Errorf("new end device does not exist")
}
if err := u.deviceDetailsRepo.ValidatePortAvailability(*backbone.DeviceEndID, 1); err != nil {
return fmt.Errorf("new end device: %w", err)
}
updates["dev_end_id"] = *backbone.DeviceEndID
devicesToUpdate[*backbone.DeviceEndID] = true
}
if backbone.CoreAmount != nil {
updates["core_amount"] = *backbone.CoreAmount
}
if len(updates) == 0 {
return fmt.Errorf("no fields to update")
}
updates["updated_at"] = time.Now()
// Update backbone
err = u.backboneRepo.Update(id, updates)
if err != nil {
return err
}
// Recalculate port usage for all affected devices
for deviceID := range devicesToUpdate {
u.deviceDetailsRepo.UpdateDevicePortUsage(deviceID)
}
return nil
}

View File

@ -39,7 +39,6 @@ func (u *devicePortUseCase) CreateDevicePort(devicePort req.DevicePort) error {
newDevicePort := entity.DevicePort{
ID: uuid.New(),
DeviceID: devicePort.DeviceID,
PortNumber: devicePort.PortNumber,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

186
usecase/device_details.go Normal file
View File

@ -0,0 +1,186 @@
package usecase
import (
"errors"
"fmt"
"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 DeviceDetailsUseCase interface {
CreateDeviceDetails(device req.DeviceDetailsDTO) error
GetAllDeviceDetails() ([]res.DeviceDetailsResponse, error)
GetDeviceDetailsByID(id uuid.UUID) (res.DeviceDetailsResponse, error)
UpdateDeviceDetails(id uuid.UUID, device req.UpdateDeviceDetailsDTO) error
DeleteDeviceDetails(id uuid.UUID) error
// Port management
ValidatePortUsage(deviceID uuid.UUID, requiredPorts int) error
RecalculatePortUsage(deviceID uuid.UUID) error
}
type deviceDetailsUseCase struct {
deviceDetailsRepo repository.DeviceDetailsRepo
geocoder service.GeocodingService
validate *validator.Validate
}
func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, geocoder service.GeocodingService) DeviceDetailsUseCase {
return &deviceDetailsUseCase{
deviceDetailsRepo: deviceDetailsRepo,
geocoder: geocoder,
validate: validator.New(),
}
}
func (u *deviceDetailsUseCase) CreateDeviceDetails(deviceDTO req.DeviceDetailsDTO) error {
err := u.validate.Struct(deviceDTO)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
newDevice := entity.Device{
ID: uuid.New(),
DeviceCode: deviceDTO.DeviceCode,
DeviceType: entity.DeviceType(deviceDTO.DeviceType),
Longitude: deviceDTO.Longitude,
Latitude: deviceDTO.Latitude,
PortAmount: deviceDTO.PortAmount,
Status: entity.DeviceStatus(deviceDTO.Status),
Province: deviceDTO.Province,
City: deviceDTO.City,
District: deviceDTO.District,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
return u.deviceDetailsRepo.Create(newDevice)
}
func (u *deviceDetailsUseCase) GetAllDeviceDetails() ([]res.DeviceDetailsResponse, error) {
devices, err := u.deviceDetailsRepo.GetAll()
if err != nil {
return nil, err
}
return helper.ConvertToDeviceDetailsResponses(devices, u.geocoder)
}
func (u *deviceDetailsUseCase) GetDeviceDetailsByID(id uuid.UUID) (res.DeviceDetailsResponse, error) {
device, err := u.deviceDetailsRepo.GetByID(id)
if err != nil {
return res.DeviceDetailsResponse{}, err
}
return helper.ConvertToDeviceDetailsResponse(device, u.geocoder)
}
func (u *deviceDetailsUseCase) UpdateDeviceDetails(id uuid.UUID, deviceDTO req.UpdateDeviceDetailsDTO) error {
err := u.validate.Struct(deviceDTO)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Check if device exists
exists, err := u.deviceDetailsRepo.CheckDeviceExists(id)
if err != nil {
return err
}
if !exists {
return errors.New("device not found")
}
updates := map[string]interface{}{}
if deviceDTO.DeviceCode != nil {
updates["device_code"] = *deviceDTO.DeviceCode
}
if deviceDTO.DeviceType != nil {
updates["device_type"] = *deviceDTO.DeviceType
}
if deviceDTO.Longitude != nil {
updates["longitude"] = *deviceDTO.Longitude
}
if deviceDTO.Latitude != nil {
updates["latitude"] = *deviceDTO.Latitude
}
if deviceDTO.PortAmount != nil {
// Validate port amount change
currentUsed, _, err := u.deviceDetailsRepo.GetPortUsageByDevice(id)
if err != nil {
return err
}
if *deviceDTO.PortAmount < currentUsed {
return fmt.Errorf("cannot reduce port amount to %d, currently using %d ports", *deviceDTO.PortAmount, currentUsed)
}
updates["port_amount"] = *deviceDTO.PortAmount
}
if deviceDTO.Status != nil {
updates["status"] = *deviceDTO.Status
}
if deviceDTO.Region != nil {
updates["region"] = *deviceDTO.Region
}
if deviceDTO.Province != nil {
updates["province"] = *deviceDTO.Province
}
if deviceDTO.City != nil {
updates["city"] = *deviceDTO.City
}
if deviceDTO.District != nil {
updates["district"] = *deviceDTO.District
}
if len(updates) == 0 {
return errors.New("no fields to update")
}
updates["updated_at"] = time.Now()
return u.deviceDetailsRepo.Update(id, updates)
}
func (u *deviceDetailsUseCase) DeleteDeviceDetails(id uuid.UUID) error {
// Check if device has connections
backbones, err := u.deviceDetailsRepo.GetBackbonesByDeviceID(id)
if err != nil {
return err
}
if len(backbones) > 0 {
return errors.New("cannot delete device with active backbone connections")
}
fishbones, err := u.deviceDetailsRepo.GetFishbonesByDeviceID(id)
if err != nil {
return err
}
if len(fishbones) > 0 {
return errors.New("cannot delete device with active fishbone connections")
}
towers, err := u.deviceDetailsRepo.GetTowersByDeviceID(id)
if err != nil {
return err
}
if len(towers) > 0 {
return errors.New("cannot delete device with active tower connections")
}
return u.deviceDetailsRepo.Delete(id)
}
func (u *deviceDetailsUseCase) ValidatePortUsage(deviceID uuid.UUID, requiredPorts int) error {
return u.deviceDetailsRepo.ValidatePortAvailability(deviceID, requiredPorts)
}
func (u *deviceDetailsUseCase) RecalculatePortUsage(deviceID uuid.UUID) error {
return u.deviceDetailsRepo.UpdateDevicePortUsage(deviceID)
}

View File

@ -0,0 +1,414 @@
package usecase
import (
"errors"
"fmt"
"log"
"strconv"
"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 DeviceInspectionUseCase interface {
CreateInspection(userID uuid.UUID, inspection req.DeviceInspectionDTO) error
GetAllInspections(page, limit int) ([]res.DeviceInspectionResponse, int64, error)
GetUserInspections(userID uuid.UUID, page, limit int) ([]res.DeviceInspectionResponse, int64, error)
GetInspectionByID(id uuid.UUID) (res.DeviceInspectionDetailResponse, error)
UpdateInspection(id, userID uuid.UUID, inspection req.UpdateDeviceInspectionDTO, userRole string) error
ApproveInspection(id uuid.UUID, approval req.ApproveInspectionDTO) error
CheckInspectionOwnership(inspectionID, userID uuid.UUID) (bool, error) // Add this line
}
type deviceInspectionUseCase struct {
inspectionRepo repository.DeviceInspectionRepo
activityLogUC ActivityLogUseCase
geoService service.GeocodingService
validate *validator.Validate
}
func NewDeviceInspectionUseCase(inspectionRepo repository.DeviceInspectionRepo, activityLogUC ActivityLogUseCase, geoService service.GeocodingService) DeviceInspectionUseCase {
return &deviceInspectionUseCase{
inspectionRepo: inspectionRepo,
activityLogUC: activityLogUC,
geoService: geoService,
validate: validator.New(),
}
}
func (u *deviceInspectionUseCase) CheckInspectionOwnership(inspectionID, userID uuid.UUID) (bool, error) {
return u.inspectionRepo.CheckOwnership(inspectionID, userID)
}
func (u *deviceInspectionUseCase) CreateInspection(userID uuid.UUID, inspection req.DeviceInspectionDTO) error {
err := u.validate.Struct(inspection)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Check if device exists
deviceExists, err := u.inspectionRepo.CheckDeviceExists(inspection.DeviceID)
if err != nil {
return err
}
if !deviceExists {
return errors.New("device not found")
}
// Get device details
device, err := u.inspectionRepo.GetDeviceByID(inspection.DeviceID)
if err != nil {
return err
}
// Validate device type constraints
if device.DeviceType == "OTB" && inspection.BackboneID == nil {
return errors.New("backbone is required for OTB device type")
}
if device.DeviceType == "ODP" && inspection.FishboneID == nil {
return errors.New("fishbone is required for ODP device type")
}
// Validate backbone exists if provided
if inspection.BackboneID != nil {
backboneExists, err := u.inspectionRepo.CheckBackboneExists(*inspection.BackboneID)
if err != nil {
return err
}
if !backboneExists {
return errors.New("backbone not found")
}
}
// Validate fishbone exists if provided
if inspection.FishboneID != nil {
fishboneExists, err := u.inspectionRepo.CheckFishboneExists(*inspection.FishboneID)
if err != nil {
return err
}
if !fishboneExists {
return errors.New("fishbone not found")
}
}
// Validate tower exists if provided
if inspection.TowerID != nil {
towerExists, err := u.inspectionRepo.CheckTowerExists(*inspection.TowerID)
if err != nil {
return err
}
if !towerExists {
return errors.New("tower not found")
}
}
// Check port availability
portUsedInt, err := strconv.Atoi(inspection.PortUsed)
if err != nil {
return errors.New("invalid port_used value")
}
// Get current device port usage
devicePort, err := u.inspectionRepo.GetDevicePortByDeviceID(inspection.DeviceID)
if err != nil {
// If device port doesn't exist, create it
return fmt.Errorf("device port not found for device %s: %w", inspection.DeviceID, err)
}
// Check if adding this inspection would exceed port capacity
newPortUsed := devicePort.PortUsed + portUsedInt
if newPortUsed > device.PortAmount {
return fmt.Errorf("insufficient ports available. Device has %d ports, currently %d used, requested %d additional",
device.PortAmount, devicePort.PortUsed, portUsedInt)
}
// Calculate cable amount based on device type
cableAmount, err := u.inspectionRepo.CountCablesByDevice(inspection.DeviceID, string(device.DeviceType))
if err != nil {
return err
}
// Calculate port available
portAvailable := device.PortAmount - newPortUsed
// Get address from coordinates using OpenStreetMap
longitude := inspection.InspectionPlacement.Longitude
latitude := inspection.InspectionPlacement.Latitude
address, err := u.geoService.GetAddressFromCoordinates(latitude, longitude)
if err != nil {
log.Printf("Error getting address from coordinates (%.6f, %.6f): %v", latitude, longitude, err)
address = fmt.Sprintf("Coordinates: %.6f, %.6f", latitude, longitude) // Fallback to coordinates
}
// Create inspection
newInspection := entity.DeviceInspection{
ID: uuid.New(),
DeviceID: inspection.DeviceID,
BackboneID: inspection.BackboneID,
FishboneID: inspection.FishboneID,
TowerID: inspection.TowerID,
UserID: userID,
Status: inspection.Status,
PortUsed: inspection.PortUsed,
PortAvailable: strconv.Itoa(portAvailable),
CableAmount: cableAmount,
Description: inspection.Description,
ImageURL: inspection.ImageURL,
InspectionPlacement: address, // Store the full address from OpenStreetMap
Longitude: longitude, // Store longitude
Latitude: latitude, // Store latitude
InspectionApproval: "pending",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = u.inspectionRepo.Create(newInspection)
if err != nil {
return err
}
// Log activity
inspectionIDStr := newInspection.ID.String()
u.activityLogUC.LogActivity(
userID,
"CREATE",
"device_inspection",
&inspectionIDStr,
nil,
fmt.Sprintf("Created device inspection for device %s at %s", device.DeviceCode, address),
"",
"",
"",
)
return nil
}
func (u *deviceInspectionUseCase) UpdateInspection(id, userID uuid.UUID, inspection req.UpdateDeviceInspectionDTO, userRole string) error {
err := u.validate.Struct(inspection)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Get existing inspection
existingInspection, err := u.inspectionRepo.GetByID(id)
if err != nil {
return errors.New("inspection not found")
}
// Check if user owns this inspection (for teknisi)
if userRole == "Teknisi" && existingInspection.UserID != userID {
return errors.New("unauthorized: you can only update your own inspections")
}
updates := make(map[string]interface{})
if inspection.BackboneID != nil {
backboneExists, err := u.inspectionRepo.CheckBackboneExists(*inspection.BackboneID)
if err != nil {
return err
}
if !backboneExists {
return errors.New("backbone not found")
}
updates["backbone_id"] = *inspection.BackboneID
}
if inspection.FishboneID != nil {
fishboneExists, err := u.inspectionRepo.CheckFishboneExists(*inspection.FishboneID)
if err != nil {
return err
}
if !fishboneExists {
return errors.New("fishbone not found")
}
updates["fishbone_id"] = *inspection.FishboneID
}
if inspection.TowerID != nil {
towerExists, err := u.inspectionRepo.CheckTowerExists(*inspection.TowerID)
if err != nil {
return err
}
if !towerExists {
return errors.New("tower not found")
}
updates["tower_id"] = *inspection.TowerID
}
if inspection.Status != nil {
updates["status"] = *inspection.Status
}
if inspection.PortUsed != nil {
updates["port_used"] = *inspection.PortUsed
}
if inspection.Description != nil {
updates["description"] = *inspection.Description
}
if inspection.ImageURL != nil {
updates["image_url"] = *inspection.ImageURL
}
// Handle location update
if inspection.InspectionPlacement != nil {
longitude := inspection.InspectionPlacement.Longitude
latitude := inspection.InspectionPlacement.Latitude
// Get new address from coordinates
address, err := u.geoService.GetAddressFromCoordinates(latitude, longitude)
if err != nil {
log.Printf("Error getting address from coordinates (%.6f, %.6f): %v", latitude, longitude, err)
address = fmt.Sprintf("Coordinates: %.6f, %.6f", latitude, longitude) // Fallback to coordinates
}
updates["inspection_placement"] = address
updates["longitude"] = longitude
updates["latitude"] = latitude
}
if len(updates) == 0 {
return errors.New("no fields to update")
}
updates["updated_at"] = time.Now()
return u.inspectionRepo.Update(id, updates)
}
// Keep the rest of the methods unchanged...
func (u *deviceInspectionUseCase) GetAllInspections(page, limit int) ([]res.DeviceInspectionResponse, int64, error) {
offset := (page - 1) * limit
inspections, err := u.inspectionRepo.GetAll()
if err != nil {
return nil, 0, err
}
total, err := u.inspectionRepo.CountAll()
if err != nil {
return nil, 0, err
}
// Apply pagination manually since we need all data for processing
start := offset
end := offset + limit
if start > len(inspections) {
start = len(inspections)
}
if end > len(inspections) {
end = len(inspections)
}
paginatedInspections := inspections[start:end]
responses := helper.ConvertToDeviceInspectionResponses(paginatedInspections)
return responses, total, nil
}
func (u *deviceInspectionUseCase) GetUserInspections(userID uuid.UUID, page, limit int) ([]res.DeviceInspectionResponse, int64, error) {
offset := (page - 1) * limit
inspections, err := u.inspectionRepo.GetByUserID(userID, limit, offset)
if err != nil {
return nil, 0, err
}
total, err := u.inspectionRepo.CountByUserID(userID)
if err != nil {
return nil, 0, err
}
responses := helper.ConvertToDeviceInspectionResponses(inspections)
return responses, total, nil
}
func (u *deviceInspectionUseCase) GetInspectionByID(id uuid.UUID) (res.DeviceInspectionDetailResponse, error) {
inspection, err := u.inspectionRepo.GetByID(id)
if err != nil {
return res.DeviceInspectionDetailResponse{}, err
}
response := helper.ConvertToDeviceInspectionDetailResponse(inspection)
return response, nil
}
func (u *deviceInspectionUseCase) ApproveInspection(id uuid.UUID, approval req.ApproveInspectionDTO) error {
err := u.validate.Struct(approval)
if err != nil {
return fmt.Errorf("validation error: %w", err)
}
// Get inspection details
inspection, err := u.inspectionRepo.GetByID(id)
if err != nil {
return errors.New("inspection not found")
}
updates := map[string]interface{}{
"inspection_approval": approval.InspectionApproval,
"updated_at": time.Now(),
}
// Update inspection
err = u.inspectionRepo.Update(id, updates)
if err != nil {
return err
}
// If approved, update device port and device status
if approval.InspectionApproval == "approved" {
// Update device status
err = u.inspectionRepo.UpdateDeviceStatus(inspection.DeviceID, inspection.Status)
if err != nil {
return err
}
// Update device port usage
portUsedInt, _ := strconv.Atoi(inspection.PortUsed)
devicePort, err := u.inspectionRepo.GetDevicePortByDeviceID(inspection.DeviceID)
if err != nil {
return err
}
device, err := u.inspectionRepo.GetDeviceByID(inspection.DeviceID)
if err != nil {
return err
}
newPortUsed := devicePort.PortUsed + portUsedInt
newPortAvailable := device.PortAmount - newPortUsed
portUpdates := map[string]interface{}{
"port_used": newPortUsed,
"port_available": newPortAvailable,
"updated_at": time.Now(),
}
// For OTB devices, also update backbone_id
if device.DeviceType == "OTB" && inspection.BackboneID != nil {
portUpdates["backbone_id"] = *inspection.BackboneID
}
err = u.inspectionRepo.UpdateDevicePort(inspection.DeviceID, portUpdates)
if err != nil {
return err
}
}
return nil
}

View File

@ -43,21 +43,20 @@ func (u *deviceUseCase) CreateDevice(device req.DeviceDTO) error {
return fmt.Errorf("validation error: %w", err)
}
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),
Region: device.Region,
Province: device.Province,
City: device.City,
District: device.District,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
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, // Now nullable
City: device.City, // Now nullable
District: device.District, // Now nullable
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
return u.deviceRepo.Post(newDevice)
}
@ -113,9 +112,6 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e
if device.Status != nil {
updates["Status"] = *device.Status
}
if device.Region != nil {
updates["Region"] = *device.Region
}
if device.Province != nil {
updates["Province"] = *device.Province
}

View File

@ -1,16 +1,17 @@
package usecase
import (
"errors"
"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"
"errors"
"fmt"
"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"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
type FishboneUseCase interface {
@ -22,53 +23,58 @@ type FishboneUseCase interface {
GetFishboneStats() (map[string]interface{}, error)
}
type fishboneUsecase struct {
fishboneRepo repository.FishboneRepo
validate *validator.Validate
type fishboneUseCase struct {
fishboneRepo repository.FishboneRepo
backboneRepo repository.BackboneRepo
deviceDetailsRepo repository.DeviceDetailsRepo // Add this field
validate *validator.Validate
}
func NewFishboneUseCase(fishboneRepo repository.FishboneRepo) FishboneUseCase {
return &fishboneUsecase{
fishboneRepo: fishboneRepo,
validate: validator.New(),
func NewFishboneUseCase(fishboneRepo repository.FishboneRepo, backboneRepo repository.BackboneRepo, deviceDetailsRepo repository.DeviceDetailsRepo) FishboneUseCase {
return &fishboneUseCase{
fishboneRepo: fishboneRepo,
backboneRepo: backboneRepo,
deviceDetailsRepo: deviceDetailsRepo, // Initialize the field
validate: validator.New(),
}
}
func (u *fishboneUsecase) CreateFishbone(fishbone req.FishboneDTO) error {
func (u *fishboneUseCase) CreateFishbone(fishbone req.FishboneDTO) error {
err := u.validate.Struct(fishbone)
if err != nil {
return err
return fmt.Errorf("validation error: %w", err)
}
// Check if backbone exists
backboneExists, err := u.fishboneRepo.CheckBackboneExists(fishbone.BackboneID)
// Validate that both devices exist and are ODP type
startExists, err := u.deviceDetailsRepo.CheckDeviceExists(fishbone.DeviceStartID)
if err != nil {
return err
return fmt.Errorf("error checking start device: %w", err)
}
if !backboneExists {
return errors.New("backbone not found")
if !startExists {
return fmt.Errorf("start device does not exist")
}
// Check if devices exist
deviceStartExists, err := u.fishboneRepo.CheckDeviceExists(fishbone.DeviceStartID)
endExists, err := u.deviceDetailsRepo.CheckDeviceExists(fishbone.DeviceEndID)
if err != nil {
return err
return fmt.Errorf("error checking end device: %w", err)
}
if !deviceStartExists {
return errors.New("start device not found")
if !endExists {
return fmt.Errorf("end device does not exist")
}
deviceEndExists, err := u.fishboneRepo.CheckDeviceExists(fishbone.DeviceEndID)
if err != nil {
return err
// Validate port availability for ODP devices (each core needs 1 port)
if err := u.deviceDetailsRepo.ValidatePortAvailability(fishbone.DeviceStartID, fishbone.CoreAmount); err != nil {
return fmt.Errorf("start device: %w", err)
}
if !deviceEndExists {
return errors.New("end device not found")
if err := u.deviceDetailsRepo.ValidatePortAvailability(fishbone.DeviceEndID, fishbone.CoreAmount); err != nil {
return fmt.Errorf("end device: %w", err)
}
newFishbone := entity.Fishbone{
ID: uuid.New(),
FishboneCode: fishbone.FishboneCode,
FishboneCode: fishbone.FishboneCode,
BackboneID: fishbone.BackboneID,
DeviceStartID: fishbone.DeviceStartID,
DeviceEndID: fishbone.DeviceEndID,
@ -77,10 +83,20 @@ func (u *fishboneUsecase) CreateFishbone(fishbone req.FishboneDTO) error {
UpdatedAt: time.Now(),
}
return u.fishboneRepo.Post(newFishbone)
// Create fishbone
err = u.fishboneRepo.Post(newFishbone)
if err != nil {
return err
}
// Update port usage for both devices
u.deviceDetailsRepo.UpdateDevicePortUsage(fishbone.DeviceStartID)
u.deviceDetailsRepo.UpdateDevicePortUsage(fishbone.DeviceEndID)
return nil
}
func (u *fishboneUsecase) GetAllFishbone() ([]res.FishboneResponse, error) {
func (u *fishboneUseCase) GetAllFishbone() ([]res.FishboneResponse, error) {
fishbones, err := u.fishboneRepo.GetAll()
if err != nil {
return nil, err
@ -89,7 +105,7 @@ func (u *fishboneUsecase) GetAllFishbone() ([]res.FishboneResponse, error) {
return helper.ConvertToSimpleFishboneResponses(fishbones), nil
}
func (u *fishboneUsecase) GetByID(id uuid.UUID) (res.FishboneDetailResponse, error) {
func (u *fishboneUseCase) GetByID(id uuid.UUID) (res.FishboneDetailResponse, error) {
fishbone, err := u.fishboneRepo.GetByIDWithRelations(id)
if err != nil {
return res.FishboneDetailResponse{}, err
@ -99,73 +115,113 @@ func (u *fishboneUsecase) GetByID(id uuid.UUID) (res.FishboneDetailResponse, err
return helper.ConvertToFishboneDetailResponse(fishbone), nil
}
func (u *fishboneUsecase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error {
func (u *fishboneUseCase) UpdateFishbone(id uuid.UUID, fishbone req.UpdateFishboneDTO) error {
err := u.validate.Struct(fishbone)
if err != nil {
return err
return fmt.Errorf("validation error: %w", err)
}
// Check if fishbone exists
exists, err := u.fishboneRepo.CheckFishboneExists(id)
// Get original fishbone for comparison
originalFishbone, err := u.fishboneRepo.GetByID(id)
if err != nil {
return err
}
if !exists {
return errors.New("fishbone not found")
}
updates := make(map[string]interface{})
if fishbone.FishboneCode != nil {
updates["fishbone_code"] = *fishbone.FishboneCode
}
if fishbone.BackboneID != nil {
// Validate backbone exists
backboneExists, err := u.fishboneRepo.CheckBackboneExists(*fishbone.BackboneID)
if err != nil {
return err
// Track devices that need port recalculation
devicesToUpdate := make(map[uuid.UUID]bool)
devicesToUpdate[originalFishbone.DeviceStartID] = true
devicesToUpdate[originalFishbone.DeviceEndID] = true
// If core amount is changed, validate port availability
if fishbone.CoreAmount != nil && *fishbone.CoreAmount != originalFishbone.CoreAmount {
coreDiff := *fishbone.CoreAmount - originalFishbone.CoreAmount
if coreDiff > 0 {
// Increasing cores - check port availability
if err := u.deviceDetailsRepo.ValidatePortAvailability(originalFishbone.DeviceStartID, coreDiff); err != nil {
return fmt.Errorf("start device: %w", err)
}
if err := u.deviceDetailsRepo.ValidatePortAvailability(originalFishbone.DeviceEndID, coreDiff); err != nil {
return fmt.Errorf("end device: %w", err)
}
}
if !backboneExists {
return errors.New("backbone not found")
}
updates["bb_id"] = *fishbone.BackboneID
}
if fishbone.DeviceStartID != nil {
// Validate device exists
deviceExists, err := u.fishboneRepo.CheckDeviceExists(*fishbone.DeviceStartID)
if err != nil {
return err
}
if !deviceExists {
return errors.New("start device not found")
}
updates["dev_start_id"] = *fishbone.DeviceStartID
}
if fishbone.DeviceEndID != nil {
// Validate device exists
deviceExists, err := u.fishboneRepo.CheckDeviceExists(*fishbone.DeviceEndID)
if err != nil {
return err
}
if !deviceExists {
return errors.New("end device not found")
}
updates["dev_end_id"] = *fishbone.DeviceEndID
}
if fishbone.CoreAmount != nil {
updates["core_amount"] = *fishbone.CoreAmount
}
if fishbone.DeviceStartID != nil {
exists, err := u.deviceDetailsRepo.CheckDeviceExists(*fishbone.DeviceStartID)
if err != nil {
return fmt.Errorf("error checking new start device: %w", err)
}
if !exists {
return fmt.Errorf("new start device does not exist")
}
coreAmount := originalFishbone.CoreAmount
if fishbone.CoreAmount != nil {
coreAmount = *fishbone.CoreAmount
}
if err := u.deviceDetailsRepo.ValidatePortAvailability(*fishbone.DeviceStartID, coreAmount); err != nil {
return fmt.Errorf("new start device: %w", err)
}
updates["dev_start_id"] = *fishbone.DeviceStartID
devicesToUpdate[*fishbone.DeviceStartID] = true
}
if fishbone.DeviceEndID != nil {
exists, err := u.deviceDetailsRepo.CheckDeviceExists(*fishbone.DeviceEndID)
if err != nil {
return fmt.Errorf("error checking new end device: %w", err)
}
if !exists {
return fmt.Errorf("new end device does not exist")
}
coreAmount := originalFishbone.CoreAmount
if fishbone.CoreAmount != nil {
coreAmount = *fishbone.CoreAmount
}
if err := u.deviceDetailsRepo.ValidatePortAvailability(*fishbone.DeviceEndID, coreAmount); err != nil {
return fmt.Errorf("new end device: %w", err)
}
updates["dev_end_id"] = *fishbone.DeviceEndID
devicesToUpdate[*fishbone.DeviceEndID] = true
}
if fishbone.BackboneID != nil {
updates["backbone_id"] = *fishbone.BackboneID
}
if fishbone.FishboneCode != nil {
updates["fishbone_code"] = *fishbone.FishboneCode
}
if len(updates) == 0 {
return errors.New("no fields to update")
return fmt.Errorf("no fields to update")
}
updates["updated_at"] = time.Now()
return u.fishboneRepo.Update(id, updates)
// Update fishbone
err = u.fishboneRepo.Update(id, updates)
if err != nil {
return err
}
// Recalculate port usage for all affected devices
for deviceID := range devicesToUpdate {
u.deviceDetailsRepo.UpdateDevicePortUsage(deviceID)
}
return nil
}
func (u *fishboneUsecase) DeleteFishbone(id uuid.UUID) error {
func (u *fishboneUseCase) DeleteFishbone(id uuid.UUID) error {
// Check if fishbone exists
exists, err := u.fishboneRepo.CheckFishboneExists(id)
if err != nil {
@ -178,7 +234,7 @@ func (u *fishboneUsecase) DeleteFishbone(id uuid.UUID) error {
return u.fishboneRepo.Delete(id)
}
func (u *fishboneUsecase) GetFishboneStats() (map[string]interface{}, error) {
func (u *fishboneUseCase) GetFishboneStats() (map[string]interface{}, error) {
fishbones, err := u.fishboneRepo.GetAll()
if err != nil {
return nil, err

106
usecase/nearest_device.go Normal file
View File

@ -0,0 +1,106 @@
package usecase
import (
"math"
"users_management/m/model/dto/req"
"users_management/m/model/dto/res"
"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 NearestDeviceUseCase interface {
GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error)
GetNearestDeviceByID(id uuid.UUID, userLat, userLng float64) (res.NearestDeviceDetailResponse, error)
}
type nearestDeviceUseCase struct {
nearestDeviceRepo repository.NearestDeviceRepo
geocoder service.GeocodingService
validate *validator.Validate
}
func NewNearestDeviceUseCase(nearestDeviceRepo repository.NearestDeviceRepo, geocoder service.GeocodingService) NearestDeviceUseCase {
return &nearestDeviceUseCase{
nearestDeviceRepo: nearestDeviceRepo,
geocoder: geocoder,
validate: validator.New(),
}
}
func (u *nearestDeviceUseCase) GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error) {
err := u.validate.Struct(request)
if err != nil {
return nil, err
}
// Set defaults
radius := request.Radius
if radius == 0 {
radius = 5.0 // Default 5km
}
limit := request.Limit
if limit == 0 {
limit = 10 // Default 10 devices
}
devices, err := u.nearestDeviceRepo.GetNearestDevices(
request.Longitude,
request.Latitude,
radius,
limit,
request.Province,
request.City,
request.District,
)
if err != nil {
return nil, err
}
// Updated function call - removed userLat, userLng parameters since distance is already calculated
responses, err := helper.ConvertToNearestDeviceResponses(devices, u.nearestDeviceRepo, u.geocoder)
if err != nil {
return nil, err
}
return responses, nil
}
func (u *nearestDeviceUseCase) GetNearestDeviceByID(id uuid.UUID, userLat, userLng float64) (res.NearestDeviceDetailResponse, error) {
device, err := u.nearestDeviceRepo.GetDeviceByIDWithConnections(id)
if err != nil {
return res.NearestDeviceDetailResponse{}, err
}
// Calculate distance
distance := calculateDistance(userLat, userLng, device.Latitude, device.Longitude)
response, err := helper.ConvertToNearestDeviceDetailResponse(device, distance, u.nearestDeviceRepo, u.geocoder)
if err != nil {
return res.NearestDeviceDetailResponse{}, err
}
return response, nil
}
// calculateDistance calculates the distance between two coordinates using Haversine formula
func calculateDistance(lat1, lng1, lat2, lng2 float64) float64 {
const earthRadius = 6371 // Earth's radius in kilometers
lat1Rad := lat1 * math.Pi / 180
lng1Rad := lng1 * math.Pi / 180
lat2Rad := lat2 * math.Pi / 180
lng2Rad := lng2 * math.Pi / 180
dlat := lat2Rad - lat1Rad
dlng := lng2Rad - lng1Rad
a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(lat1Rad)*math.Cos(lat2Rad)*math.Sin(dlng/2)*math.Sin(dlng/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return earthRadius * c
}

View File

@ -1,6 +1,7 @@
package helper
import (
"fmt"
"log"
"users_management/m/model/dto/res"
"users_management/m/model/entity"
@ -14,6 +15,7 @@ func ConvertToDeviceTypeResponse(devices []entity.Device) []res.DeviceTypeRespon
ID: devices.ID,
DeviceType: string(devices.DeviceType),
DeviceCode: devices.DeviceCode,
}
responses = append(responses, deviceResp)
}
@ -22,41 +24,38 @@ func ConvertToDeviceTypeResponse(devices []entity.Device) []res.DeviceTypeRespon
}
func ConvertToDeviceResponse (devices []entity.Device, geocoder service.GeocodingService) ([]res.DeviceResponse, error) {
var responses []res.DeviceResponse
for _, devices := range devices {
var address string
func ConvertToDeviceResponse(devices []entity.Device, geocoder service.GeocodingService) ([]res.DeviceResponse, error) {
var responses []res.DeviceResponse
if geocoder != nil {
generatedAddress, err := geocoder.GetAddressFromCoordinates(devices.Latitude, devices.Longitude)
if err != nil {
// Log specific geocoding error
log.Printf("Geocoding error for device %s: %v", devices.DeviceCode, err)
} else {
address = generatedAddress
}
} else{
log.Println("WARNING: Geocoder is nil")
}
for _, device := range devices {
// Get address from coordinates
address, err := geocoder.GetAddressFromCoordinates(device.Latitude, device.Longitude)
log.Printf("Geocoding for device %s: %.6f, %.6f -> %s", device.DeviceCode, device.Latitude, device.Longitude, address)
if err != nil {
address = fmt.Sprintf("Coordinates: %.6f, %.6f", device.Latitude, device.Longitude)
}
deviceResp := res.DeviceResponse{
ID: devices.ID,
DeviceCode: devices.DeviceCode,
DeviceType: string(devices.DeviceType),
Longitude: devices.Longitude,
Latitude: devices.Latitude,
Address: address,
PortAmount: devices.PortAmount,
Status: string(devices.Status),
CreatedAt: devices.CreatedAt,
UpdatedAt: devices.UpdatedAt,
}
responses = append(responses, deviceResp)
}
response := res.DeviceResponse{
ID: device.ID,
DeviceCode: device.DeviceCode,
DeviceType: string(device.DeviceType),
Longitude: device.Longitude,
Latitude: device.Latitude,
Address: address,
PortAmount: device.PortAmount,
Status: string(device.Status),
Province: device.Province, // Nullable field
City: device.City, // Nullable field
District: device.District, // Nullable field
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
}
return responses, nil
responses = append(responses, response)
}
return responses, nil
}
func ConvertToDeviceResponseId (devices entity.Device, geocoder service.GeocodingService) (res.DeviceResponse, error) {
var address string
@ -79,6 +78,9 @@ func ConvertToDeviceResponseId (devices entity.Device, geocoder service.Geocodin
Longitude: devices.Longitude,
Latitude: devices.Latitude,
Address: address,
Province: devices.Province,
City: devices.City,
District: devices.District,
PortAmount: devices.PortAmount,
Status: string(devices.Status),
CreatedAt: devices.CreatedAt,

View File

@ -0,0 +1,125 @@
package helper
import (
"fmt"
"log"
"users_management/m/model/dto/res"
"users_management/m/model/entity"
"users_management/m/utils/service"
)
func ConvertToDeviceDetailsResponses(devices []entity.DeviceDetails, geocoder service.GeocodingService) ([]res.DeviceDetailsResponse, error) {
var responses []res.DeviceDetailsResponse
for _, device := range devices {
response, err := ConvertToDeviceDetailsResponse(device, geocoder)
if err != nil {
return nil, err
}
responses = append(responses, response)
}
return responses, nil
}
func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder service.GeocodingService) (res.DeviceDetailsResponse, error) {
// Get address
address := ""
if geocoder != nil {
addr, err := geocoder.GetAddressFromCoordinates(device.Latitude, device.Longitude)
if err != nil {
log.Printf("Geocoding error for device %s: %v", device.DeviceCode, err)
address = fmt.Sprintf("Coordinates: %.6f, %.6f", device.Latitude, device.Longitude)
} else {
address = addr
}
}
// Get all backbones connected to this device
allBackbones := device.GetAllBackbones()
backboneInfos := make([]res.BackboneConnectionInfo, 0)
for _, backbone := range allBackbones {
isStartDevice := backbone.DeviceStartID == device.ID
connectedTo := ""
if isStartDevice && backbone.DeviceEnd.DeviceCode != "" {
connectedTo = backbone.DeviceEnd.DeviceCode
} else if !isStartDevice && backbone.DeviceStart.DeviceCode != "" {
connectedTo = backbone.DeviceStart.DeviceCode
}
info := res.BackboneConnectionInfo{
ID: backbone.ID,
BackboneCode: backbone.BackboneCode,
CoreAmount: backbone.CoreAmount,
IsStartDevice: isStartDevice,
ConnectedTo: connectedTo,
}
backboneInfos = append(backboneInfos, info)
}
// Get all fishbones connected to this device
allFishbones := device.GetAllFishbones()
fishboneInfos := make([]res.FishboneConnectionInfo, 0)
for _, fishbone := range allFishbones {
isStartDevice := fishbone.DeviceStartID == device.ID
connectedTo := ""
if isStartDevice && fishbone.DeviceEnd.DeviceCode != "" {
connectedTo = fishbone.DeviceEnd.DeviceCode
} else if !isStartDevice && fishbone.DeviceStart.DeviceCode != "" {
connectedTo = fishbone.DeviceStart.DeviceCode
}
info := res.FishboneConnectionInfo{
ID: fishbone.ID,
FishboneCode: fishbone.FishboneCode,
CoreAmount: fishbone.CoreAmount,
BackboneCode: fishbone.Backbone.BackboneCode,
IsStartDevice: isStartDevice,
ConnectedTo: connectedTo,
}
fishboneInfos = append(fishboneInfos, info)
}
// Convert tower connections
towerInfos := make([]res.TowerConnectionDetail, 0)
for _, tower := range device.Towers {
distance := calculateDistance(device.Latitude, device.Longitude, tower.Latitude, tower.Longitude)
info := res.TowerConnectionDetail{
ID: tower.ID,
TowerCode: tower.TowerCode,
Distance: distance,
ImageURL: &tower.ImageURL,
}
towerInfos = append(towerInfos, info)
}
response := res.DeviceDetailsResponse{
ID: device.ID,
DeviceCode: device.DeviceCode,
DeviceType: string(device.DeviceType),
Address: address,
Longitude: device.Longitude,
Latitude: device.Latitude,
Status: string(device.Status),
PortAmount: device.PortAmount,
PortUsed: device.DevicePort.PortUsed,
PortAvailable: device.DevicePort.PortAvailable,
Region: device.Region,
Province: device.Province,
City: device.City,
District: device.District,
ImageURL: device.ImageURL,
Backbones: backboneInfos,
Fishbones: fishboneInfos,
Towers: towerInfos,
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
}
return response, nil
}
// ... rest of helper functions remain the same

View File

@ -0,0 +1,103 @@
package helper
import (
"bytes"
"users_management/m/model/dto/res"
"users_management/m/model/entity"
)
// IsEmptyUUID checks if a UUID is empty (all zeros)
func IsEmptyUUID(id [16]byte) bool {
return bytes.Equal(id[:], make([]byte, 16))
}
func ConvertToDeviceInspectionResponses(inspections []entity.DeviceInspection) []res.DeviceInspectionResponse {
var responses []res.DeviceInspectionResponse
for _, inspection := range inspections {
response := res.DeviceInspectionResponse{
ID: inspection.ID,
DeviceCode: inspection.Device.DeviceCode,
DeviceType: string(inspection.Device.DeviceType),
UserName: inspection.User.Name,
Status: inspection.Status,
PortUsed: inspection.PortUsed,
PortAvailable: inspection.PortAvailable,
CableAmount: inspection.CableAmount,
Description: inspection.Description,
ImageURL: inspection.ImageURL,
InspectionPlacement: inspection.InspectionPlacement, // Full address
Longitude: inspection.Longitude,
Latitude: inspection.Latitude,
InspectionApproval: inspection.InspectionApproval,
CreatedAt: inspection.CreatedAt,
UpdatedAt: inspection.UpdatedAt,
}
if inspection.Backbone != nil && !IsEmptyUUID(inspection.Backbone.ID) {
response.BackboneCode = &inspection.Backbone.BackboneCode
}
if inspection.Fishbone != nil && !IsEmptyUUID(inspection.Fishbone.ID) {
response.FishboneCode = &inspection.Fishbone.FishboneCode
}
if inspection.Tower != nil && !IsEmptyUUID(inspection.Tower.ID) {
response.TowerCode = &inspection.Tower.TowerCode
}
responses = append(responses, response)
}
return responses
}
func ConvertToDeviceInspectionDetailResponse(inspection entity.DeviceInspection) res.DeviceInspectionDetailResponse {
response := res.DeviceInspectionDetailResponse{
ID: inspection.ID,
Device: res.DeviceInfo{
ID: inspection.Device.ID,
DeviceCode: inspection.Device.DeviceCode,
DeviceType: string(inspection.Device.DeviceType),
},
User: res.UserInfo{
ID: inspection.User.ID,
Name: inspection.User.Name,
Username: inspection.User.Username,
},
Status: inspection.Status,
PortUsed: inspection.PortUsed,
PortAvailable: inspection.PortAvailable,
CableAmount: inspection.CableAmount,
Description: inspection.Description,
ImageURL: inspection.ImageURL,
InspectionPlacement: inspection.InspectionPlacement, // Full address
Longitude: inspection.Longitude,
Latitude: inspection.Latitude,
InspectionApproval: inspection.InspectionApproval,
CreatedAt: inspection.CreatedAt,
UpdatedAt: inspection.UpdatedAt,
}
if inspection.Backbone != nil && !IsEmptyUUID(inspection.Backbone.ID) {
response.Backbone = &res.BackboneInfo{
ID: inspection.Backbone.ID,
BackboneCode: inspection.Backbone.BackboneCode,
}
}
if inspection.Fishbone != nil && !IsEmptyUUID(inspection.Fishbone.ID) {
response.Fishbone = &res.FishboneInfo{
ID: inspection.Fishbone.ID,
FishboneCode: inspection.Fishbone.FishboneCode,
}
}
if inspection.Tower != nil && !IsEmptyUUID(inspection.Tower.ID) {
response.Tower = &res.TowerInfo{
ID: inspection.Tower.ID,
TowerCode: inspection.Tower.TowerCode,
}
}
return response
}

View File

@ -0,0 +1,212 @@
package helper
import (
"fmt"
"log"
"math"
"users_management/m/model/dto/res"
"users_management/m/model/entity"
"users_management/m/repository"
"users_management/m/utils/service"
"github.com/google/uuid"
)
func ConvertToNearestDeviceResponses(devices []entity.DeviceWithDistance, repo repository.NearestDeviceRepo, geocoder service.GeocodingService) ([]res.NearestDeviceResponse, error) {
var responses []res.NearestDeviceResponse
for _, device := range devices {
// Get address
address := ""
if geocoder != nil {
addr, err := geocoder.GetAddressFromCoordinates(device.Latitude, device.Longitude)
if err != nil {
log.Printf("Geocoding error for device %s: %v", device.DeviceCode, err)
address = fmt.Sprintf("Coordinates: %.6f, %.6f", device.Latitude, device.Longitude)
} else {
address = addr
}
}
// Count connections
backboneCount, fishboneCount, towerCount, err := repo.CountConnectionsByDeviceID(device.ID)
if err != nil {
log.Printf("Error counting connections for device %s: %v", device.DeviceCode, err)
backboneCount, fishboneCount, towerCount = 0, 0, 0
}
response := res.NearestDeviceResponse{
ID: device.ID,
DeviceCode: device.DeviceCode,
DeviceType: string(device.DeviceType),
Distance: device.Distance, // Use the calculated distance from query
Address: address,
Longitude: device.Longitude,
Latitude: device.Latitude,
Status: string(device.Status),
PortAmount: device.PortAmount,
Region: device.Region,
Province: device.Province,
City: device.City,
District: device.District,
BackboneCount: backboneCount,
FishboneCount: fishboneCount,
TowerCount: towerCount,
CreatedAt: device.CreatedAt,
}
responses = append(responses, response)
}
return responses, nil
}
func ConvertToNearestDeviceDetailResponse(device entity.Device, distance float64, repo repository.NearestDeviceRepo, geocoder service.GeocodingService) (res.NearestDeviceDetailResponse, error) {
// Get address
address := ""
if geocoder != nil {
addr, err := geocoder.GetAddressFromCoordinates(device.Latitude, device.Longitude)
if err != nil {
log.Printf("Geocoding error for device %s: %v", device.DeviceCode, err)
address = fmt.Sprintf("Coordinates: %.6f, %.6f", device.Latitude, device.Longitude)
} else {
address = addr
}
}
// Get detailed connection information
backbones, err := repo.GetBackbonesByDeviceID(device.ID)
if err != nil {
return res.NearestDeviceDetailResponse{}, err
}
fishbones, err := repo.GetFishbonesByDeviceID(device.ID)
if err != nil {
return res.NearestDeviceDetailResponse{}, err
}
towers, err := repo.GetTowersByDeviceID(device.ID)
if err != nil {
return res.NearestDeviceDetailResponse{}, err
}
// Convert to response format
backboneInfos := convertToBackboneConnectionInfos(backbones, device.ID)
fishboneInfos := convertToFishboneConnectionInfos(fishbones, device.ID)
towerInfos := convertToTowerConnectionInfos(towers, device.Latitude, device.Longitude)
response := res.NearestDeviceDetailResponse{
ID: device.ID,
DeviceCode: device.DeviceCode,
DeviceType: string(device.DeviceType),
Distance: distance,
Address: address,
Longitude: device.Longitude,
Latitude: device.Latitude,
Status: string(device.Status),
PortAmount: device.PortAmount,
Province: device.Province,
City: device.City,
District: device.District,
ImageURL: device.ImageURL,
Backbones: backboneInfos,
Fishbones: fishboneInfos,
Towers: towerInfos,
CreatedAt: device.CreatedAt,
UpdatedAt: device.UpdatedAt,
}
return response, nil
}
func convertToBackboneConnectionInfos(backbones []entity.Backbone, deviceID uuid.UUID) []res.BackboneConnectionInfo {
var infos []res.BackboneConnectionInfo
for _, backbone := range backbones {
isStartDevice := backbone.DeviceStartID == deviceID
connectedTo := ""
if isStartDevice && backbone.DeviceEnd.DeviceCode != "" {
connectedTo = backbone.DeviceEnd.DeviceCode
} else if !isStartDevice && backbone.DeviceStart.DeviceCode != "" {
connectedTo = backbone.DeviceStart.DeviceCode
}
info := res.BackboneConnectionInfo{
ID: backbone.ID,
BackboneCode: backbone.BackboneCode,
CoreAmount: backbone.CoreAmount,
IsStartDevice: isStartDevice,
ConnectedTo: connectedTo,
}
infos = append(infos, info)
}
return infos
}
func convertToFishboneConnectionInfos(fishbones []entity.Fishbone, deviceID uuid.UUID) []res.FishboneConnectionInfo {
var infos []res.FishboneConnectionInfo
for _, fishbone := range fishbones {
isStartDevice := fishbone.DeviceStartID == deviceID
connectedTo := ""
if isStartDevice && fishbone.DeviceEnd.DeviceCode != "" {
connectedTo = fishbone.DeviceEnd.DeviceCode
} else if !isStartDevice && fishbone.DeviceStart.DeviceCode != "" {
connectedTo = fishbone.DeviceStart.DeviceCode
}
info := res.FishboneConnectionInfo{
ID: fishbone.ID,
FishboneCode: fishbone.FishboneCode,
CoreAmount: fishbone.CoreAmount,
BackboneCode: fishbone.Backbone.BackboneCode,
IsStartDevice: isStartDevice,
ConnectedTo: connectedTo,
}
infos = append(infos, info)
}
return infos
}
func convertToTowerConnectionInfos(towers []entity.Tower, deviceLat, deviceLng float64) []res.TowerConnectionInfo {
var infos []res.TowerConnectionInfo
for _, tower := range towers {
// Calculate distance from device to tower
distance := calculateDistance(deviceLat, deviceLng, tower.Latitude, tower.Longitude)
info := res.TowerConnectionInfo{
ID: tower.ID,
TowerCode: tower.TowerCode,
Distance: distance,
ImageURL: &tower.ImageURL,
}
infos = append(infos, info)
}
return infos
}
func calculateDistance(lat1, lng1, lat2, lng2 float64) float64 {
const earthRadius = 6371 // Earth's radius in kilometers
lat1Rad := lat1 * math.Pi / 180
lng1Rad := lng1 * math.Pi / 180
lat2Rad := lat2 * math.Pi / 180
lng2Rad := lng2 * math.Pi / 180
dlat := lat2Rad - lat1Rad
dlng := lng2Rad - lng1Rad
a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(lat1Rad)*math.Cos(lat2Rad)*math.Sin(dlng/2)*math.Sin(dlng/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return earthRadius * c
}

View File

@ -29,7 +29,7 @@ func ConvertToTowerResponses(towers []entity.Tower, geocoder service.GeocodingSe
towerResp := res.TowerResponse{
ID: tower.ID,
DeviceCode: tower.Device.DeviceCode,
TowerCode: tower.TowerCode,
TowerCode: &tower.TowerCode,
Longitude: tower.Longitude,
Latitude: tower.Latitude,
Address: address,
@ -59,7 +59,7 @@ func ConvertToTowerIDResponses(tower entity.Tower, geocoder service.GeocodingSer
towerResp := res.TowerResponse{
ID: tower.ID,
DeviceCode: tower.Device.DeviceCode,
TowerCode: tower.TowerCode,
TowerCode: &tower.TowerCode,
Longitude: tower.Longitude,
Latitude: tower.Latitude,
Address: address,