432 lines
13 KiB
Go
432 lines
13 KiB
Go
package controllers
|
|
|
|
import (
|
|
"errors"
|
|
"nearle/models"
|
|
"nearle/services"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type PartnerController struct {
|
|
partnerService services.PartnerService
|
|
}
|
|
|
|
func NewPartnerController(partnerService services.PartnerService) *PartnerController {
|
|
return &PartnerController{partnerService: partnerService}
|
|
}
|
|
|
|
func (ctl *PartnerController) GetActiveRiders(c *fiber.Ctx) error {
|
|
pid, _ := strconv.Atoi(c.Query("partnerid"))
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
uid, _ := strconv.Atoi(c.Query("userid"))
|
|
tid, _ := strconv.Atoi(c.Query("tenantid"))
|
|
|
|
if pid == 0 && aid == 0 && uid == 0 && tid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "At least one of tenantid, partnerid, applocationid or userid is required",
|
|
})
|
|
}
|
|
|
|
result, err := ctl.partnerService.GetActiveRiders(pid, aid, uid, tid)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func (ctl *PartnerController) GetPartners(c *fiber.Ctx) error {
|
|
pid, _ := strconv.Atoi(c.Query("partnerid"))
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
uid, _ := strconv.Atoi(c.Query("userid"))
|
|
|
|
result, err := ctl.partnerService.GetPartners(aid, pid, uid)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func (ctl *PartnerController) GetRiderShifts(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
|
|
result, err := ctl.partnerService.GetRiderShifts(aid)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func (ctl *PartnerController) GetLocationConfig(c *fiber.Ctx) error {
|
|
|
|
uid, _ := strconv.Atoi(c.Query("userid"))
|
|
cid, _ := strconv.Atoi(c.Query("configid"))
|
|
|
|
result, err := ctl.partnerService.GetLocationConfig(uid, cid)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func (ctl *PartnerController) GetRiderLogs(c *fiber.Ctx) error {
|
|
pid, _ := strconv.Atoi(c.Query("partnerid"))
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
fdate := c.Query("fromdate")
|
|
tdate := c.Query("todate")
|
|
|
|
if pid == 0 && aid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "At least one of partnerid or applocationid is required",
|
|
})
|
|
}
|
|
|
|
data, err := ctl.partnerService.GetRiderLogs(pid, aid, fdate, tdate)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *PartnerController) GetFleetSummary(c *fiber.Ctx) error {
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
tid, _ := strconv.Atoi(c.Query("tenantid"))
|
|
fdate := c.Query("fromdate")
|
|
tdate := c.Query("todate")
|
|
|
|
result, err := ctl.partnerService.GetFleetSummary(aid, tid, fdate, tdate)
|
|
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func (ctl *PartnerController) GetRiderInfo(c *fiber.Ctx) error {
|
|
uid, _ := strconv.Atoi(c.Query("userid"))
|
|
|
|
result, err := ctl.partnerService.GetRiderInfo(uid)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
// CreateRider hires a rider — three tables in one transaction.
|
|
//
|
|
// `tenantid` comes from the query string, which is where this console's other
|
|
// scoped writes take it from. It is required: a rider with no tenant belongs to
|
|
// the region and to no merchant, which is the state the 84 existing riders are
|
|
// already in and not one worth creating more of.
|
|
func (ctl *PartnerController) CreateRider(c *fiber.Ctx) error {
|
|
var rider models.NewRider
|
|
|
|
if err := c.BodyParser(&rider); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Taken from the scope rather than trusted from the body. A store admin
|
|
// must not be able to put a rider on another merchant's books by editing a
|
|
// payload, and the body is the caller's to edit.
|
|
if tid, _ := strconv.Atoi(c.Query("tenantid")); tid != 0 {
|
|
rider.Tenantid = tid
|
|
}
|
|
if pid, _ := strconv.Atoi(c.Query("partnerid")); pid != 0 {
|
|
rider.Partnerid = pid
|
|
}
|
|
|
|
// A rider belongs to a merchant OR to a delivery partner — never to
|
|
// neither, and never to both.
|
|
//
|
|
// This used to demand a tenantid outright, which made a PARTNER's rider
|
|
// impossible to create: a partner supplies riders to many merchants and
|
|
// their riders sit under no single one. It is the same endpoint because it
|
|
// is the same rider — the difference is only who they ride for, and that is
|
|
// what the assign screen later branches on.
|
|
//
|
|
// Both at once is refused rather than silently preferred. A rider carrying
|
|
// a tenantid AND a partnerid appears in two directories and two assign
|
|
// pickers, and nothing downstream says which one owns them.
|
|
switch {
|
|
case rider.Tenantid == 0 && rider.Partnerid == 0:
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": "a rider needs either a tenantid (the shop's own rider) " +
|
|
"or a partnerid (a delivery partner's rider)",
|
|
})
|
|
case rider.Tenantid != 0 && rider.Partnerid != 0:
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": "a rider belongs to a shop or to a partner, not to both",
|
|
})
|
|
}
|
|
|
|
userid, err := ctl.partnerService.CreateRider(rider)
|
|
if err != nil {
|
|
// 400, not 500: every failure this can produce is something the caller
|
|
// sent — a shift that does not exist, an unconfigured region, a phone
|
|
// number already in use. Answering 500 would send them to look at the
|
|
// server for a problem in their own form.
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
|
"code": http.StatusCreated,
|
|
"status": true,
|
|
// Said plainly because it is the single most confusing thing about this
|
|
// flow: the rider is created and will NOT show in the on-duty fleet
|
|
// until they open the app and start a shift.
|
|
"message": "Rider created. They appear in the fleet once they sign in and start a shift.",
|
|
"details": fiber.Map{"userid": userid},
|
|
})
|
|
}
|
|
|
|
func (ctl *PartnerController) UpdateRider(c *fiber.Ctx) error {
|
|
var rider models.NewRider
|
|
|
|
if err := c.BodyParser(&rider); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
if err := ctl.partnerService.UpdateRider(rider); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"status": true,
|
|
"message": "Success",
|
|
})
|
|
}
|
|
|
|
// GetRiderRoster lists riders whether or not they are working today.
|
|
//
|
|
// The scope is required. Unscoped this returns every rider on the platform,
|
|
// which is not a merchant's business — the same guard the other tenant-scoped
|
|
// reads carry.
|
|
func (ctl *PartnerController) GetRiderRoster(c *fiber.Ctx) error {
|
|
tid, _ := strconv.Atoi(c.Query("tenantid"))
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
pid, _ := strconv.Atoi(c.Query("partnerid"))
|
|
|
|
if tid == 0 && aid == 0 && pid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"status": false,
|
|
"message": "One of tenantid, applocationid or partnerid is required",
|
|
})
|
|
}
|
|
|
|
result, err := ctl.partnerService.GetRiderRoster(tid, aid, pid)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"status": false,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"status": true,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
/* ── Onboarding a delivery partner ────────────────────────────────────────────
|
|
|
|
Platform-side only. A merchant does not create the company that supplies their
|
|
riders — they are assigned one, which is `AssignPartner` on the tenant. Both of
|
|
these live in the web group for the same reason `createrider` does: a partner is
|
|
onboarded from a console, not from a phone. */
|
|
|
|
// CreatePartner onboards a delivery partner and the regions they cover.
|
|
func (ctl *PartnerController) CreatePartner(c *fiber.Ctx) error {
|
|
var input models.NewPartner
|
|
|
|
if err := c.BodyParser(&input); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest, "status": false, "message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
partnerid, err := ctl.partnerService.CreatePartner(input)
|
|
if err != nil {
|
|
// 400, not 500: every failure this produces is something the caller
|
|
// sent — a missing name, an unconfigured region, a number already in
|
|
// use. A 500 sends them to look at the server for their own form.
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
|
"code": http.StatusCreated, "status": true, "message": "Successfully Created",
|
|
"details": fiber.Map{"partnerid": partnerid},
|
|
})
|
|
}
|
|
|
|
// UpdatePartner edits a partner and, when regions are sent, re-states them.
|
|
func (ctl *PartnerController) UpdatePartner(c *fiber.Ctx) error {
|
|
var input models.NewPartner
|
|
|
|
if err := c.BodyParser(&input); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest, "status": false, "message": "Invalid request body",
|
|
})
|
|
}
|
|
if pid, _ := strconv.Atoi(c.Query("partnerid")); pid != 0 {
|
|
input.Partnerid = pid
|
|
}
|
|
|
|
if err := ctl.partnerService.UpdatePartner(input); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK, "status": true, "message": "Successfully Updated",
|
|
})
|
|
}
|
|
|
|
// GetPartnerLocations lists the regions one partner covers.
|
|
func (ctl *PartnerController) GetPartnerLocations(c *fiber.Ctx) error {
|
|
pid, _ := strconv.Atoi(c.Query("partnerid"))
|
|
if pid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest, "status": false, "message": "partnerid is required",
|
|
})
|
|
}
|
|
|
|
rows, err := ctl.partnerService.GetPartnerLocations(pid)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError, "status": false, "message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK, "status": true, "message": "Success", "details": rows,
|
|
})
|
|
}
|
|
|
|
// riderOwner enforces that a rider belongs to a shop or to a partner.
|
|
//
|
|
// Its own function so the rule can be tested without a request, and so both
|
|
// call sites — create today, anything that moves a rider tomorrow — cannot
|
|
// drift apart on it.
|
|
func riderOwner(tenantid, partnerid int) error {
|
|
switch {
|
|
case tenantid == 0 && partnerid == 0:
|
|
return errors.New("a rider needs either a tenantid (the shop's own rider) " +
|
|
"or a partnerid (a delivery partner's rider)")
|
|
case tenantid != 0 && partnerid != 0:
|
|
return errors.New("a rider belongs to a shop or to a partner, not to both")
|
|
}
|
|
return nil
|
|
}
|