diff --git a/config/database.go b/config/database.go index 9bb56e9..e9e920c 100644 --- a/config/database.go +++ b/config/database.go @@ -42,6 +42,7 @@ func ConnectDatabase() { // ==============main feature============== // =>user preparation<= &model.User{}, + &model.Collector{}, &model.Role{}, &model.UserPin{}, &model.Address{}, diff --git a/dto/address_dto.go b/dto/address_dto.go index f33016e..b689ad5 100644 --- a/dto/address_dto.go +++ b/dto/address_dto.go @@ -3,29 +3,29 @@ package dto import "strings" type AddressResponseDTO struct { - UserID string `json:"user_id"` - ID string `json:"address_id"` - Province string `json:"province"` - Regency string `json:"regency"` - District string `json:"district"` - Village string `json:"village"` - PostalCode string `json:"postalCode"` - Detail string `json:"detail"` - Latitude string `json:"latitude"` - Longitude string `json:"longitude"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` + UserID string `json:"user_id"` + ID string `json:"address_id"` + Province string `json:"province"` + Regency string `json:"regency"` + District string `json:"district"` + Village string `json:"village"` + PostalCode string `json:"postalCode"` + Detail string `json:"detail"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` } type CreateAddressDTO struct { - Province string `json:"province_id"` - Regency string `json:"regency_id"` - District string `json:"district_id"` - Village string `json:"village_id"` - PostalCode string `json:"postalCode"` - Detail string `json:"detail"` - Latitude string `json:"latitude"` - Longitude string `json:"longitude"` + Province string `json:"province_id"` + Regency string `json:"regency_id"` + District string `json:"district_id"` + Village string `json:"village_id"` + PostalCode string `json:"postalCode"` + Detail string `json:"detail"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` } func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) { @@ -34,28 +34,35 @@ func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) { if strings.TrimSpace(r.Province) == "" { errors["province_id"] = append(errors["province_id"], "Province ID is required") } + if strings.TrimSpace(r.Regency) == "" { errors["regency_id"] = append(errors["regency_id"], "Regency ID is required") } + if strings.TrimSpace(r.District) == "" { errors["district_id"] = append(errors["district_id"], "District ID is required") } + if strings.TrimSpace(r.Village) == "" { errors["village_id"] = append(errors["village_id"], "Village ID is required") } + if strings.TrimSpace(r.PostalCode) == "" { - errors["postalCode"] = append(errors["village_id"], "PostalCode ID is required") + errors["postalCode"] = append(errors["postalCode"], "PostalCode is required") } else if len(r.PostalCode) < 5 { - errors["postalCode"] = append(errors["postalCode"], "kode pos belum sesuai") + errors["postalCode"] = append(errors["postalCode"], "PostalCode must be at least 5 characters") } + if strings.TrimSpace(r.Detail) == "" { errors["detail"] = append(errors["detail"], "Detail address is required") } - if strings.TrimSpace(r.Latitude) == "" { - errors["latitude"] = append(errors["latitude"], "Geographic coordinates are required") + + if r.Latitude == 0 { + errors["latitude"] = append(errors["latitude"], "Latitude is required") } - if strings.TrimSpace(r.Longitude) == "" { - errors["longitude"] = append(errors["longitude"], "Geographic coordinates are required") + + if r.Longitude == 0 { + errors["longitude"] = append(errors["longitude"], "Longitude is required") } if len(errors) > 0 { diff --git a/dto/collector_dto.go b/dto/collector_dto.go new file mode 100644 index 0000000..c1f96fb --- /dev/null +++ b/dto/collector_dto.go @@ -0,0 +1,31 @@ +package dto + +import "strings" + +type RequestCollectorDTO struct { + UserId string `json:"user_id"` + AddressId string `json:"address_id"` +} + +type ResponseCollectorDTO struct { + ID string `json:"collector_id"` + UserId string `json:"user_id"` + AddressId string `json:"address_id"` + JobStatus string `json:"job_status"` + Rating float32 `json:"rating"` + // CreatedAt string `json:"createdAt"` + // UpdatedAt string `json:"updatedAt"` +} + +func (r *RequestCollectorDTO) ValidateRequestColector() (map[string][]string, bool) { + errors := make(map[string][]string) + + if strings.TrimSpace(r.AddressId) == "" { + errors["address_id"] = append(errors["address_id"], "address_id harus diisi") + } + if len(errors) > 0 { + return errors, false + } + + return nil, true +} diff --git a/dto/requestpickup_dto.go b/dto/requestpickup_dto.go index 73764bf..643f95c 100644 --- a/dto/requestpickup_dto.go +++ b/dto/requestpickup_dto.go @@ -9,6 +9,7 @@ type RequestPickup struct { RequestItems []RequestPickupItem `json:"request_items"` EvidenceImage string `json:"evidence_image"` AddressID string `json:"address_id"` + RequestMethod string `json:"request_method"` } type RequestPickupItem struct { @@ -17,18 +18,20 @@ type RequestPickupItem struct { } type ResponseRequestPickup struct { - ID string `json:"id"` - UserId string `json:"user_id"` - AddressID string `json:"address_id"` - EvidenceImage string `json:"evidence_image"` - StatusPickup string `json:"status_pickup"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - RequestItems []ResponseRequestPickupItem `json:"request_items"` + ID string `json:"id,omitempty"` + UserId string `json:"user_id,omitempty"` + AddressID string `json:"address_id,omitempty"` + EvidenceImage string `json:"evidence_image,omitempty"` + StatusPickup string `json:"status_pickup,omitempty"` + CollectorID string `json:"collectorid,omitempty"` + ConfirmedByCollectorAt string `json:"confirmedat,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + RequestItems []ResponseRequestPickupItem `json:"request_items,omitempty"` } type ResponseRequestPickupItem struct { - ID string `json:"id"` + ID string `json:"id"` // TrashCategoryID string `json:"trash_category_id"` TrashCategoryName string `json:"trash_category_name"` EstimatedAmount float64 `json:"estimated_amount"` diff --git a/go.mod b/go.mod index 86771ff..687dde0 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( ) require ( - github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 // indirect golang.org/x/term v0.30.0 // indirect rsc.io/qr v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 1b59849..83b4326 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w= -github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= diff --git a/internal/handler/collector_handler.go b/internal/handler/collector_handler.go new file mode 100644 index 0000000..cb2d738 --- /dev/null +++ b/internal/handler/collector_handler.go @@ -0,0 +1,36 @@ +package handler + +import ( + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type CollectorHandler struct { + service services.CollectorService +} + +func NewCollectorHandler(service services.CollectorService) *CollectorHandler { + return &CollectorHandler{service} +} + +func (h *CollectorHandler) ConfirmRequestPickup(c *fiber.Ctx) error { + + collectorId, ok := c.Locals("userID").(string) + if !ok || collectorId == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") + } + + requestPickupId := c.Params("id") + if requestPickupId == "" { + return utils.ErrorResponse(c, "RequestPickup ID is required") + } + + req, err := h.service.ConfirmRequestPickup(requestPickupId, collectorId) + if err != nil { + return utils.ErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, req, "Request pickup confirmed successfully") +} diff --git a/internal/handler/requestpickup_handler.go b/internal/handler/requestpickup_handler.go index 4ce19fe..7558093 100644 --- a/internal/handler/requestpickup_handler.go +++ b/internal/handler/requestpickup_handler.go @@ -53,49 +53,32 @@ func (h *RequestPickupHandler) GetRequestPickupByID(c *fiber.Ctx) error { return utils.SuccessResponse(c, response, "Request pickup retrieved successfully") } -func (h *RequestPickupHandler) GetAllRequestPickups(c *fiber.Ctx) error { +// func (h *RequestPickupHandler) GetAutomaticRequestByUser(c *fiber.Ctx) error { - response, err := h.service.GetAllRequestPickups() +// collectorId, ok := c.Locals("userID").(string) +// if !ok || collectorId == "" { +// return utils.ErrorResponse(c, "Unauthorized: User session not found") +// } + +// requestPickups, err := h.service.GetAllAutomaticRequestPickup(collectorId) +// if err != nil { + +// return utils.ErrorResponse(c, err.Error()) +// } + +// return utils.SuccessResponse(c, requestPickups, "Request pickups fetched successfully") +// } + +func (h *RequestPickupHandler) GetRequestPickups(c *fiber.Ctx) error { + // Get userID from Locals + collectorId := c.Locals("userID").(string) + + // Call service layer to get the request pickups + requests, err := h.service.GetRequestPickupsForCollector(collectorId) if err != nil { - return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error fetching all request pickups: %v", err)) + return utils.ErrorResponse(c, err.Error()) } - return utils.SuccessResponse(c, response, "All request pickups retrieved successfully") -} - -func (h *RequestPickupHandler) UpdateRequestPickup(c *fiber.Ctx) error { - userID, ok := c.Locals("userID").(string) - if !ok || userID == "" { - return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") - } - - id := c.Params("id") - var request dto.RequestPickup - - if err := c.BodyParser(&request); err != nil { - return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid request body") - } - - errors, valid := request.ValidateRequestPickup() - if !valid { - return utils.ValidationErrorResponse(c, errors) - } - - response, err := h.service.UpdateRequestPickup(id, request) - if err != nil { - return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error updating request pickup: %v", err)) - } - - return utils.SuccessResponse(c, response, "Request pickup updated successfully") -} - -func (h *RequestPickupHandler) DeleteRequestPickup(c *fiber.Ctx) error { - id := c.Params("id") - - err := h.service.DeleteRequestPickup(id) - if err != nil { - return utils.GenericResponse(c, fiber.StatusNotFound, fmt.Sprintf("Request pickup with ID %s not found: %v", id, err)) - } - - return utils.GenericResponse(c, fiber.StatusOK, "Request pickup deleted successfully") -} + // Return response + return utils.SuccessResponse(c, requests, "Automatic request pickups retrieved successfully") +} \ No newline at end of file diff --git a/internal/repositories/collector_repo.go b/internal/repositories/collector_repo.go new file mode 100644 index 0000000..0062610 --- /dev/null +++ b/internal/repositories/collector_repo.go @@ -0,0 +1,105 @@ +package repositories + +import ( + "errors" + "fmt" + "log" + "rijig/model" + "rijig/utils" + + "gorm.io/gorm" +) + +type CollectorRepository interface { + FindActiveCollectors() ([]model.Collector, error) + FindCollectorById(collector_id string) (*model.Collector, error) + CreateCollector(collector *model.Collector) error + UpdateCollector(userId string, jobStatus string) (*model.Collector, error) + FindAllAutomaticMethodRequestWithDistance(requestMethod, statuspickup string, collectorLat, collectorLon float64, maxDistance float64) ([]model.RequestPickup, error) +} + +type collectorRepository struct { + DB *gorm.DB +} + +func NewCollectorRepository(db *gorm.DB) CollectorRepository { + return &collectorRepository{DB: db} +} + +func (r *collectorRepository) FindActiveCollectors() ([]model.Collector, error) { + var collectors []model.Collector + + err := r.DB.Where("job_status = ?", "active").First(&collectors).Error + if err != nil { + return nil, fmt.Errorf("failed to fetch active collectors: %v", err) + } + + return collectors, nil +} + +func (r *collectorRepository) FindCollectorById(collector_id string) (*model.Collector, error) { + var collector model.Collector + err := r.DB.Where("user_id = ?", collector_id).First(&collector).Error + if err != nil { + return nil, fmt.Errorf("error fetching collector: %v", err) + } + fmt.Printf("menampilkan data collector %v", &collector) + return &collector, nil +} + +func (r *collectorRepository) CreateCollector(collector *model.Collector) error { + if err := r.DB.Create(collector).Error; err != nil { + return fmt.Errorf("failed to create collector: %v", err) + } + return nil +} + +func (r *collectorRepository) UpdateCollector(userId string, jobStatus string) (*model.Collector, error) { + var existingCollector model.Collector + + if err := r.DB.Where("user_id = ?", userId).First(&existingCollector).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("collector dengan user_id %s tidak ditemukan", userId) + } + log.Printf("Gagal mencari collector: %v", err) + return nil, fmt.Errorf("gagal fetching collector: %w", err) + } + + if jobStatus != "active" && jobStatus != "nonactive" { + return nil, fmt.Errorf("invalid job status: %v", jobStatus) + } + + if err := r.DB.Model(&existingCollector).Update("jobstatus", jobStatus).Error; err != nil { + log.Printf("Gagal mengupdate data collector: %v", err) + return nil, fmt.Errorf("gagal mengupdate job status untuk collector: %w", err) + } + + return &existingCollector, nil +} + +// #====experimen====# +func (r *collectorRepository) FindAllAutomaticMethodRequestWithDistance(requestMethod, statuspickup string, collectorLat, collectorLon float64, maxDistance float64) ([]model.RequestPickup, error) { + var requests []model.RequestPickup + + err := r.DB.Preload("RequestItems"). + Where("request_method = ? AND status_pickup = ?", requestMethod, statuspickup). + Find(&requests).Error + if err != nil { + return nil, fmt.Errorf("error fetching request pickups with request_method '%s' and status '%s': %v", requestMethod, statuspickup, err) + } + + var nearbyRequests []model.RequestPickup + for _, request := range requests { + address := request.Address + + requestCoord := utils.Coord{Lat: address.Latitude, Lon: address.Longitude} + collectorCoord := utils.Coord{Lat: collectorLat, Lon: collectorLon} + _, km := utils.Distance(requestCoord, collectorCoord) + + if km <= maxDistance { + nearbyRequests = append(nearbyRequests, request) + } + } + + return nearbyRequests, nil +} diff --git a/internal/repositories/requestpickup_repo.go b/internal/repositories/requestpickup_repo.go index 912c83e..0eadea7 100644 --- a/internal/repositories/requestpickup_repo.go +++ b/internal/repositories/requestpickup_repo.go @@ -11,11 +11,13 @@ type RequestPickupRepository interface { CreateRequestPickup(request *model.RequestPickup) error CreateRequestPickupItem(item *model.RequestPickupItem) error FindRequestPickupByID(id string) (*model.RequestPickup, error) - FindAllRequestPickups() ([]model.RequestPickup, error) + FindAllRequestPickups(userId string) ([]model.RequestPickup, error) + FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error) + FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) + GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) + GetAutomaticRequestPickupsForCollector(collectorId string) ([]model.RequestPickup, error) UpdateRequestPickup(id string, request *model.RequestPickup) error DeleteRequestPickup(id string) error - FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error) - FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) } type requestPickupRepository struct { @@ -57,20 +59,43 @@ func (r *requestPickupRepository) FindRequestPickupByID(id string) (*model.Reque return &request, nil } -func (r *requestPickupRepository) FindAllRequestPickups() ([]model.RequestPickup, error) { +func (r *requestPickupRepository) FindAllRequestPickups(userId string) ([]model.RequestPickup, error) { var requests []model.RequestPickup - err := r.DB.Preload("RequestItems").Find(&requests).Error + err := r.DB.Preload("RequestItems").Where("user_id = ?", userId).Find(&requests).Error if err != nil { return nil, fmt.Errorf("failed to fetch all request pickups: %v", err) } return requests, nil } +func (r *requestPickupRepository) FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error) { + var requests []model.RequestPickup + err := r.DB.Preload("RequestItems").Where("request_method = ? AND status_pickup = ?", requestMethod, statuspickup).Find(&requests).Error + if err != nil { + return nil, fmt.Errorf("error fetching request pickups with request_method %s: %v", requestMethod, err) + } + + return requests, nil +} + +func (r *requestPickupRepository) FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) { + var request model.RequestPickup + err := r.DB.Where("user_id = ? AND status_pickup = ?", userId, status).First(&request).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to check existing request pickup: %v", err) + } + return &request, nil +} + func (r *requestPickupRepository) UpdateRequestPickup(id string, request *model.RequestPickup) error { err := r.DB.Model(&model.RequestPickup{}).Where("id = ?", id).Updates(request).Error if err != nil { return fmt.Errorf("failed to update request pickup: %v", err) } + return nil } @@ -87,28 +112,25 @@ func (r *requestPickupRepository) DeleteRequestPickup(id string) error { return nil } -func (r *requestPickupRepository) FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error) { - var request model.RequestPickup - err := r.DB.Joins("JOIN request_pickup_items ON request_pickups.id = request_pickup_items.request_pickup_id"). - Where("request_pickups.address_id = ? AND request_pickup_items.trash_category_id = ?", addressID, trashCategoryID). - First(&request).Error +func (r *requestPickupRepository) GetAutomaticRequestPickupsForCollector(collectorId string) ([]model.RequestPickup, error) { + var requests []model.RequestPickup + + err := r.DB.Preload("Address"). + Where("request_method = ? AND status_pickup = ?", "otomatis", "waiting_collector"). + Find(&requests).Error if err != nil { - if err == gorm.ErrRecordNotFound { - return nil, nil - } - return nil, fmt.Errorf("error checking request pickup for address %s and category %s: %v", addressID, trashCategoryID, err) + return nil, fmt.Errorf("error fetching pickup requests: %v", err) } - return &request, nil + + return requests, nil } -func (r *requestPickupRepository) FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) { - var request model.RequestPickup - err := r.DB.Where("user_id = ? AND status_pickup = ?", userId, status).First(&request).Error +func (r *requestPickupRepository) GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) { + var items []model.RequestPickupItem + + err := r.DB.Preload("TrashCategory").Where("request_pickup_id = ?", requestPickupId).Find(&items).Error if err != nil { - if err == gorm.ErrRecordNotFound { - return nil, nil - } - return nil, fmt.Errorf("failed to check existing request pickup: %v", err) + return nil, fmt.Errorf("error fetching request pickup items: %v", err) } - return &request, nil + return items, nil } diff --git a/internal/services/address_service.go b/internal/services/address_service.go index 2fa6315..899f699 100644 --- a/internal/services/address_service.go +++ b/internal/services/address_service.go @@ -155,8 +155,8 @@ func (s *addressService) GetAddressByUserID(userID string) ([]dto.AddressRespons Village: addressData["village"].(string), PostalCode: addressData["postalCode"].(string), Detail: addressData["detail"].(string), - Latitude: addressData["latitude"].(string), - Longitude: addressData["longitude"].(string), + Latitude: addressData["latitude"].(float64), + Longitude: addressData["longitude"].(float64), CreatedAt: addressData["createdAt"].(string), UpdatedAt: addressData["updatedAt"].(string), }) @@ -227,8 +227,8 @@ func (s *addressService) GetAddressByID(userID, id string) (*dto.AddressResponse Village: addressData["village"].(string), PostalCode: addressData["postalCode"].(string), Detail: addressData["detail"].(string), - Latitude: addressData["latitude"].(string), - Longitude: addressData["longitude"].(string), + Latitude: addressData["latitude"].(float64), + Longitude: addressData["longitude"].(float64), CreatedAt: addressData["createdAt"].(string), UpdatedAt: addressData["updatedAt"].(string), } diff --git a/internal/services/auth/auth_pengepul_service.go b/internal/services/auth/auth_pengepul_service.go index 0fb0f3c..3ce5246 100644 --- a/internal/services/auth/auth_pengepul_service.go +++ b/internal/services/auth/auth_pengepul_service.go @@ -67,9 +67,10 @@ func (s *authPengepulService) checkOTPRequestCooldown(phone string) error { func (s *authPengepulService) sendOTP(phone string) error { otp := generateOTP() - if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil { - return err - } + fmt.Printf("ur otp is:%s", otp) + // if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil { + // return err + // } if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil { return err diff --git a/internal/services/collector_service.go b/internal/services/collector_service.go new file mode 100644 index 0000000..5cc255f --- /dev/null +++ b/internal/services/collector_service.go @@ -0,0 +1,108 @@ +package services + +import ( + "fmt" + "rijig/dto" + "rijig/internal/repositories" + "rijig/utils" +) + +type CollectorService interface { + FindCollectorsNearby(userId string) ([]dto.ResponseCollectorDTO, error) + ConfirmRequestPickup(requestId, collectorId string) (*dto.ResponseRequestPickup, error) +} + +type collectorService struct { + repo repositories.CollectorRepository + repoReq repositories.RequestPickupRepository + repoAddress repositories.AddressRepository +} + +func NewCollectorService(repo repositories.CollectorRepository, + repoReq repositories.RequestPickupRepository, + repoAddress repositories.AddressRepository) CollectorService { + return &collectorService{repo: repo, repoReq: repoReq, repoAddress: repoAddress} +} + +func (s *collectorService) FindCollectorsNearby(userId string) ([]dto.ResponseCollectorDTO, error) { + collectors, err := s.repo.FindActiveCollectors() + if err != nil { + return nil, fmt.Errorf("error fetching active collectors: %v", err) + } + + request, err := s.repoReq.FindRequestPickupByAddressAndStatus(userId, "waiting_collector") + if err != nil { + return nil, fmt.Errorf("gagal mendapatkan data request pickup dengan userid: %v", err) + } + + reqpickaddress, err := s.repoAddress.FindAddressByID(request.AddressId) + if err != nil { + return nil, fmt.Errorf("error fetching address for request pickup %s: %v", request.ID, err) + } + + var nearbyCollectorsResponse []dto.ResponseCollectorDTO + var maxDistance = 10.0 + + for _, collector := range collectors { + + address, err := s.repoAddress.FindAddressByID(collector.AddressId) + if err != nil { + return nil, fmt.Errorf("error fetching address for collector %s: %v", collector.ID, err) + } + + collectorCoord := utils.Coord{Lat: reqpickaddress.Latitude, Lon: reqpickaddress.Longitude} + userCoord := utils.Coord{Lat: address.Latitude, Lon: address.Longitude} + + _, km := utils.Distance(collectorCoord, userCoord) + + if km <= maxDistance { + + nearbyCollectorsResponse = append(nearbyCollectorsResponse, dto.ResponseCollectorDTO{ + ID: collector.ID, + AddressId: collector.User.Name, + Rating: collector.Rating, + }) + } + } + + if len(nearbyCollectorsResponse) == 0 { + return nil, fmt.Errorf("no request pickups found within %v km", maxDistance) + } + + return nearbyCollectorsResponse, nil +} + +func (s *collectorService) ConfirmRequestPickup(requestId, collectorId string) (*dto.ResponseRequestPickup, error) { + + request, err := s.repoReq.FindRequestPickupByID(requestId) + if err != nil { + return nil, fmt.Errorf("request pickup not found: %v", err) + } + + if request.StatusPickup != "waiting_collector" { + return nil, fmt.Errorf("pickup request is not in 'waiting_collector' status") + } + + collector, err := s.repo.FindCollectorById(collectorId) + if err != nil { + return nil, fmt.Errorf("collector tidak ditemukan: %v", err) + } + + request.StatusPickup = "confirmed" + request.CollectorID = &collector.ID + + err = s.repoReq.UpdateRequestPickup(requestId, request) + if err != nil { + return nil, fmt.Errorf("failed to update request pickup: %v", err) + } + + confirmedAt, _ := utils.FormatDateToIndonesianFormat(request.ConfirmedByCollectorAt) + + response := dto.ResponseRequestPickup{ + StatusPickup: request.StatusPickup, + CollectorID: *request.CollectorID, + ConfirmedByCollectorAt: confirmedAt, + } + + return &response, nil +} diff --git a/internal/services/requestpickup_service.go b/internal/services/requestpickup_service.go index 86d4276..ecd4514 100644 --- a/internal/services/requestpickup_service.go +++ b/internal/services/requestpickup_service.go @@ -5,18 +5,22 @@ import ( "rijig/dto" "rijig/internal/repositories" "rijig/model" + "rijig/utils" ) type RequestPickupService interface { CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) - GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) - UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) - DeleteRequestPickup(id string) error + GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) + // GetAllAutomaticRequestPickups(collector_id string) ([]dto.ResponseRequestPickup, error) + // GetAllAutomaticRequestPickup(collectorId string) ([]dto.ResponseRequestPickup, error) + + GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) } type requestPickupService struct { repo repositories.RequestPickupRepository + repoReq repositories.CollectorRepository repoAddress repositories.AddressRepository repoTrash repositories.TrashRepository } @@ -34,12 +38,12 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us return nil, fmt.Errorf("validation errors: %v", errors) } - findAddress, err := s.repoAddress.FindAddressByID(request.AddressID) + _, err := s.repoAddress.FindAddressByID(request.AddressID) if err != nil { return nil, fmt.Errorf("address with ID %s not found", request.AddressID) } - existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_pengepul") + existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_collector") if err != nil { return nil, fmt.Errorf("error checking for existing request pickup: %v", err) } @@ -49,8 +53,9 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us modelRequest := model.RequestPickup{ UserId: UserId, - AddressId: findAddress.ID, + AddressId: request.AddressID, EvidenceImage: request.EvidenceImage, + RequestMethod: request.RequestMethod, } err = s.repo.CreateRequestPickup(&modelRequest) @@ -58,14 +63,17 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us return nil, fmt.Errorf("failed to create request pickup: %v", err) } + createdAt, _ := utils.FormatDateToIndonesianFormat(modelRequest.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(modelRequest.UpdatedAt) + response := &dto.ResponseRequestPickup{ ID: modelRequest.ID, UserId: UserId, AddressID: modelRequest.AddressId, EvidenceImage: modelRequest.EvidenceImage, StatusPickup: modelRequest.StatusPickup, - CreatedAt: modelRequest.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: modelRequest.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: createdAt, + UpdatedAt: updatedAt, } for _, item := range request.RequestItems { @@ -102,80 +110,94 @@ func (s *requestPickupService) GetRequestPickupByID(id string) (*dto.ResponseReq return nil, fmt.Errorf("error fetching request pickup with ID %s: %v", id, err) } + createdAt, _ := utils.FormatDateToIndonesianFormat(request.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(request.UpdatedAt) + response := &dto.ResponseRequestPickup{ ID: request.ID, UserId: request.UserId, AddressID: request.AddressId, EvidenceImage: request.EvidenceImage, StatusPickup: request.StatusPickup, - CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: createdAt, + UpdatedAt: updatedAt, } return response, nil } -func (s *requestPickupService) GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) { +func (s *requestPickupService) GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) { - requests, err := s.repo.FindAllRequestPickups() + requests, err := s.repo.FindAllRequestPickups(userid) if err != nil { return nil, fmt.Errorf("error fetching all request pickups: %v", err) } var response []dto.ResponseRequestPickup for _, request := range requests { + createdAt, _ := utils.FormatDateToIndonesianFormat(request.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(request.UpdatedAt) response = append(response, dto.ResponseRequestPickup{ ID: request.ID, UserId: request.UserId, AddressID: request.AddressId, EvidenceImage: request.EvidenceImage, StatusPickup: request.StatusPickup, - CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), + CreatedAt: createdAt, + UpdatedAt: updatedAt, }) } return response, nil } -func (s *requestPickupService) UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) { +func (s *requestPickupService) GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) { - errors, valid := request.ValidateRequestPickup() - if !valid { - return nil, fmt.Errorf("validation errors: %v", errors) - } - - existingRequest, err := s.repo.FindRequestPickupByID(id) + requests, err := s.repo.GetAutomaticRequestPickupsForCollector(collectorId) if err != nil { - return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) + return nil, fmt.Errorf("error retrieving automatic pickup requests: %v", err) } - existingRequest.EvidenceImage = request.EvidenceImage + var response []dto.ResponseRequestPickup - err = s.repo.UpdateRequestPickup(id, existingRequest) - if err != nil { - return nil, fmt.Errorf("failed to update request pickup: %v", err) - } + for _, req := range requests { - response := &dto.ResponseRequestPickup{ - ID: existingRequest.ID, - UserId: existingRequest.UserId, - AddressID: existingRequest.AddressId, - EvidenceImage: existingRequest.EvidenceImage, - StatusPickup: existingRequest.StatusPickup, - CreatedAt: existingRequest.CreatedAt.Format("2006-01-02 15:04:05"), - UpdatedAt: existingRequest.UpdatedAt.Format("2006-01-02 15:04:05"), + _, distance := utils.Distance( + utils.Coord{Lat: req.Address.Latitude, Lon: req.Address.Longitude}, + utils.Coord{Lat: req.Address.Latitude, Lon: req.Address.Longitude}, + ) + + if distance <= 20 { + + mappedRequest := dto.ResponseRequestPickup{ + ID: req.ID, + UserId: req.UserId, + AddressID: req.AddressId, + EvidenceImage: req.EvidenceImage, + StatusPickup: req.StatusPickup, + CreatedAt: req.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: req.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + requestItems, err := s.repo.GetRequestPickupItems(req.ID) + if err != nil { + return nil, fmt.Errorf("error fetching request items: %v", err) + } + + var mappedRequestItems []dto.ResponseRequestPickupItem + for _, item := range requestItems { + mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{ + ID: item.ID, + TrashCategoryName: item.TrashCategory.Name, + EstimatedAmount: item.EstimatedAmount, + }) + } + + mappedRequest.RequestItems = mappedRequestItems + + response = append(response, mappedRequest) + } } return response, nil } - -func (s *requestPickupService) DeleteRequestPickup(id string) error { - - err := s.repo.DeleteRequestPickup(id) - if err != nil { - return fmt.Errorf("failed to delete request pickup with ID %s: %v", id, err) - } - - return nil -} diff --git a/model/address_model.go b/model/address_model.go index a381737..d5674d0 100644 --- a/model/address_model.go +++ b/model/address_model.go @@ -12,8 +12,8 @@ type Address struct { Village string `gorm:"not null" json:"village"` PostalCode string `gorm:"not null" json:"postalCode"` Detail string `gorm:"not null" json:"detail"` - Latitude string `gorm:"not null" json:"latitude"` - Longitude string `gorm:"not null" json:"longitude"` + Latitude float64 `gorm:"not null" json:"latitude"` + Longitude float64 `gorm:"not null" json:"longitude"` CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` } diff --git a/model/collector_model.go b/model/collector_model.go new file mode 100644 index 0000000..ed024f2 --- /dev/null +++ b/model/collector_model.go @@ -0,0 +1,11 @@ +package model + +type Collector struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + UserID string `gorm:"not null" json:"userId"` + User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"` + JobStatus string `gorm:"default:nonactive" json:"jobstatus"` + Rating float32 `gorm:"default:5" json:"rating"` + AddressId string `gorm:"not null" json:"address_id"` + Address Address `gorm:"foreignKey:AddressId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"address"` +} diff --git a/model/requestpickup_model.go b/model/requestpickup_model.go index 584cbb2..a8ee3e8 100644 --- a/model/requestpickup_model.go +++ b/model/requestpickup_model.go @@ -5,20 +5,26 @@ import ( ) type RequestPickup struct { - ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` - UserId string `gorm:"not null" json:"user_id"` - AddressId string `gorm:"not null" json:"address_id"` - RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"` - EvidenceImage string `json:"evidence_image"` - StatusPickup string `gorm:"default:'waiting_pengepul'" json:"status_pickup"` - CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"` - UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"` + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + UserId string `gorm:"not null" json:"user_id"` + User User `gorm:"foreignKey:UserId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"` + AddressId string `gorm:"not null" json:"address_id"` + Address Address `gorm:"foreignKey:AddressId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"address"` + RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"` + EvidenceImage string `json:"evidence_image"` + StatusPickup string `gorm:"default:'waiting_collector'" json:"status_pickup"` + CollectorID *string `gorm:"type:uuid" json:"collector_id,omitempty"` + ConfirmedByCollectorAt time.Time `gorm:"default:current_timestamp" json:"confirmed_by_collector_at,omitempty"` + RequestMethod string `gorm:"not null" json:"request_method"` + CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"` + UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"` } type RequestPickupItem struct { - ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` - RequestPickupId string `gorm:"not null" json:"request_pickup_id"` - TrashCategoryId string `gorm:"not null" json:"trash_category_id"` - TrashDetailId string `json:"trash_detail_id,omitempty"` - EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"` + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + RequestPickupId string `gorm:"not null" json:"request_pickup_id"` + RequestPickup RequestPickup `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;"` + TrashCategoryId string `gorm:"not null" json:"trash_category_id"` + TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryId;constraint:OnDelete:CASCADE;"` + EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"` } diff --git a/presentation/collector_route.go b/presentation/collector_route.go new file mode 100644 index 0000000..451b3bc --- /dev/null +++ b/presentation/collector_route.go @@ -0,0 +1,26 @@ +package presentation + +import ( + "rijig/config" + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +func CollectorRouter(api fiber.Router) { + repo := repositories.NewCollectorRepository(config.DB) + repoReq := repositories.NewRequestPickupRepository(config.DB) + repoAddress := repositories.NewAddressRepository(config.DB) + colectorService := services.NewCollectorService(repo, repoReq, repoAddress) + collectorHandler := handler.NewCollectorHandler(colectorService) + + collector := api.Group("/collector") + collector.Use(middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RolePengepul)) + + collector.Put("confirmrequest/:id", collectorHandler.ConfirmRequestPickup) + +} diff --git a/presentation/requestpickup_route.go b/presentation/requestpickup_route.go index 6c63262..e06e983 100644 --- a/presentation/requestpickup_route.go +++ b/presentation/requestpickup_route.go @@ -15,8 +15,12 @@ func RequestPickupRouter(api fiber.Router) { requestRepo := repositories.NewRequestPickupRepository(config.DB) repoTrash := repositories.NewTrashRepository(config.DB) repoAddress := repositories.NewAddressRepository(config.DB) + // collectorRepo := repositories.NewCollectorRepository(config.DB) requestPickupServices := services.NewRequestPickupService(requestRepo, repoAddress, repoTrash) + // collectorService := services.NewCollectorService(collectorRepo, requestRepo, repoAddress) + // service services.RequestPickupService, + // collectorService services.CollectorService requestPickupHandler := handler.NewRequestPickupHandler(requestPickupServices) @@ -24,6 +28,8 @@ func RequestPickupRouter(api fiber.Router) { requestPickupAPI.Use(middleware.AuthMiddleware) requestPickupAPI.Post("/", requestPickupHandler.CreateRequestPickup) + // requestPickupAPI.Get("/get", middleware.AuthMiddleware, requestPickupHandler.GetAutomaticRequestByUser) + requestPickupAPI.Get("/get-allrequest", requestPickupHandler.GetRequestPickups) // requestPickupAPI.Get("/:id", requestPickupHandler.GetRequestPickupByID) // requestPickupAPI.Get("/", requestPickupHandler.GetAllRequestPickups) // requestPickupAPI.Put("/:id", requestPickupHandler.UpdateRequestPickup) diff --git a/router/setup_routes.go.go b/router/setup_routes.go.go index 8b3a343..9b99496 100644 --- a/router/setup_routes.go.go +++ b/router/setup_routes.go.go @@ -26,6 +26,7 @@ func SetupRoutes(app *fiber.App) { presentation.IdentityCardRouter(api) presentation.CompanyProfileRouter(api) presentation.RequestPickupRouter(api) + presentation.CollectorRouter(api) presentation.UserProfileRouter(api) presentation.UserPinRouter(api) diff --git a/utils/havershine.go b/utils/havershine.go new file mode 100644 index 0000000..9de461f --- /dev/null +++ b/utils/havershine.go @@ -0,0 +1,39 @@ +package utils + +import ( + "math" +) + +const ( + earthRadiusMi = 3958 + earthRaidusKm = 6371 +) + +type Coord struct { + Lat float64 + Lon float64 +} + +func degreesToRadians(d float64) float64 { + return d * math.Pi / 180 +} + +func Distance(p, q Coord) (mi, km float64) { + lat1 := degreesToRadians(p.Lat) + lon1 := degreesToRadians(p.Lon) + lat2 := degreesToRadians(q.Lat) + lon2 := degreesToRadians(q.Lon) + + diffLat := lat2 - lat1 + diffLon := lon2 - lon1 + + a := math.Pow(math.Sin(diffLat/2), 2) + math.Cos(lat1)*math.Cos(lat2)* + math.Pow(math.Sin(diffLon/2), 2) + + c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) + + mi = c * earthRadiusMi + km = c * earthRaidusKm + + return mi, km +}