diff --git a/config/database.go b/config/database.go index d19ebbd..04707b3 100644 --- a/config/database.go +++ b/config/database.go @@ -54,6 +54,8 @@ func ConnectDatabase() { // =>requestpickup preparation<= &model.RequestPickup{}, &model.RequestPickupItem{}, + &model.PickupStatusHistory{}, + &model.PickupRating{}, &model.Cart{}, &model.CartItem{}, diff --git a/dto/collector_dto.go b/dto/collector_dto.go index a48033a..0a523d7 100644 --- a/dto/collector_dto.go +++ b/dto/collector_dto.go @@ -5,6 +5,17 @@ import ( "strings" ) +type NearbyCollectorDTO struct { + CollectorID string `json:"collector_id"` + Name string `json:"name"` + Phone string `json:"phone"` + Rating float32 `json:"rating"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + DistanceKm float64 `json:"distance_km"` + MatchedTrash []string `json:"matched_trash_ids"` +} + type RequestCollectorDTO struct { AddressId string `json:"address_id"` AvaibleTrashbyCollector []RequestAvaibleTrashbyCollector `json:"avaible_trash"` diff --git a/dto/rating_dto.go b/dto/rating_dto.go new file mode 100644 index 0000000..62b680d --- /dev/null +++ b/dto/rating_dto.go @@ -0,0 +1,25 @@ +package dto + +import "strings" + +type CreatePickupRatingDTO struct { + Rating float32 `json:"rating"` + Feedback string `json:"feedback"` +} + +func (r *CreatePickupRatingDTO) ValidateCreatePickupRatingDTO() (map[string][]string, bool) { + errors := make(map[string][]string) + + if r.Rating < 1.0 || r.Rating > 5.0 { + errors["rating"] = append(errors["rating"], "Rating harus antara 1.0 sampai 5.0") + } + + if len(strings.TrimSpace(r.Feedback)) > 255 { + errors["feedback"] = append(errors["feedback"], "Feedback tidak boleh lebih dari 255 karakter") + } + + if len(errors) > 0 { + return errors, false + } + return nil, true +} diff --git a/dto/request_pickup_dto.go b/dto/request_pickup_dto.go new file mode 100644 index 0000000..480ed9e --- /dev/null +++ b/dto/request_pickup_dto.go @@ -0,0 +1,85 @@ +package dto + +import ( + "strings" +) + +// type NearbyCollectorDTO struct { +// CollectorID string `json:"collector_id"` +// Name string `json:"name"` +// Phone string `json:"phone"` +// Rating float32 `json:"rating"` +// Latitude float64 `json:"latitude"` +// Longitude float64 `json:"longitude"` +// DistanceKm float64 `json:"distance_km"` +// MatchedTrash []string `json:"matched_trash_ids"` +// } + +type SelectCollectorDTO struct { + CollectorID string `json:"collector_id"` +} + +type UpdateRequestPickupItemDTO struct { + ItemID string `json:"item_id"` + Amount float64 `json:"actual_amount"` +} + +type UpdatePickupItemsRequest struct { + Items []UpdateRequestPickupItemDTO `json:"items"` +} + +func (r *SelectCollectorDTO) Validate() (map[string][]string, bool) { + errors := make(map[string][]string) + + if strings.TrimSpace(r.CollectorID) == "" { + errors["collector_id"] = append(errors["collector_id"], "collector_id tidak boleh kosong") + } + + if len(errors) > 0 { + return errors, false + } + return nil, true +} + +type AssignedPickupDTO struct { + PickupID string `json:"pickup_id"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Notes string `json:"notes"` + MatchedTrash []string `json:"matched_trash"` +} + +type PickupRequestForCollectorDTO struct { + PickupID string `json:"pickup_id"` + UserID string `json:"user_id"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + DistanceKm float64 `json:"distance_km"` + MatchedTrash []string `json:"matched_trash"` +} + +type RequestPickupDTO struct { + AddressID string `json:"address_id"` + RequestMethod string `json:"request_method"` // "manual" atau "otomatis" + Notes string `json:"notes,omitempty"` +} + +func (r *RequestPickupDTO) Validate() (map[string][]string, bool) { + errors := make(map[string][]string) + + if strings.TrimSpace(r.AddressID) == "" { + errors["address_id"] = append(errors["address_id"], "alamat harus dipilih") + } + + method := strings.ToLower(strings.TrimSpace(r.RequestMethod)) + if method != "manual" && method != "otomatis" { + errors["request_method"] = append(errors["request_method"], "harus manual atau otomatis") + } + + if len(errors) > 0 { + return errors, false + } + return nil, true +} diff --git a/internal/handler/pickup_history_handler.go b/internal/handler/pickup_history_handler.go new file mode 100644 index 0000000..2525f44 --- /dev/null +++ b/internal/handler/pickup_history_handler.go @@ -0,0 +1,37 @@ +package handler + +import ( + "context" + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type PickupStatusHistoryHandler interface { + GetStatusHistory(c *fiber.Ctx) error +} + +type pickupStatusHistoryHandler struct { + service services.PickupStatusHistoryService +} + +func NewPickupStatusHistoryHandler(service services.PickupStatusHistoryService) PickupStatusHistoryHandler { + return &pickupStatusHistoryHandler{service: service} +} + +func (h *pickupStatusHistoryHandler) GetStatusHistory(c *fiber.Ctx) error { + pickupID := c.Params("id") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID tidak boleh kosong"}, + }) + } + + histories, err := h.service.GetStatusHistory(context.Background(), pickupID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, histories, "Riwayat status pickup berhasil diambil") +} diff --git a/internal/handler/pickup_matching_handler.go b/internal/handler/pickup_matching_handler.go new file mode 100644 index 0000000..ecb39d9 --- /dev/null +++ b/internal/handler/pickup_matching_handler.go @@ -0,0 +1,49 @@ +package handler + +import ( + "context" + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type PickupMatchingHandler interface { + GetNearbyCollectorsForPickup(c *fiber.Ctx) error + GetAvailablePickupForCollector(c *fiber.Ctx) error +} + +type pickupMatchingHandler struct { + service services.PickupMatchingService +} + +func NewPickupMatchingHandler(service services.PickupMatchingService) PickupMatchingHandler { + return &pickupMatchingHandler{service: service} +} + +func (h *pickupMatchingHandler) GetNearbyCollectorsForPickup(c *fiber.Ctx) error { + pickupID := c.Params("pickupID") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID harus disertakan"}, + }) + } + + collectors, err := h.service.FindNearbyCollectorsForPickup(context.Background(), pickupID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, collectors, "Data collector terdekat berhasil diambil") +} + +func (h *pickupMatchingHandler) GetAvailablePickupForCollector(c *fiber.Ctx) error { + collectorID := c.Locals("userID").(string) + + pickups, err := h.service.FindAvailableRequestsForCollector(context.Background(), collectorID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, pickups, "Data request pickup otomatis berhasil diambil") +} diff --git a/internal/handler/rating_handler.go b/internal/handler/rating_handler.go new file mode 100644 index 0000000..6f64916 --- /dev/null +++ b/internal/handler/rating_handler.go @@ -0,0 +1,66 @@ +package handler + +import ( + "context" + "rijig/dto" + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type PickupRatingHandler interface { + CreateRating(c *fiber.Ctx) error + GetRatingsByCollector(c *fiber.Ctx) error + GetAverageRating(c *fiber.Ctx) error +} + +type pickupRatingHandler struct { + service services.PickupRatingService +} + +func NewPickupRatingHandler(service services.PickupRatingService) PickupRatingHandler { + return &pickupRatingHandler{service: service} +} + +func (h *pickupRatingHandler) CreateRating(c *fiber.Ctx) error { + pickupID := c.Params("id") + userID := c.Locals("userID").(string) + collectorID := c.Query("collector_id") + + var req dto.CreatePickupRatingDTO + if err := c.BodyParser(&req); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{ + "body": {"Format JSON tidak valid"}, + }) + } + + if errs, ok := req.ValidateCreatePickupRatingDTO(); !ok { + return utils.ValidationErrorResponse(c, errs) + } + + err := h.service.CreateRating(context.Background(), userID, pickupID, collectorID, req) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, nil, "Rating berhasil dikirim") +} + +func (h *pickupRatingHandler) GetRatingsByCollector(c *fiber.Ctx) error { + collectorID := c.Params("id") + ratings, err := h.service.GetRatingsByCollector(context.Background(), collectorID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + return utils.SuccessResponse(c, ratings, "Daftar rating collector berhasil diambil") +} + +func (h *pickupRatingHandler) GetAverageRating(c *fiber.Ctx) error { + collectorID := c.Params("id") + avg, err := h.service.GetAverageRating(context.Background(), collectorID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + return utils.SuccessResponse(c, fiber.Map{"average_rating": avg}, "Rata-rata rating collector") +} diff --git a/internal/handler/request_pickup_handler.go b/internal/handler/request_pickup_handler.go new file mode 100644 index 0000000..f5c2826 --- /dev/null +++ b/internal/handler/request_pickup_handler.go @@ -0,0 +1,150 @@ +package handler + +import ( + "context" + "rijig/dto" + "rijig/internal/services" + "rijig/utils" + "time" + + "github.com/gofiber/fiber/v2" +) + +type RequestPickupHandler interface { + CreateRequestPickup(c *fiber.Ctx) error + SelectCollector(c *fiber.Ctx) error + GetAssignedPickup(c *fiber.Ctx) error + ConfirmPickup(c *fiber.Ctx) error + UpdatePickupStatus(c *fiber.Ctx) error + UpdatePickupItemActualAmount(c *fiber.Ctx) error +} + +type requestPickupHandler struct { + service services.RequestPickupService +} + +func NewRequestPickupHandler(service services.RequestPickupService) RequestPickupHandler { + return &requestPickupHandler{service: service} +} + +func (h *requestPickupHandler) CreateRequestPickup(c *fiber.Ctx) error { + userID := c.Locals("userID").(string) + + var req dto.RequestPickupDTO + if err := c.BodyParser(&req); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{ + "body": {"format JSON tidak valid"}, + }) + } + + if errs, ok := req.Validate(); !ok { + return utils.ValidationErrorResponse(c, errs) + } + + if err := h.service.ConvertCartToRequestPickup(context.Background(), userID, req); err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, nil, "Request pickup berhasil dibuat") +} + +func (h *requestPickupHandler) SelectCollector(c *fiber.Ctx) error { + pickupID := c.Params("id") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID harus disertakan"}, + }) + } + + var req dto.SelectCollectorDTO + if err := c.BodyParser(&req); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{ + "body": {"format JSON tidak valid"}, + }) + } + + if errs, ok := req.Validate(); !ok { + return utils.ValidationErrorResponse(c, errs) + } + + if err := h.service.AssignCollectorToRequest(context.Background(), pickupID, req); err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, nil, "Collector berhasil dipilih untuk pickup") +} + +func (h *requestPickupHandler) GetAssignedPickup(c *fiber.Ctx) error { + collectorID := c.Locals("userID").(string) + result, err := h.service.FindRequestsAssignedToCollector(context.Background(), collectorID) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + return utils.SuccessResponse(c, result, "Data pickup yang ditugaskan berhasil diambil") +} + +func (h *requestPickupHandler) ConfirmPickup(c *fiber.Ctx) error { + pickupID := c.Params("id") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID wajib diisi"}, + }) + } + + err := h.service.ConfirmPickupByCollector(context.Background(), pickupID, time.Now()) + if err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + return utils.SuccessResponse(c, nil, "Pickup berhasil dikonfirmasi oleh collector") +} + +func (h *requestPickupHandler) UpdatePickupStatus(c *fiber.Ctx) error { + pickupID := c.Params("id") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID tidak boleh kosong"}, + }) + } + + if err := h.service.UpdatePickupStatusToPickingUp(context.Background(), pickupID); err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, nil, "Status pickup berhasil diperbarui menjadi 'collector_are_picking_up'") +} + +func (h *requestPickupHandler) UpdatePickupItemActualAmount(c *fiber.Ctx) error { + pickupID := c.Params("id") + if pickupID == "" { + return utils.ValidationErrorResponse(c, map[string][]string{ + "pickup_id": {"pickup ID tidak boleh kosong"}, + }) + } + + var req dto.UpdatePickupItemsRequest + if err := c.BodyParser(&req); err != nil { + return utils.ValidationErrorResponse(c, map[string][]string{ + "body": {"format JSON tidak valid"}, + }) + } + + if len(req.Items) == 0 { + return utils.ValidationErrorResponse(c, map[string][]string{ + "items": {"daftar item tidak boleh kosong"}, + }) + } + + for _, item := range req.Items { + if item.ItemID == "" || item.Amount <= 0 { + return utils.ValidationErrorResponse(c, map[string][]string{ + "item": {"item_id harus valid dan amount > 0"}, + }) + } + } + + if err := h.service.UpdateActualPickupItems(context.Background(), pickupID, req.Items); err != nil { + return utils.InternalServerErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, nil, "Berat aktual dan harga berhasil diperbarui") +} \ No newline at end of file diff --git a/internal/repositories/collector_repo.go b/internal/repositories/collector_repo.go index 9340893..aa41c71 100644 --- a/internal/repositories/collector_repo.go +++ b/internal/repositories/collector_repo.go @@ -4,21 +4,11 @@ import ( "context" "errors" - // "fmt" - - // "log" "rijig/config" "rijig/model" - // "gorm.io/gorm" ) type CollectorRepository interface { - // FindActiveCollectors() ([]model.Collector, error) - // FindCollectorById(collector_id string) (*model.Collector, error) - // FindCollectorByIdWithoutAddr(collector_id string) (*model.Collector, error) - // CreateCollector(collector *model.Collector) error - // UpdateCollector(userId string, jobStatus string) (*model.Collector, error) - CreateCollector(ctx context.Context, collector *model.Collector) error AddAvaibleTrash(ctx context.Context, trashItems []model.AvaibleTrashByCollector) error GetCollectorByID(ctx context.Context, collectorID string) (*model.Collector, error) @@ -27,80 +17,18 @@ type CollectorRepository interface { UpdateCollector(ctx context.Context, collector *model.Collector, updates map[string]interface{}) error UpdateAvaibleTrashByCollector(ctx context.Context, collectorID string, updatedTrash []model.AvaibleTrashByCollector) error DeleteAvaibleTrash(ctx context.Context, trashID string) error + + GetActiveCollectorsWithTrashAndAddress(ctx context.Context) ([]model.Collector, error) + GetCollectorWithAddressAndTrash(ctx context.Context, collectorID string) (*model.Collector, error) } type collectorRepository struct { - // DB *gorm.DB } -// func NewCollectorRepository(db *gorm.DB) CollectorRepository { -// return &collectorRepository{DB: db} -// } func NewCollectorRepository() CollectorRepository { return &collectorRepository{} } -// func (r *collectorRepository) FindActiveCollectors() ([]model.Collector, error) { -// var collectors []model.Collector - -// err := r.DB.Preload("Address").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.Preload("Address").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) FindCollectorByIdWithoutAddr(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 -// } - func (r *collectorRepository) CreateCollector(ctx context.Context, collector *model.Collector) error { return config.DB.WithContext(ctx).Create(collector).Error } @@ -175,3 +103,35 @@ func (r *collectorRepository) DeleteAvaibleTrash(ctx context.Context, trashID st return config.DB.WithContext(ctx). Delete(&model.AvaibleTrashByCollector{}, "id = ?", trashID).Error } + + +// +func (r *collectorRepository) GetActiveCollectorsWithTrashAndAddress(ctx context.Context) ([]model.Collector, error) { + var collectors []model.Collector + err := config.DB.WithContext(ctx). + Preload("User"). + Preload("Address"). + Preload("AvaibleTrashbyCollector.TrashCategory"). + Where("job_status = ?", "active"). + Find(&collectors).Error + + if err != nil { + return nil, err + } + + return collectors, nil +} + +func (r *collectorRepository) GetCollectorWithAddressAndTrash(ctx context.Context, collectorID string) (*model.Collector, error) { + var collector model.Collector + err := config.DB.WithContext(ctx). + Preload("Address"). + Preload("AvaibleTrashbyCollector"). + Where("id = ?", collectorID). + First(&collector).Error + + if err != nil { + return nil, err + } + return &collector, nil +} diff --git a/internal/repositories/pickup_history_repo.go b/internal/repositories/pickup_history_repo.go new file mode 100644 index 0000000..536ecf7 --- /dev/null +++ b/internal/repositories/pickup_history_repo.go @@ -0,0 +1,34 @@ +package repositories + +import ( + "context" + "rijig/config" + "rijig/model" +) + +type PickupStatusHistoryRepository interface { + CreateStatusHistory(ctx context.Context, history model.PickupStatusHistory) error + GetStatusHistoryByRequestID(ctx context.Context, requestID string) ([]model.PickupStatusHistory, error) +} + +type pickupStatusHistoryRepository struct{} + +func NewPickupStatusHistoryRepository() PickupStatusHistoryRepository { + return &pickupStatusHistoryRepository{} +} + +func (r *pickupStatusHistoryRepository) CreateStatusHistory(ctx context.Context, history model.PickupStatusHistory) error { + return config.DB.WithContext(ctx).Create(&history).Error +} + +func (r *pickupStatusHistoryRepository) GetStatusHistoryByRequestID(ctx context.Context, requestID string) ([]model.PickupStatusHistory, error) { + var histories []model.PickupStatusHistory + err := config.DB.WithContext(ctx). + Where("request_id = ?", requestID). + Order("changed_at asc"). + Find(&histories).Error + if err != nil { + return nil, err + } + return histories, nil +} diff --git a/internal/repositories/rating_repo.go b/internal/repositories/rating_repo.go new file mode 100644 index 0000000..b593cd8 --- /dev/null +++ b/internal/repositories/rating_repo.go @@ -0,0 +1,48 @@ +package repositories + +import ( + "context" + "rijig/config" + "rijig/model" +) + +type PickupRatingRepository interface { + CreateRating(ctx context.Context, rating model.PickupRating) error + GetRatingsByCollector(ctx context.Context, collectorID string) ([]model.PickupRating, error) + CalculateAverageRating(ctx context.Context, collectorID string) (float32, error) +} + +type pickupRatingRepository struct{} + +func NewPickupRatingRepository() PickupRatingRepository { + return &pickupRatingRepository{} +} + +func (r *pickupRatingRepository) CreateRating(ctx context.Context, rating model.PickupRating) error { + return config.DB.WithContext(ctx).Create(&rating).Error +} + +func (r *pickupRatingRepository) GetRatingsByCollector(ctx context.Context, collectorID string) ([]model.PickupRating, error) { + var ratings []model.PickupRating + err := config.DB.WithContext(ctx). + Where("collector_id = ?", collectorID). + Order("created_at desc"). + Find(&ratings).Error + if err != nil { + return nil, err + } + return ratings, nil +} + +func (r *pickupRatingRepository) CalculateAverageRating(ctx context.Context, collectorID string) (float32, error) { + var avg float32 + err := config.DB.WithContext(ctx). + Model(&model.PickupRating{}). + Select("AVG(rating)"). + Where("collector_id = ?", collectorID). + Scan(&avg).Error + if err != nil { + return 0, err + } + return avg, nil +} diff --git a/internal/repositories/request_pickup_repo.go b/internal/repositories/request_pickup_repo.go new file mode 100644 index 0000000..25f3225 --- /dev/null +++ b/internal/repositories/request_pickup_repo.go @@ -0,0 +1,143 @@ +package repositories + +import ( + "context" + "rijig/config" + "rijig/dto" + "rijig/model" + "time" +) + +type RequestPickupRepository interface { + CreateRequestPickup(ctx context.Context, pickup *model.RequestPickup) error + GetPickupWithItemsAndAddress(ctx context.Context, id string) (*model.RequestPickup, error) + GetAllAutomaticRequestsWithAddress(ctx context.Context) ([]model.RequestPickup, error) + UpdateCollectorID(ctx context.Context, pickupID, collectorID string) error + GetRequestsAssignedToCollector(ctx context.Context, collectorID string) ([]model.RequestPickup, error) + UpdatePickupStatusAndConfirmationTime(ctx context.Context, pickupID string, status string, confirmedAt time.Time) error + UpdatePickupStatus(ctx context.Context, pickupID string, status string) error + UpdateRequestPickupItemsAmountAndPrice(ctx context.Context, pickupID string, items []dto.UpdateRequestPickupItemDTO) error +} + +type requestPickupRepository struct{} + +func NewRequestPickupRepository() RequestPickupRepository { + return &requestPickupRepository{} +} + +func (r *requestPickupRepository) CreateRequestPickup(ctx context.Context, pickup *model.RequestPickup) error { + return config.DB.WithContext(ctx).Create(pickup).Error +} + +func (r *requestPickupRepository) GetPickupWithItemsAndAddress(ctx context.Context, id string) (*model.RequestPickup, error) { + var pickup model.RequestPickup + err := config.DB.WithContext(ctx). + Preload("RequestItems"). + Preload("Address"). + Where("id = ?", id). + First(&pickup).Error + + if err != nil { + return nil, err + } + return &pickup, nil +} + +func (r *requestPickupRepository) UpdateCollectorID(ctx context.Context, pickupID, collectorID string) error { + return config.DB.WithContext(ctx). + Model(&model.RequestPickup{}). + Where("id = ?", pickupID). + Update("collector_id", collectorID). + Error +} + +func (r *requestPickupRepository) GetAllAutomaticRequestsWithAddress(ctx context.Context) ([]model.RequestPickup, error) { + var pickups []model.RequestPickup + err := config.DB.WithContext(ctx). + Preload("RequestItems"). + Preload("Address"). + Where("request_method = ?", "otomatis"). + Find(&pickups).Error + + if err != nil { + return nil, err + } + return pickups, nil +} + +func (r *requestPickupRepository) GetRequestsAssignedToCollector(ctx context.Context, collectorID string) ([]model.RequestPickup, error) { + var pickups []model.RequestPickup + err := config.DB.WithContext(ctx). + Preload("User"). + Preload("Address"). + Preload("RequestItems"). + Where("collector_id = ? AND status_pickup = ?", collectorID, "waiting_collector"). + Find(&pickups).Error + + if err != nil { + return nil, err + } + return pickups, nil +} + +func (r *requestPickupRepository) UpdatePickupStatusAndConfirmationTime(ctx context.Context, pickupID string, status string, confirmedAt time.Time) error { + return config.DB.WithContext(ctx). + Model(&model.RequestPickup{}). + Where("id = ?", pickupID). + Updates(map[string]interface{}{ + "status_pickup": status, + "confirmed_by_collector_at": confirmedAt, + }).Error +} + +func (r *requestPickupRepository) UpdatePickupStatus(ctx context.Context, pickupID string, status string) error { + return config.DB.WithContext(ctx). + Model(&model.RequestPickup{}). + Where("id = ?", pickupID). + Update("status_pickup", status). + Error +} + +func (r *requestPickupRepository) UpdateRequestPickupItemsAmountAndPrice(ctx context.Context, pickupID string, items []dto.UpdateRequestPickupItemDTO) error { + // ambil collector_id dulu dari pickup + var pickup model.RequestPickup + if err := config.DB.WithContext(ctx). + Select("collector_id"). + Where("id = ?", pickupID). + First(&pickup).Error; err != nil { + return err + } + + for _, item := range items { + var pickupItem model.RequestPickupItem + err := config.DB.WithContext(ctx). + Where("id = ? AND request_pickup_id = ?", item.ItemID, pickupID). + First(&pickupItem).Error + if err != nil { + return err + } + + var price float64 + err = config.DB.WithContext(ctx). + Model(&model.AvaibleTrashByCollector{}). + Where("collector_id = ? AND trash_category_id = ?", pickup.CollectorID, pickupItem.TrashCategoryId). + Select("price"). + Scan(&price).Error + if err != nil { + return err + } + + finalPrice := item.Amount * price + err = config.DB.WithContext(ctx). + Model(&model.RequestPickupItem{}). + Where("id = ?", item.ItemID). + Updates(map[string]interface{}{ + "estimated_amount": item.Amount, + "final_price": finalPrice, + }).Error + if err != nil { + return err + } + } + return nil +} diff --git a/internal/repositories/requestpickup_repo.go b/internal/repositories/requestpickup_repo.go index 76fd64e..9c6a57a 100644 --- a/internal/repositories/requestpickup_repo.go +++ b/internal/repositories/requestpickup_repo.go @@ -1,181 +1,181 @@ package repositories -import ( - "fmt" - "rijig/model" +// import ( +// "fmt" +// "rijig/model" - "gorm.io/gorm" -) +// "gorm.io/gorm" +// ) -type RequestPickupRepository interface { - CreateRequestPickup(request *model.RequestPickup) error - CreateRequestPickupItem(item *model.RequestPickupItem) error - FindRequestPickupByID(id string) (*model.RequestPickup, error) - FindAllRequestPickups(userId string) ([]model.RequestPickup, error) - FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error) - FindRequestPickupByAddressAndStatus(userId, status, method string) (*model.RequestPickup, error) - FindRequestPickupByStatus(userId, status, method string) (*model.RequestPickup, error) - GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) - GetAutomaticRequestPickupsForCollector() ([]model.RequestPickup, error) - GetManualReqMethodforCollect(collector_id string) ([]model.RequestPickup, error) - // SelectCollectorInRequest(userId string, collectorId string) error - UpdateRequestPickup(id string, request *model.RequestPickup) error - DeleteRequestPickup(id string) error -} +// type RequestPickupRepository interface { +// CreateRequestPickup(request *model.RequestPickup) error +// CreateRequestPickupItem(item *model.RequestPickupItem) error +// FindRequestPickupByID(id string) (*model.RequestPickup, error) +// FindAllRequestPickups(userId string) ([]model.RequestPickup, error) +// FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error) +// FindRequestPickupByAddressAndStatus(userId, status, method string) (*model.RequestPickup, error) +// FindRequestPickupByStatus(userId, status, method string) (*model.RequestPickup, error) +// GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) +// GetAutomaticRequestPickupsForCollector() ([]model.RequestPickup, error) +// GetManualReqMethodforCollect(collector_id string) ([]model.RequestPickup, error) +// // SelectCollectorInRequest(userId string, collectorId string) error +// UpdateRequestPickup(id string, request *model.RequestPickup) error +// DeleteRequestPickup(id string) error +// } -type requestPickupRepository struct { - DB *gorm.DB -} +// type requestPickupRepository struct { +// DB *gorm.DB +// } -func NewRequestPickupRepository(db *gorm.DB) RequestPickupRepository { - return &requestPickupRepository{DB: db} -} +// func NewRequestPickupRepository(db *gorm.DB) RequestPickupRepository { +// return &requestPickupRepository{DB: db} +// } -func (r *requestPickupRepository) CreateRequestPickup(request *model.RequestPickup) error { - if err := r.DB.Create(request).Error; err != nil { - return fmt.Errorf("failed to create request pickup: %v", err) - } - - for _, item := range request.RequestItems { - item.RequestPickupId = request.ID - if err := r.DB.Create(&item).Error; err != nil { - return fmt.Errorf("failed to create request pickup item: %v", err) - } - } - - return nil -} - -func (r *requestPickupRepository) CreateRequestPickupItem(item *model.RequestPickupItem) error { - if err := r.DB.Create(item).Error; err != nil { - return fmt.Errorf("failed to create request pickup item: %v", err) - } - return nil -} - -func (r *requestPickupRepository) FindRequestPickupByID(id string) (*model.RequestPickup, error) { - var request model.RequestPickup - err := r.DB.Preload("RequestItems").First(&request, "id = ?", id).Error - if err != nil { - return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) - } - return &request, nil -} - -func (r *requestPickupRepository) FindAllRequestPickups(userId string) ([]model.RequestPickup, error) { - var requests []model.RequestPickup - 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, method string) (*model.RequestPickup, error) { - var request model.RequestPickup - err := r.DB.Preload("Address").Where("user_id = ? AND status_pickup = ? AND request_method =?", userId, status, method).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) FindRequestPickupByStatus(userId, status, method string) (*model.RequestPickup, error) { - var request model.RequestPickup - err := r.DB.Where("user_id = ? AND status_pickup = ? AND request_method =?", userId, status, method).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 -} - -// func (r *requestPickupRepository) SelectCollectorInRequest(userId string, collectorId string) error { -// var request model.RequestPickup -// err := r.DB.Model(&model.RequestPickup{}). -// Where("user_id = ? AND status_pickup = ? AND request_method = ? AND collector_id IS NULL", userId, "waiting_collector", "manual"). -// First(&request).Error -// if err != nil { -// if err == gorm.ErrRecordNotFound { -// return fmt.Errorf("no matching request pickup found for user %s", userId) -// } -// return fmt.Errorf("failed to find request pickup: %v", err) +// func (r *requestPickupRepository) CreateRequestPickup(request *model.RequestPickup) error { +// if err := r.DB.Create(request).Error; err != nil { +// return fmt.Errorf("failed to create request pickup: %v", err) // } -// err = r.DB.Model(&model.RequestPickup{}). -// Where("id = ?", request.ID). -// Update("collector_id", collectorId). -// Error -// if err != nil { -// return fmt.Errorf("failed to update collector_id: %v", err) +// for _, item := range request.RequestItems { +// item.RequestPickupId = request.ID +// if err := r.DB.Create(&item).Error; err != nil { +// return fmt.Errorf("failed to create request pickup item: %v", err) +// } +// } + +// return nil +// } + +// func (r *requestPickupRepository) CreateRequestPickupItem(item *model.RequestPickupItem) error { +// if err := r.DB.Create(item).Error; err != nil { +// return fmt.Errorf("failed to create request pickup item: %v", err) // } // return nil // } -func (r *requestPickupRepository) DeleteRequestPickup(id string) error { +// func (r *requestPickupRepository) FindRequestPickupByID(id string) (*model.RequestPickup, error) { +// var request model.RequestPickup +// err := r.DB.Preload("RequestItems").First(&request, "id = ?", id).Error +// if err != nil { +// return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) +// } +// return &request, nil +// } - if err := r.DB.Where("request_pickup_id = ?", id).Delete(&model.RequestPickupItem{}).Error; err != nil { - return fmt.Errorf("failed to delete request pickup items: %v", err) - } +// func (r *requestPickupRepository) FindAllRequestPickups(userId string) ([]model.RequestPickup, error) { +// var requests []model.RequestPickup +// 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 +// } - err := r.DB.Delete(&model.RequestPickup{}, "id = ?", id).Error - if err != nil { - return fmt.Errorf("failed to delete request pickup: %v", err) - } - return 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) +// } -func (r *requestPickupRepository) GetAutomaticRequestPickupsForCollector() ([]model.RequestPickup, error) { - var requests []model.RequestPickup - err := r.DB.Preload("Address"). - Where("request_method = ? AND status_pickup = ? AND collector_id = ?", "otomatis", "waiting_collector", nil). - Find(&requests).Error - if err != nil { - return nil, fmt.Errorf("error fetching pickup requests: %v", err) - } - return requests, nil -} +// return requests, nil +// } -func (r *requestPickupRepository) GetManualReqMethodforCollect(collector_id string) ([]model.RequestPickup, error) { - var requests []model.RequestPickup - err := r.DB.Where("request_method = ? AND status_pickup = ? AND collector_id = ?", "otomatis", "waiting_collector", collector_id). - Find(&requests).Error - if err != nil { - return nil, fmt.Errorf("error fetching pickup requests: %v", err) - } - return requests, nil -} +// func (r *requestPickupRepository) FindRequestPickupByAddressAndStatus(userId, status, method string) (*model.RequestPickup, error) { +// var request model.RequestPickup +// err := r.DB.Preload("Address").Where("user_id = ? AND status_pickup = ? AND request_method =?", userId, status, method).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) GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) { - var items []model.RequestPickupItem +// func (r *requestPickupRepository) FindRequestPickupByStatus(userId, status, method string) (*model.RequestPickup, error) { +// var request model.RequestPickup +// err := r.DB.Where("user_id = ? AND status_pickup = ? AND request_method =?", userId, status, method).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 +// } - err := r.DB.Preload("TrashCategory").Where("request_pickup_id = ?", requestPickupId).Find(&items).Error - if err != nil { - return nil, fmt.Errorf("error fetching request pickup items: %v", err) - } - return items, 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 +// } + +// // func (r *requestPickupRepository) SelectCollectorInRequest(userId string, collectorId string) error { +// // var request model.RequestPickup +// // err := r.DB.Model(&model.RequestPickup{}). +// // Where("user_id = ? AND status_pickup = ? AND request_method = ? AND collector_id IS NULL", userId, "waiting_collector", "manual"). +// // First(&request).Error +// // if err != nil { +// // if err == gorm.ErrRecordNotFound { +// // return fmt.Errorf("no matching request pickup found for user %s", userId) +// // } +// // return fmt.Errorf("failed to find request pickup: %v", err) +// // } + +// // err = r.DB.Model(&model.RequestPickup{}). +// // Where("id = ?", request.ID). +// // Update("collector_id", collectorId). +// // Error +// // if err != nil { +// // return fmt.Errorf("failed to update collector_id: %v", err) +// // } +// // return nil +// // } + +// func (r *requestPickupRepository) DeleteRequestPickup(id string) error { + +// if err := r.DB.Where("request_pickup_id = ?", id).Delete(&model.RequestPickupItem{}).Error; err != nil { +// return fmt.Errorf("failed to delete request pickup items: %v", err) +// } + +// err := r.DB.Delete(&model.RequestPickup{}, "id = ?", id).Error +// if err != nil { +// return fmt.Errorf("failed to delete request pickup: %v", err) +// } +// return nil +// } + +// func (r *requestPickupRepository) GetAutomaticRequestPickupsForCollector() ([]model.RequestPickup, error) { +// var requests []model.RequestPickup +// err := r.DB.Preload("Address"). +// Where("request_method = ? AND status_pickup = ? AND collector_id = ?", "otomatis", "waiting_collector", nil). +// Find(&requests).Error +// if err != nil { +// return nil, fmt.Errorf("error fetching pickup requests: %v", err) +// } +// return requests, nil +// } + +// func (r *requestPickupRepository) GetManualReqMethodforCollect(collector_id string) ([]model.RequestPickup, error) { +// var requests []model.RequestPickup +// err := r.DB.Where("request_method = ? AND status_pickup = ? AND collector_id = ?", "otomatis", "waiting_collector", collector_id). +// Find(&requests).Error +// if err != nil { +// return nil, fmt.Errorf("error fetching pickup requests: %v", err) +// } +// return requests, nil +// } + +// 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 { +// return nil, fmt.Errorf("error fetching request pickup items: %v", err) +// } +// return items, nil +// } diff --git a/internal/repositories/trash_repo.go b/internal/repositories/trash_repo.go index 4b88d35..53815f9 100644 --- a/internal/repositories/trash_repo.go +++ b/internal/repositories/trash_repo.go @@ -20,6 +20,7 @@ type TrashRepository interface { FindCategoryId(id string) (*model.TrashCategory, error) GetTrashCategoryByName(name string) (*model.TrashCategory, error) GetTrashDetailByID(id string) (*model.TrashDetail, error) + GetTrashCategoryByID(ctx context.Context, id string) (*model.TrashCategory, error) GetDetailsByCategoryID(categoryID string) ([]model.TrashDetail, error) UpdateCategoryName(id string, newName string) error UpdateCategory(id string, updateTrashCategory *model.TrashCategory) (*model.TrashCategory, error) @@ -68,6 +69,15 @@ func (r *trashRepository) GetCategoryByID(id string) (*model.TrashCategory, erro return &category, nil } +// spesial code +func (r *trashRepository) GetTrashCategoryByID(ctx context.Context, id string) (*model.TrashCategory, error) { + var trash model.TrashCategory + if err := config.DB.WithContext(ctx).First(&trash, "id = ?", id).Error; err != nil { + return nil, err + } + return &trash, nil +} + func (r *trashRepository) FindCategoryId(id string) (*model.TrashCategory, error) { var category model.TrashCategory diff --git a/internal/services/pickup_history_service.go b/internal/services/pickup_history_service.go new file mode 100644 index 0000000..5362664 --- /dev/null +++ b/internal/services/pickup_history_service.go @@ -0,0 +1,36 @@ +package services + +import ( + "context" + "time" + "rijig/model" + "rijig/internal/repositories" +) + +type PickupStatusHistoryService interface { + LogStatusChange(ctx context.Context, requestID, status, changedByID, changedByRole string) error + GetStatusHistory(ctx context.Context, requestID string) ([]model.PickupStatusHistory, error) +} + +type pickupStatusHistoryService struct { + repo repositories.PickupStatusHistoryRepository +} + +func NewPickupStatusHistoryService(repo repositories.PickupStatusHistoryRepository) PickupStatusHistoryService { + return &pickupStatusHistoryService{repo: repo} +} + +func (s *pickupStatusHistoryService) LogStatusChange(ctx context.Context, requestID, status, changedByID, changedByRole string) error { + history := model.PickupStatusHistory{ + RequestID: requestID, + Status: status, + ChangedAt: time.Now(), + ChangedByID: changedByID, + ChangedByRole: changedByRole, + } + return s.repo.CreateStatusHistory(ctx, history) +} + +func (s *pickupStatusHistoryService) GetStatusHistory(ctx context.Context, requestID string) ([]model.PickupStatusHistory, error) { + return s.repo.GetStatusHistoryByRequestID(ctx, requestID) +} diff --git a/internal/services/pickup_maching_service.go b/internal/services/pickup_maching_service.go new file mode 100644 index 0000000..ae36e1a --- /dev/null +++ b/internal/services/pickup_maching_service.go @@ -0,0 +1,146 @@ +package services + +import ( + "context" + "fmt" + "rijig/dto" + "rijig/internal/repositories" + "rijig/utils" +) + +type PickupMatchingService interface { + FindNearbyCollectorsForPickup(ctx context.Context, pickupID string) ([]dto.NearbyCollectorDTO, error) + FindAvailableRequestsForCollector(ctx context.Context, collectorID string) ([]dto.PickupRequestForCollectorDTO, error) +} + +type pickupMatchingService struct { + pickupRepo repositories.RequestPickupRepository + collectorRepo repositories.CollectorRepository +} + +func NewPickupMatchingService(pickupRepo repositories.RequestPickupRepository, collectorRepo repositories.CollectorRepository) PickupMatchingService { + return &pickupMatchingService{ + pickupRepo: pickupRepo, + collectorRepo: collectorRepo, + } +} + +func (s *pickupMatchingService) FindNearbyCollectorsForPickup(ctx context.Context, pickupID string) ([]dto.NearbyCollectorDTO, error) { + pickup, err := s.pickupRepo.GetPickupWithItemsAndAddress(ctx, pickupID) + if err != nil { + return nil, fmt.Errorf("pickup tidak ditemukan: %w", err) + } + + userCoord := utils.Coord{ + Lat: pickup.Address.Latitude, + Lon: pickup.Address.Longitude, + } + + requestedTrash := make(map[string]bool) + for _, item := range pickup.RequestItems { + requestedTrash[item.TrashCategoryId] = true + } + + collectors, err := s.collectorRepo.GetActiveCollectorsWithTrashAndAddress(ctx) + if err != nil { + return nil, fmt.Errorf("gagal mengambil data collector: %w", err) + } + + var result []dto.NearbyCollectorDTO + for _, col := range collectors { + coord := utils.Coord{ + Lat: col.Address.Latitude, + Lon: col.Address.Longitude, + } + + _, km := utils.Distance(userCoord, coord) + if km > 10 { + continue + } + + var matchedTrash []string + for _, item := range col.AvaibleTrashByCollector { + if requestedTrash[item.TrashCategoryID] { + matchedTrash = append(matchedTrash, item.TrashCategoryID) + } + } + + if len(matchedTrash) == 0 { + continue + } + + result = append(result, dto.NearbyCollectorDTO{ + CollectorID: col.ID, + Name: col.User.Name, + Phone: col.User.Phone, + Rating: col.Rating, + Latitude: col.Address.Latitude, + Longitude: col.Address.Longitude, + DistanceKm: km, + MatchedTrash: matchedTrash, + }) + } + + return result, nil +} + +// terdpaat error seperti ini: "undefined: dto.PickupRequestForCollectorDTO" dan seprti ini: s.collectorRepo.GetCollectorWithAddressAndTrash undefined (type repositories.CollectorRepository has no field or method GetCollectorWithAddressAndTrash) pada kode berikut: + +func (s *pickupMatchingService) FindAvailableRequestsForCollector(ctx context.Context, collectorID string) ([]dto.PickupRequestForCollectorDTO, error) { + collector, err := s.collectorRepo.GetCollectorWithAddressAndTrash(ctx, collectorID) + if err != nil { + return nil, fmt.Errorf("collector tidak ditemukan: %w", err) + } + + pickupList, err := s.pickupRepo.GetAllAutomaticRequestsWithAddress(ctx) + if err != nil { + return nil, fmt.Errorf("gagal mengambil pickup otomatis: %w", err) + } + + collectorCoord := utils.Coord{ + Lat: collector.Address.Latitude, + Lon: collector.Address.Longitude, + } + + // map trash collector + collectorTrash := make(map[string]bool) + for _, t := range collector.AvaibleTrashByCollector { + collectorTrash[t.TrashCategoryID] = true + } + + var results []dto.PickupRequestForCollectorDTO + for _, p := range pickupList { + if p.StatusPickup != "waiting_collector" { + continue + } + coord := utils.Coord{ + Lat: p.Address.Latitude, + Lon: p.Address.Longitude, + } + _, km := utils.Distance(collectorCoord, coord) + if km > 10 { + continue + } + + match := false + var matchedTrash []string + for _, item := range p.RequestItems { + if collectorTrash[item.TrashCategoryId] { + match = true + matchedTrash = append(matchedTrash, item.TrashCategoryId) + } + } + if match { + results = append(results, dto.PickupRequestForCollectorDTO{ + PickupID: p.ID, + UserID: p.UserId, + Latitude: p.Address.Latitude, + Longitude: p.Address.Longitude, + DistanceKm: km, + MatchedTrash: matchedTrash, + }) + } + } + + return results, nil +} diff --git a/internal/services/rating_service.go b/internal/services/rating_service.go new file mode 100644 index 0000000..e5d7c55 --- /dev/null +++ b/internal/services/rating_service.go @@ -0,0 +1,43 @@ +package services + +import ( + "context" + "time" + "rijig/dto" + "rijig/internal/repositories" + "rijig/model" +) + +type PickupRatingService interface { + CreateRating(ctx context.Context, userID, pickupID, collectorID string, input dto.CreatePickupRatingDTO) error + GetRatingsByCollector(ctx context.Context, collectorID string) ([]model.PickupRating, error) + GetAverageRating(ctx context.Context, collectorID string) (float32, error) +} + +type pickupRatingService struct { + repo repositories.PickupRatingRepository +} + +func NewPickupRatingService(repo repositories.PickupRatingRepository) PickupRatingService { + return &pickupRatingService{repo: repo} +} + +func (s *pickupRatingService) CreateRating(ctx context.Context, userID, pickupID, collectorID string, input dto.CreatePickupRatingDTO) error { + rating := model.PickupRating{ + RequestID: pickupID, + UserID: userID, + CollectorID: collectorID, + Rating: input.Rating, + Feedback: input.Feedback, + CreatedAt: time.Now(), + } + return s.repo.CreateRating(ctx, rating) +} + +func (s *pickupRatingService) GetRatingsByCollector(ctx context.Context, collectorID string) ([]model.PickupRating, error) { + return s.repo.GetRatingsByCollector(ctx, collectorID) +} + +func (s *pickupRatingService) GetAverageRating(ctx context.Context, collectorID string) (float32, error) { + return s.repo.CalculateAverageRating(ctx, collectorID) +} diff --git a/internal/services/request_pickup_service.go b/internal/services/request_pickup_service.go new file mode 100644 index 0000000..a5eb89f --- /dev/null +++ b/internal/services/request_pickup_service.go @@ -0,0 +1,139 @@ +package services + +import ( + "context" + "fmt" + "rijig/dto" + "rijig/internal/repositories" + "rijig/model" + "time" +) + +type RequestPickupService interface { + ConvertCartToRequestPickup(ctx context.Context, userID string, req dto.RequestPickupDTO) error + AssignCollectorToRequest(ctx context.Context, pickupID string, req dto.SelectCollectorDTO) error + FindRequestsAssignedToCollector(ctx context.Context, collectorID string) ([]dto.AssignedPickupDTO, error) + ConfirmPickupByCollector(ctx context.Context, pickupID string, confirmedAt time.Time) error + UpdatePickupStatusToPickingUp(ctx context.Context, pickupID string) error + UpdateActualPickupItems(ctx context.Context, pickupID string, items []dto.UpdateRequestPickupItemDTO) error +} + +type requestPickupService struct { + trashRepo repositories.TrashRepository + pickupRepo repositories.RequestPickupRepository + cartService CartService + historyRepo repositories.PickupStatusHistoryRepository +} + +func NewRequestPickupService(trashRepo repositories.TrashRepository, pickupRepo repositories.RequestPickupRepository, cartService CartService, historyRepo repositories.PickupStatusHistoryRepository) RequestPickupService { + return &requestPickupService{ + trashRepo: trashRepo, + pickupRepo: pickupRepo, + cartService: cartService, + historyRepo: historyRepo, + } +} + +func (s *requestPickupService) ConvertCartToRequestPickup(ctx context.Context, userID string, req dto.RequestPickupDTO) error { + cart, err := s.cartService.GetCart(ctx, userID) + if err != nil || len(cart.CartItems) == 0 { + return fmt.Errorf("cart kosong atau tidak ditemukan") + } + + var requestItems []model.RequestPickupItem + for _, item := range cart.CartItems { + trash, err := s.trashRepo.GetTrashCategoryByID(ctx, item.TrashID) + if err != nil { + continue + } + subtotal := float64(item.Amount) * trash.EstimatedPrice + + requestItems = append(requestItems, model.RequestPickupItem{ + TrashCategoryId: item.TrashID, + EstimatedAmount: float64(item.Amount), + EstimatedPricePerKg: trash.EstimatedPrice, + EstimatedSubtotalPrice: subtotal, + }) + } + + if len(requestItems) == 0 { + return fmt.Errorf("tidak ada item valid dalam cart") + } + + pickup := model.RequestPickup{ + UserId: userID, + AddressId: req.AddressID, + RequestMethod: req.RequestMethod, + Notes: req.Notes, + StatusPickup: "waiting_collector", + RequestItems: requestItems, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if err := s.pickupRepo.CreateRequestPickup(ctx, &pickup); err != nil { + return fmt.Errorf("gagal menyimpan request pickup: %w", err) + } + + if err := s.cartService.ClearCart(ctx, userID); err != nil { + return fmt.Errorf("request berhasil, tapi gagal hapus cart: %w", err) + } + + return nil +} + +func (s *requestPickupService) AssignCollectorToRequest(ctx context.Context, pickupID string, req dto.SelectCollectorDTO) error { + if req.CollectorID == "" { + return fmt.Errorf("collector_id tidak boleh kosong") + } + return s.pickupRepo.UpdateCollectorID(ctx, pickupID, req.CollectorID) +} + +func (s *requestPickupService) FindRequestsAssignedToCollector(ctx context.Context, collectorID string) ([]dto.AssignedPickupDTO, error) { + pickups, err := s.pickupRepo.GetRequestsAssignedToCollector(ctx, collectorID) + if err != nil { + return nil, err + } + + var result []dto.AssignedPickupDTO + for _, p := range pickups { + var matchedTrash []string + for _, item := range p.RequestItems { + matchedTrash = append(matchedTrash, item.TrashCategoryId) + } + + result = append(result, dto.AssignedPickupDTO{ + PickupID: p.ID, + UserID: p.UserId, + UserName: p.User.Name, + Latitude: p.Address.Latitude, + Longitude: p.Address.Longitude, + Notes: p.Notes, + MatchedTrash: matchedTrash, + }) + } + + return result, nil +} + +func (s *requestPickupService) ConfirmPickupByCollector(ctx context.Context, pickupID string, confirmedAt time.Time) error { + return s.pickupRepo.UpdatePickupStatusAndConfirmationTime(ctx, pickupID, "confirmed_by_collector", confirmedAt) +} + +func (s *requestPickupService) UpdatePickupStatusToPickingUp(ctx context.Context, pickupID string) error { + err := s.pickupRepo.UpdatePickupStatus(ctx, pickupID, "collector_are_picking_up") + if err != nil { + return err + } + return s.historyRepo.CreateStatusHistory(ctx, model.PickupStatusHistory{ + RequestID: pickupID, + Status: "collector_are_picking_up", + ChangedAt: time.Now(), + ChangedByID: "collector", + ChangedByRole: "collector", + }) +} + +func (s *requestPickupService) UpdateActualPickupItems(ctx context.Context, pickupID string, items []dto.UpdateRequestPickupItemDTO) error { + return s.pickupRepo.UpdateRequestPickupItemsAmountAndPrice(ctx, pickupID, items) +} diff --git a/internal/services/requestpickup_service.go b/internal/services/requestpickup_service.go index 8656d06..63917c0 100644 --- a/internal/services/requestpickup_service.go +++ b/internal/services/requestpickup_service.go @@ -1,349 +1,349 @@ package services -import ( - "fmt" - "rijig/dto" - "rijig/internal/repositories" - "rijig/model" - "rijig/utils" -) +// import ( +// "fmt" +// "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(userid string) ([]dto.ResponseRequestPickup, error) - // GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) - // SelectCollectorInRequest(userId, collectorId string) error -} +// type RequestPickupService interface { +// // CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) +// // GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) +// // GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) +// // GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) +// // SelectCollectorInRequest(userId, collectorId string) error +// } -type requestPickupService struct { - repo repositories.RequestPickupRepository - repoColl repositories.CollectorRepository - repoAddress repositories.AddressRepository - repoTrash repositories.TrashRepository - repoUser repositories.UserProfilRepository -} +// type requestPickupService struct { +// repo repositories.RequestPickupRepository +// repoColl repositories.CollectorRepository +// repoAddress repositories.AddressRepository +// repoTrash repositories.TrashRepository +// repoUser repositories.UserProfilRepository +// } -func NewRequestPickupService(repo repositories.RequestPickupRepository, - repoColl repositories.CollectorRepository, - repoAddress repositories.AddressRepository, - repoTrash repositories.TrashRepository, - repoUser repositories.UserProfilRepository) RequestPickupService { - return &requestPickupService{repo: repo, repoColl: repoColl, repoAddress: repoAddress, repoTrash: repoTrash, repoUser: repoUser} -} +// func NewRequestPickupService(repo repositories.RequestPickupRepository, +// repoColl repositories.CollectorRepository, +// repoAddress repositories.AddressRepository, +// repoTrash repositories.TrashRepository, +// repoUser repositories.UserProfilRepository) RequestPickupService { +// return &requestPickupService{repo: repo, repoColl: repoColl, repoAddress: repoAddress, repoTrash: repoTrash, repoUser: repoUser} +// } -func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) { +// func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) { - errors, valid := request.ValidateRequestPickup() - if !valid { - return nil, fmt.Errorf("validation errors: %v", errors) - } - - _, 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_collector", "otomatis") - if err != nil { - return nil, fmt.Errorf("error checking for existing request pickup: %v", err) - } - if existingRequest != nil { - return nil, fmt.Errorf("there is already a pending pickup request for address %s", request.AddressID) - } - - modelRequest := model.RequestPickup{ - UserId: UserId, - AddressId: request.AddressID, - EvidenceImage: request.EvidenceImage, - RequestMethod: request.RequestMethod, - } - - err = s.repo.CreateRequestPickup(&modelRequest) - if err != nil { - 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: createdAt, - UpdatedAt: updatedAt, - } - - for _, item := range request.RequestItems { - - findTrashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryID) - if err != nil { - return nil, fmt.Errorf("trash category with ID %s not found", item.TrashCategoryID) - } - - modelItem := model.RequestPickupItem{ - RequestPickupId: modelRequest.ID, - TrashCategoryId: findTrashCategory.ID, - EstimatedAmount: item.EstimatedAmount, - } - err = s.repo.CreateRequestPickupItem(&modelItem) - if err != nil { - return nil, fmt.Errorf("failed to create request pickup item: %v", err) - } - - response.RequestItems = append(response.RequestItems, dto.ResponseRequestPickupItem{ - ID: modelItem.ID, - TrashCategory: []dto.ResponseTrashCategoryDTO{{Name: findTrashCategory.Name, Icon: findTrashCategory.Icon}}, - EstimatedAmount: modelItem.EstimatedAmount, - }) - } - - return response, nil -} - -func (s *requestPickupService) GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) { - - request, err := s.repo.FindRequestPickupByID(id) - if err != nil { - 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: createdAt, - UpdatedAt: updatedAt, - } - - return response, nil -} - -func (s *requestPickupService) GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) { - - 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: createdAt, - UpdatedAt: updatedAt, - }) - } - - return response, nil -} - -// func (s *requestPickupService) GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) { -// requests, err := s.repo.GetAutomaticRequestPickupsForCollector() -// if err != nil { -// return nil, fmt.Errorf("error retrieving automatic pickup requests: %v", err) +// errors, valid := request.ValidateRequestPickup() +// if !valid { +// return nil, fmt.Errorf("validation errors: %v", errors) // } -// var response []dto.ResponseRequestPickup +// _, err := s.repoAddress.FindAddressByID(request.AddressID) +// if err != nil { +// return nil, fmt.Errorf("address with ID %s not found", request.AddressID) +// } -// for _, req := range requests { +// existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_collector", "otomatis") +// if err != nil { +// return nil, fmt.Errorf("error checking for existing request pickup: %v", err) +// } +// if existingRequest != nil { +// return nil, fmt.Errorf("there is already a pending pickup request for address %s", request.AddressID) +// } -// collector, err := s.repoColl.FindCollectorById(collectorId) +// modelRequest := model.RequestPickup{ +// UserId: UserId, +// AddressId: request.AddressID, +// EvidenceImage: request.EvidenceImage, +// RequestMethod: request.RequestMethod, +// } + +// err = s.repo.CreateRequestPickup(&modelRequest) +// if err != nil { +// 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: createdAt, +// UpdatedAt: updatedAt, +// } + +// for _, item := range request.RequestItems { + +// findTrashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryID) // if err != nil { -// return nil, fmt.Errorf("error fetching collector data: %v", err) +// return nil, fmt.Errorf("trash category with ID %s not found", item.TrashCategoryID) // } -// _, distance := utils.Distance( -// utils.Coord{Lat: collector.Address.Latitude, Lon: collector.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"), -// } - -// user, err := s.repoUser.FindByID(req.UserId) -// if err != nil { -// return nil, fmt.Errorf("error fetching user data: %v", err) -// } -// mappedRequest.User = []dto.UserResponseDTO{ -// { -// Name: user.Name, -// Phone: user.Phone, -// }, -// } - -// address, err := s.repoAddress.FindAddressByID(req.AddressId) -// if err != nil { -// return nil, fmt.Errorf("error fetching address data: %v", err) -// } -// mappedRequest.Address = []dto.AddressResponseDTO{ -// { -// District: address.District, -// Village: address.Village, -// Detail: address.Detail, -// }, -// } - -// 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 { -// trashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryId) -// if err != nil { -// return nil, fmt.Errorf("error fetching trash category: %v", err) -// } - -// mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{ -// ID: item.ID, -// TrashCategory: []dto.ResponseTrashCategoryDTO{{ -// Name: trashCategory.Name, -// Icon: trashCategory.Icon, -// }}, -// EstimatedAmount: item.EstimatedAmount, -// }) -// } - -// mappedRequest.RequestItems = mappedRequestItems - -// response = append(response, mappedRequest) +// modelItem := model.RequestPickupItem{ +// RequestPickupId: modelRequest.ID, +// TrashCategoryId: findTrashCategory.ID, +// EstimatedAmount: item.EstimatedAmount, // } +// err = s.repo.CreateRequestPickupItem(&modelItem) +// if err != nil { +// return nil, fmt.Errorf("failed to create request pickup item: %v", err) +// } + +// response.RequestItems = append(response.RequestItems, dto.ResponseRequestPickupItem{ +// ID: modelItem.ID, +// TrashCategory: []dto.ResponseTrashCategoryDTO{{Name: findTrashCategory.Name, Icon: findTrashCategory.Icon}}, +// EstimatedAmount: modelItem.EstimatedAmount, +// }) // } // return response, nil // } -// func (s *requestPickupService) GetManualRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) { +// func (s *requestPickupService) GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) { -// collector, err := s.repoColl.FindCollectorById(collectorId) +// request, err := s.repo.FindRequestPickupByID(id) // if err != nil { -// return nil, fmt.Errorf("error fetching collector data: %v", err) +// return nil, fmt.Errorf("error fetching request pickup with ID %s: %v", id, err) // } -// requests, err := s.repo.GetManualReqMethodforCollect(collector.ID) + +// 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: createdAt, +// UpdatedAt: updatedAt, +// } + +// return response, nil +// } + +// func (s *requestPickupService) GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) { + +// requests, err := s.repo.FindAllRequestPickups(userid) // if err != nil { -// return nil, fmt.Errorf("error retrieving manual pickup requests: %v", err) +// return nil, fmt.Errorf("error fetching all request pickups: %v", err) // } // var response []dto.ResponseRequestPickup - -// for _, req := range requests { - -// createdAt, _ := utils.FormatDateToIndonesianFormat(req.CreatedAt) -// updatedAt, _ := utils.FormatDateToIndonesianFormat(req.UpdatedAt) - -// mappedRequest := dto.ResponseRequestPickup{ -// ID: req.ID, -// UserId: req.UserId, -// AddressID: req.AddressId, -// EvidenceImage: req.EvidenceImage, -// StatusPickup: req.StatusPickup, +// 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: createdAt, // UpdatedAt: updatedAt, -// } - -// user, err := s.repoUser.FindByID(req.UserId) -// if err != nil { -// return nil, fmt.Errorf("error fetching user data: %v", err) -// } -// mappedRequest.User = []dto.UserResponseDTO{ -// { -// Name: user.Name, -// Phone: user.Phone, -// }, -// } - -// address, err := s.repoAddress.FindAddressByID(req.AddressId) -// if err != nil { -// return nil, fmt.Errorf("error fetching address data: %v", err) -// } -// mappedRequest.Address = []dto.AddressResponseDTO{ -// { -// District: address.District, -// Village: address.Village, -// Detail: address.Detail, -// }, -// } - -// 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 { - -// trashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryId) -// if err != nil { -// return nil, fmt.Errorf("error fetching trash category: %v", err) -// } - -// mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{ -// ID: item.ID, -// TrashCategory: []dto.ResponseTrashCategoryDTO{{ -// Name: trashCategory.Name, -// Icon: trashCategory.Icon, -// }}, -// EstimatedAmount: item.EstimatedAmount, -// }) -// } - -// mappedRequest.RequestItems = mappedRequestItems - -// response = append(response, mappedRequest) +// }) // } // return response, nil // } -// func (s *requestPickupService) SelectCollectorInRequest(userId, collectorId string) error { +// // func (s *requestPickupService) GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) { +// // requests, err := s.repo.GetAutomaticRequestPickupsForCollector() +// // if err != nil { +// // return nil, fmt.Errorf("error retrieving automatic pickup requests: %v", err) +// // } -// request, err := s.repo.FindRequestPickupByStatus(userId, "waiting_collector", "manual") -// if err != nil { -// return fmt.Errorf("request pickup not found: %v", err) -// } +// // var response []dto.ResponseRequestPickup -// if request.StatusPickup != "waiting_collector" && request.RequestMethod != "manual" { -// return fmt.Errorf("pickup request is not in 'waiting_collector' status and not 'manual' method") -// } +// // for _, req := range requests { -// collector, err := s.repoColl.FindCollectorByIdWithoutAddr(collectorId) -// if err != nil { -// return fmt.Errorf("collector tidak ditemukan: %v", err) -// } +// // collector, err := s.repoColl.FindCollectorById(collectorId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching collector data: %v", err) +// // } -// request.CollectorID = &collector.ID +// // _, distance := utils.Distance( +// // utils.Coord{Lat: collector.Address.Latitude, Lon: collector.Address.Longitude}, +// // utils.Coord{Lat: req.Address.Latitude, Lon: req.Address.Longitude}, +// // ) -// err = s.repo.UpdateRequestPickup(request.ID, request) -// if err != nil { -// return fmt.Errorf("failed to update request pickup: %v", err) -// } +// // if distance <= 20 { -// return nil -// } +// // 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"), +// // } + +// // user, err := s.repoUser.FindByID(req.UserId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching user data: %v", err) +// // } +// // mappedRequest.User = []dto.UserResponseDTO{ +// // { +// // Name: user.Name, +// // Phone: user.Phone, +// // }, +// // } + +// // address, err := s.repoAddress.FindAddressByID(req.AddressId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching address data: %v", err) +// // } +// // mappedRequest.Address = []dto.AddressResponseDTO{ +// // { +// // District: address.District, +// // Village: address.Village, +// // Detail: address.Detail, +// // }, +// // } + +// // 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 { +// // trashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching trash category: %v", err) +// // } + +// // mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{ +// // ID: item.ID, +// // TrashCategory: []dto.ResponseTrashCategoryDTO{{ +// // Name: trashCategory.Name, +// // Icon: trashCategory.Icon, +// // }}, +// // EstimatedAmount: item.EstimatedAmount, +// // }) +// // } + +// // mappedRequest.RequestItems = mappedRequestItems + +// // response = append(response, mappedRequest) +// // } +// // } + +// // return response, nil +// // } + +// // func (s *requestPickupService) GetManualRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) { + +// // collector, err := s.repoColl.FindCollectorById(collectorId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching collector data: %v", err) +// // } +// // requests, err := s.repo.GetManualReqMethodforCollect(collector.ID) +// // if err != nil { +// // return nil, fmt.Errorf("error retrieving manual pickup requests: %v", err) +// // } + +// // var response []dto.ResponseRequestPickup + +// // for _, req := range requests { + +// // createdAt, _ := utils.FormatDateToIndonesianFormat(req.CreatedAt) +// // updatedAt, _ := utils.FormatDateToIndonesianFormat(req.UpdatedAt) + +// // mappedRequest := dto.ResponseRequestPickup{ +// // ID: req.ID, +// // UserId: req.UserId, +// // AddressID: req.AddressId, +// // EvidenceImage: req.EvidenceImage, +// // StatusPickup: req.StatusPickup, +// // CreatedAt: createdAt, +// // UpdatedAt: updatedAt, +// // } + +// // user, err := s.repoUser.FindByID(req.UserId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching user data: %v", err) +// // } +// // mappedRequest.User = []dto.UserResponseDTO{ +// // { +// // Name: user.Name, +// // Phone: user.Phone, +// // }, +// // } + +// // address, err := s.repoAddress.FindAddressByID(req.AddressId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching address data: %v", err) +// // } +// // mappedRequest.Address = []dto.AddressResponseDTO{ +// // { +// // District: address.District, +// // Village: address.Village, +// // Detail: address.Detail, +// // }, +// // } + +// // 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 { + +// // trashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryId) +// // if err != nil { +// // return nil, fmt.Errorf("error fetching trash category: %v", err) +// // } + +// // mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{ +// // ID: item.ID, +// // TrashCategory: []dto.ResponseTrashCategoryDTO{{ +// // Name: trashCategory.Name, +// // Icon: trashCategory.Icon, +// // }}, +// // EstimatedAmount: item.EstimatedAmount, +// // }) +// // } + +// // mappedRequest.RequestItems = mappedRequestItems + +// // response = append(response, mappedRequest) +// // } + +// // return response, nil +// // } + +// // func (s *requestPickupService) SelectCollectorInRequest(userId, collectorId string) error { + +// // request, err := s.repo.FindRequestPickupByStatus(userId, "waiting_collector", "manual") +// // if err != nil { +// // return fmt.Errorf("request pickup not found: %v", err) +// // } + +// // if request.StatusPickup != "waiting_collector" && request.RequestMethod != "manual" { +// // return fmt.Errorf("pickup request is not in 'waiting_collector' status and not 'manual' method") +// // } + +// // collector, err := s.repoColl.FindCollectorByIdWithoutAddr(collectorId) +// // if err != nil { +// // return fmt.Errorf("collector tidak ditemukan: %v", err) +// // } + +// // request.CollectorID = &collector.ID + +// // err = s.repo.UpdateRequestPickup(request.ID, request) +// // if err != nil { +// // return fmt.Errorf("failed to update request pickup: %v", err) +// // } + +// // return nil +// // } diff --git a/model/pickup_history_model.go b/model/pickup_history_model.go new file mode 100644 index 0000000..71c2504 --- /dev/null +++ b/model/pickup_history_model.go @@ -0,0 +1,12 @@ +package model + +import "time" + +type PickupStatusHistory struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + RequestID string `gorm:"not null" json:"request_id"` + Status string `gorm:"not null" json:"status"` + ChangedAt time.Time `gorm:"not null" json:"changed_at"` + ChangedByID string `gorm:"not null" json:"changed_by_id"` + ChangedByRole string `gorm:"not null" json:"changed_by_role"` +} diff --git a/model/rating_model.go b/model/rating_model.go new file mode 100644 index 0000000..8cceafb --- /dev/null +++ b/model/rating_model.go @@ -0,0 +1,13 @@ +package model + +import "time" + +type PickupRating struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + RequestID string `gorm:"not null;unique" json:"request_id"` + UserID string `gorm:"not null" json:"user_id"` + CollectorID string `gorm:"not null" json:"collector_id"` + Rating float32 `gorm:"not null" json:"rating"` + Feedback string `json:"feedback,omitempty"` + CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"` +} diff --git a/model/requestpickup_model.go b/model/requestpickup_model.go index 13402ca..0f7ecb8 100644 --- a/model/requestpickup_model.go +++ b/model/requestpickup_model.go @@ -7,28 +7,30 @@ 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"` - User User `gorm:"foreignKey:UserId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"` + User *User `gorm:"foreignKey:UserId" json:"user"` AddressId string `gorm:"not null" json:"address_id"` - Address Address `gorm:"foreignKey:AddressId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"address"` + Address *Address `gorm:"foreignKey:AddressId" json:"address"` RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"` - EvidenceImage string `json:"evidence_image"` Notes string `json:"notes"` StatusPickup string `gorm:"default:'waiting_collector'" json:"status_pickup"` CollectorID *string `gorm:"type:uuid" json:"collector_id,omitempty"` - Collector Collector `gorm:"foreignKey:CollectorID;constraint:OnDelete:CASCADE;" json:"collector"` + Collector *Collector `gorm:"foreignKey:CollectorID;references:ID" json:"collector,omitempty"` ConfirmedByCollectorAt *time.Time `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"` + RequestMethod string `gorm:"not null" json:"request_method"` // manual / otomatis + FinalPrice float64 `json:"final_price"` // diisi setelah collector update berat + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" 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"` - 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;" json:"trash_category"` - 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" json:"-"` + TrashCategoryId string `gorm:"not null" json:"trash_category_id"` + TrashCategory *TrashCategory `gorm:"foreignKey:TrashCategoryId" json:"trash_category"` + EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"` + EstimatedPricePerKg float64 `gorm:"not null" json:"estimated_price_per_kg"` // harga pada saat itu + EstimatedSubtotalPrice float64 `gorm:"not null" json:"estimated_subtotal_price"` // amount * price per kg } // request_method { diff --git a/presentation/collector_route.go b/presentation/collector_route.go index 06f200a..0114967 100644 --- a/presentation/collector_route.go +++ b/presentation/collector_route.go @@ -30,8 +30,15 @@ func CollectorRouter(api fiber.Router) { // Middleware Auth dan Role // Inisialisasi repository dan service - collectorRepo := repositories.NewCollectorRepository() + pickupRepo := repositories.NewRequestPickupRepository() trashRepo := repositories.NewTrashRepository(config.DB) + historyRepo := repositories.NewPickupStatusHistoryRepository() + cartService := services.NewCartService() + + pickupService := services.NewRequestPickupService(trashRepo, pickupRepo, cartService, historyRepo) + pickupHandler := handler.NewRequestPickupHandler(pickupService) + collectorRepo := repositories.NewCollectorRepository() + // trashRepo := repositories.NewTrashRepository(config.DB) collectorService := services.NewCollectorService(collectorRepo, trashRepo) collectorHandler := handler.NewCollectorHandler(collectorService) @@ -43,7 +50,9 @@ func CollectorRouter(api fiber.Router) { collectors.Post("/", collectorHandler.CreateCollector) // Create collector collectors.Post("/:id/trash", collectorHandler.AddTrashToCollector) // Add trash to collector collectors.Get("/:id", collectorHandler.GetCollectorByID) // Get collector by ID - collectors.Get("/", collectorHandler.GetCollectorByUserID) // Get collector by ID + collectors.Get("/", collectorHandler.GetCollectorByUserID) + collectors.Get("/pickup/assigned-to-me", pickupHandler.GetAssignedPickup) + // Get collector by ID collectors.Patch("/:id", collectorHandler.UpdateCollector) // Update collector fields collectors.Patch("/:id/trash", collectorHandler.UpdateTrash) collectors.Patch("/:id/job-status", collectorHandler.UpdateJobStatus) diff --git a/presentation/pickup_matching_route.go b/presentation/pickup_matching_route.go new file mode 100644 index 0000000..025252b --- /dev/null +++ b/presentation/pickup_matching_route.go @@ -0,0 +1,25 @@ +package presentation + +import ( + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + + "github.com/gofiber/fiber/v2" +) + +func PickupMatchingRouter(api fiber.Router) { + pickupRepo := repositories.NewRequestPickupRepository() + collectorRepo := repositories.NewCollectorRepository() + service := services.NewPickupMatchingService(pickupRepo, collectorRepo) + handler := handler.NewPickupMatchingHandler(service) + + manual := api.Group("/pickup/manual") + manual.Use(middleware.AuthMiddleware) + manual.Get("/:pickupID/nearby-collectors", handler.GetNearbyCollectorsForPickup) + + auto := api.Group("/pickup/otomatis") + auto.Use(middleware.AuthMiddleware) + auto.Get("/available-requests", handler.GetAvailablePickupForCollector) +} diff --git a/presentation/rating_route.go b/presentation/rating_route.go new file mode 100644 index 0000000..a86d8ec --- /dev/null +++ b/presentation/rating_route.go @@ -0,0 +1,24 @@ +package presentation + +import ( + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + + "github.com/gofiber/fiber/v2" +) + +func PickupRatingRouter(api fiber.Router) { + ratingRepo := repositories.NewPickupRatingRepository() + ratingService := services.NewPickupRatingService(ratingRepo) + ratingHandler := handler.NewPickupRatingHandler(ratingService) + + rating := api.Group("/pickup") + rating.Use(middleware.AuthMiddleware) + rating.Post("/:id/rating", ratingHandler.CreateRating) + + collector := api.Group("/collector") + collector.Get("/:id/ratings", ratingHandler.GetRatingsByCollector) + collector.Get("/:id/ratings/average", ratingHandler.GetAverageRating) +} diff --git a/presentation/request_pickup_route.go b/presentation/request_pickup_route.go new file mode 100644 index 0000000..cb968e6 --- /dev/null +++ b/presentation/request_pickup_route.go @@ -0,0 +1,33 @@ +package presentation + +import ( + "rijig/config" + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + + "github.com/gofiber/fiber/v2" +) + +func RequestPickupRouter(api fiber.Router) { + pickupRepo := repositories.NewRequestPickupRepository() + historyRepo := repositories.NewPickupStatusHistoryRepository() + trashRepo := repositories.NewTrashRepository(config.DB) + cartService := services.NewCartService() + historyService := services.NewPickupStatusHistoryService(historyRepo) + + pickupService := services.NewRequestPickupService(trashRepo, pickupRepo, cartService, historyRepo) + pickupHandler := handler.NewRequestPickupHandler(pickupService) + statuspickupHandler := handler.NewPickupStatusHistoryHandler(historyService) + + reqpickup := api.Group("/reqpickup") + reqpickup.Use(middleware.AuthMiddleware) + + reqpickup.Post("/manual", pickupHandler.CreateRequestPickup) + reqpickup.Get("/pickup/:id/history", statuspickupHandler.GetStatusHistory) + reqpickup.Post("/otomatis", pickupHandler.CreateRequestPickup) + reqpickup.Put("/:id/select-collector", pickupHandler.SelectCollector) + reqpickup.Put("/pickup/:id/status", pickupHandler.UpdatePickupStatus) + reqpickup.Put("/pickup/:id/item/update-actual", pickupHandler.UpdatePickupItemActualAmount) +} diff --git a/router/setup_routes.go.go b/router/setup_routes.go.go index 4c7e163..4a6e4ea 100644 --- a/router/setup_routes.go.go +++ b/router/setup_routes.go.go @@ -26,7 +26,10 @@ func SetupRoutes(app *fiber.App) { // || auth router || // presentation.IdentityCardRouter(api) presentation.CompanyProfileRouter(api) - // presentation.RequestPickupRouter(api) + presentation.RequestPickupRouter(api) + presentation.PickupMatchingRouter(api) + presentation.PickupRatingRouter(api) + presentation.CollectorRouter(api) presentation.TrashCartRouter(api)