shifts
This commit is contained in:
@@ -73,6 +73,40 @@ func (ctl *PartnerController) GetPartners(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRiderShift opens a working window in a delivery region.
|
||||||
|
//
|
||||||
|
// Riders cannot be hired without one, and until this existed the table could
|
||||||
|
// only be read — a region that shipped with no shift rows was a region no rider
|
||||||
|
// could ever be added to, with nothing in the product able to change that.
|
||||||
|
//
|
||||||
|
// The region comes from the body rather than the query because this is a write
|
||||||
|
// and the whole shift is one object; `getridershifts` beside it reads the same
|
||||||
|
// id from a param, which is the existing convention for reads here.
|
||||||
|
func (ctl *PartnerController) CreateRiderShift(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
var shift models.Ridershifts
|
||||||
|
if err := c.BodyParser(&shift); err != nil {
|
||||||
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"status": false, "code": http.StatusBadRequest, "message": "Invalid request body",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ctl.partnerService.CreateRiderShift(shift)
|
||||||
|
if err != nil {
|
||||||
|
// 400, not 500. Every failure here is something the person typed — a
|
||||||
|
// region that is not configured, a window that already exists, a time
|
||||||
|
// that is not a time — and each message says which.
|
||||||
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"status": false, "code": http.StatusBadRequest, "message": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
||||||
|
"status": true, "code": http.StatusCreated,
|
||||||
|
"message": "Shift created", "details": result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (ctl *PartnerController) GetRiderShifts(c *fiber.Ctx) error {
|
func (ctl *PartnerController) GetRiderShifts(c *fiber.Ctx) error {
|
||||||
|
|
||||||
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ type Partnerinfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Ridershifts struct {
|
type Ridershifts struct {
|
||||||
Shiftid int `json:"shiftid" gorm:"Primary_Key"`
|
Shiftid int `json:"shiftid" gorm:"Primary_Key"`
|
||||||
|
// The region this shift belongs to. The column has always been on the table
|
||||||
|
// — `GetRiderShifts` filters on it — but there was no field for it here, so
|
||||||
|
// nothing could write one. That is why shifts could only ever be read.
|
||||||
|
Applocationid int `json:"applocationid"`
|
||||||
Shiftdate string `json:"shiftdate"`
|
Shiftdate string `json:"shiftdate"`
|
||||||
Starttime string `json:"starttime"`
|
Starttime string `json:"starttime"`
|
||||||
Endtime string `json:"endtime"`
|
Endtime string `json:"endtime"`
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type PartnerRepository interface {
|
|||||||
GetActiveRiders(partnerid, aid, uid, tid int) ([]models.RiderInfo, error)
|
GetActiveRiders(partnerid, aid, uid, tid int) ([]models.RiderInfo, error)
|
||||||
GetPartners(aid, pid, uid int) ([]models.Partnerinfo, error)
|
GetPartners(aid, pid, uid int) ([]models.Partnerinfo, error)
|
||||||
GetRiderShifts(aid int) ([]models.Ridershifts, error)
|
GetRiderShifts(aid int) ([]models.Ridershifts, error)
|
||||||
|
CreateRiderShift(shift models.Ridershifts) (models.Ridershifts, error)
|
||||||
GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error)
|
GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error)
|
||||||
GetRiderLogs(pid, aid int, fdate, tdate string) ([]models.RiderlogDetails, error)
|
GetRiderLogs(pid, aid int, fdate, tdate string) ([]models.RiderlogDetails, error)
|
||||||
GetRiderInfo(userid int) (models.RiderInfo, error)
|
GetRiderInfo(userid int) (models.RiderInfo, error)
|
||||||
@@ -1027,3 +1028,178 @@ func (r *partnerRepository) regionByName(db *gorm.DB, name string) int {
|
|||||||
WHERE LOWER(TRIM(locationname)) = LOWER(TRIM(?)) LIMIT 1`, name).Scan(&id)
|
WHERE LOWER(TRIM(locationname)) = LOWER(TRIM(?)) LIMIT 1`, name).Scan(&id)
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Rider shifts ────────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// A shift is the window a rider works, and `CreateRider` refuses a rider
|
||||||
|
// without one — `getriders` joins `ridershifts` through `ridersettings.shiftid`,
|
||||||
|
// so a rider on a shift that does not exist is a rider nobody can see.
|
||||||
|
//
|
||||||
|
// Until now the table could only be READ. There was no endpoint, no service
|
||||||
|
// method and not even a field for `applocationid` on the model, so a region
|
||||||
|
// that shipped without shift rows could never have a rider added to it at all:
|
||||||
|
// the console showed "No shifts set up for this region" and there was nothing
|
||||||
|
// anybody could do from the product to change that. Till staff had
|
||||||
|
// `createstaffshift` from the beginning; riders were simply missed.
|
||||||
|
|
||||||
|
// riderShiftClock is a start or end time as the column stores it.
|
||||||
|
//
|
||||||
|
// Accepts `9:00`, `09:00` and `09:00:00` and normalises to `HH:MM`. The rows
|
||||||
|
// inserted by hand over the years use all three spellings, and `GetRiderShifts`
|
||||||
|
// builds its label by concatenating the two columns raw — so `9:00-17:00` and
|
||||||
|
// `09:00-17:00` are two different labels for one window in the same dropdown.
|
||||||
|
func riderShiftClock(raw string) (string, error) {
|
||||||
|
text := strings.TrimSpace(raw)
|
||||||
|
if text == "" {
|
||||||
|
return "", errors.New("a shift needs a start and an end time")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(text, ":")
|
||||||
|
if len(parts) < 2 || len(parts) > 3 {
|
||||||
|
return "", fmt.Errorf("%q is not a time — write it as HH:MM", raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
hour, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||||
|
if err != nil || hour < 0 || hour > 23 {
|
||||||
|
return "", fmt.Errorf("%q is not a time — the hour must be 0 to 23", raw)
|
||||||
|
}
|
||||||
|
minute, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||||
|
if err != nil || minute < 0 || minute > 59 {
|
||||||
|
return "", fmt.Errorf("%q is not a time — the minutes must be 0 to 59", raw)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%02d:%02d", hour, minute), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// riderShiftHours is how long the window runs, in hours.
|
||||||
|
//
|
||||||
|
// Computed rather than asked for, because it is the one field a person gets
|
||||||
|
// wrong and nothing downstream checks: `shifthours` feeds rider pay, and a
|
||||||
|
// window of 09:00–17:00 recorded as 4 hours underpays every rider on it.
|
||||||
|
//
|
||||||
|
// A window that ends before it starts crosses midnight and is measured that
|
||||||
|
// way — a 22:00–06:00 night shift is eight hours, not minus sixteen.
|
||||||
|
func riderShiftHours(start, end string) float32 {
|
||||||
|
toMinutes := func(clock string) int {
|
||||||
|
parts := strings.Split(clock, ":")
|
||||||
|
hour, _ := strconv.Atoi(parts[0])
|
||||||
|
minute, _ := strconv.Atoi(parts[1])
|
||||||
|
return hour*60 + minute
|
||||||
|
}
|
||||||
|
|
||||||
|
span := toMinutes(end) - toMinutes(start)
|
||||||
|
if span <= 0 {
|
||||||
|
span += 24 * 60
|
||||||
|
}
|
||||||
|
return float32(span) / 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateRiderShift checks everything that does not need the database.
|
||||||
|
//
|
||||||
|
// Split out so the rules are testable without one, and returns the shift with
|
||||||
|
// its times normalised and its hours worked out rather than reporting on a copy
|
||||||
|
// the caller then has to rebuild.
|
||||||
|
func validateRiderShift(shift models.Ridershifts) (models.Ridershifts, error) {
|
||||||
|
if shift.Applocationid == 0 {
|
||||||
|
return shift, errors.New("a shift needs a delivery region")
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := riderShiftClock(shift.Starttime)
|
||||||
|
if err != nil {
|
||||||
|
return shift, err
|
||||||
|
}
|
||||||
|
end, err := riderShiftClock(shift.Endtime)
|
||||||
|
if err != nil {
|
||||||
|
return shift, err
|
||||||
|
}
|
||||||
|
if start == end {
|
||||||
|
return shift, errors.New("a shift cannot start and end at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
shift.Starttime = start
|
||||||
|
shift.Endtime = end
|
||||||
|
// Always recomputed, never taken from the request. A caller that sends its
|
||||||
|
// own number is a caller that can disagree with the window it just sent.
|
||||||
|
shift.Shifthours = riderShiftHours(start, end)
|
||||||
|
|
||||||
|
if shift.Basefare < 0 || shift.Additionalcharges < 0 || shift.Fuelcharge < 0 {
|
||||||
|
return shift, errors.New("pay cannot be negative")
|
||||||
|
}
|
||||||
|
return shift, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRiderShift opens a shift window in one region.
|
||||||
|
func (r *partnerRepository) CreateRiderShift(shift models.Ridershifts) (models.Ridershifts, error) {
|
||||||
|
shift, err := validateRiderShift(shift)
|
||||||
|
if err != nil {
|
||||||
|
return models.Ridershifts{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same guard `CreateRider` applies to a rider's region, for the same reason:
|
||||||
|
// `getriders` joins app_locationconfig, so a shift in a region with no
|
||||||
|
// config row would be offered in the dropdown and then hide every rider put
|
||||||
|
// on it.
|
||||||
|
var configs int64
|
||||||
|
if err := r.db.Table("app_locationconfig").
|
||||||
|
Where("applocationid = ?", shift.Applocationid).Count(&configs).Error; err != nil {
|
||||||
|
return models.Ridershifts{}, err
|
||||||
|
}
|
||||||
|
if configs == 0 {
|
||||||
|
return models.Ridershifts{}, fmt.Errorf(
|
||||||
|
"delivery region %d is not configured, so a shift there would hide every rider on it", shift.Applocationid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The dropdown labels a shift by its times alone, so a duplicate window is
|
||||||
|
// two identical-looking choices and no way to tell which one a rider is on.
|
||||||
|
var clash int64
|
||||||
|
if err := r.db.Table("ridershifts").
|
||||||
|
Where("applocationid = ? AND starttime = ? AND endtime = ?",
|
||||||
|
shift.Applocationid, shift.Starttime, shift.Endtime).
|
||||||
|
Count(&clash).Error; err != nil {
|
||||||
|
return models.Ridershifts{}, err
|
||||||
|
}
|
||||||
|
if clash > 0 {
|
||||||
|
return models.Ridershifts{}, fmt.Errorf(
|
||||||
|
"a %s-%s shift already exists in this region", shift.Starttime, shift.Endtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A column map with RETURNING, the same way CreatePartner writes its row,
|
||||||
|
// rather than inserting the struct.
|
||||||
|
//
|
||||||
|
// Inserting the struct would carry `shiftid` at zero into the statement and
|
||||||
|
// leave whether the sequence is used to how GORM reads a `Primary_Key` tag
|
||||||
|
// written in the v1 spelling — which is exactly the kind of thing that works
|
||||||
|
// on one driver and writes a row with id 0 on another. Naming the columns
|
||||||
|
// removes the question: the id is the database's to assign.
|
||||||
|
row := map[string]any{
|
||||||
|
"applocationid": shift.Applocationid,
|
||||||
|
"shiftdate": shift.Shiftdate,
|
||||||
|
"starttime": shift.Starttime,
|
||||||
|
"endtime": shift.Endtime,
|
||||||
|
"shifthours": shift.Shifthours,
|
||||||
|
"basefare": shift.Basefare,
|
||||||
|
"additionalkm": shift.Additionalkm,
|
||||||
|
"additionalcharges": shift.Additionalcharges,
|
||||||
|
"orders": shift.Orders,
|
||||||
|
"fuelcharge": shift.Fuelcharge,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.db.Table("ridershifts").
|
||||||
|
Clauses(clause.Returning{Columns: []clause.Column{{Name: "shiftid"}}}).
|
||||||
|
Create(&row).Error; err != nil {
|
||||||
|
return models.Ridershifts{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, ok := row["shiftid"]
|
||||||
|
if !ok || toInt(id) == 0 {
|
||||||
|
// The rider form selects the new shift by id the moment this returns. A
|
||||||
|
// shift written without one would leave the drawer selecting nothing and
|
||||||
|
// reading as a failed save.
|
||||||
|
return models.Ridershifts{}, errors.New("the shift was written without an id")
|
||||||
|
}
|
||||||
|
shift.Shiftid = toInt(id)
|
||||||
|
|
||||||
|
// The label the dropdown shows, built the same way GetRiderShifts builds it
|
||||||
|
// so a shift reads identically the moment it is created and after a reload.
|
||||||
|
shift.Shiftname = shift.Starttime + "-" + shift.Endtime
|
||||||
|
return shift, nil
|
||||||
|
}
|
||||||
|
|||||||
126
repositories/riderShift_test.go
Normal file
126
repositories/riderShift_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"nearle/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shift windows, which riders cannot be hired without.
|
||||||
|
|
||||||
|
`CreateRider` refuses a rider with no shift — correctly, because `getriders`
|
||||||
|
joins `ridershifts` and a rider on a shift that does not exist is a rider nobody
|
||||||
|
can see. But the table could only be READ: no endpoint, no service method, and
|
||||||
|
no `applocationid` field on the model to write one with. A region that shipped
|
||||||
|
without shift rows was a region no rider could ever be added to, and the console
|
||||||
|
said "No shifts set up for this region" with nothing anybody could do about it.
|
||||||
|
|
||||||
|
These cover the rules that do not need a database. The two that do — the region
|
||||||
|
must be configured, and the window must not already exist — are enforced in
|
||||||
|
CreateRiderShift against real tables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestATimeIsNormalisedSoOneWindowHasOneLabel(t *testing.T) {
|
||||||
|
// The dropdown labels a shift by concatenating its two columns raw, so
|
||||||
|
// `9:00-17:00` and `09:00-17:00` are two different labels for one window.
|
||||||
|
// Rows inserted by hand over the years use every spelling.
|
||||||
|
for _, tc := range []struct{ in, want string }{
|
||||||
|
{"9:00", "09:00"},
|
||||||
|
{"09:00", "09:00"},
|
||||||
|
{"09:00:00", "09:00"},
|
||||||
|
{" 9:5 ", "09:05"},
|
||||||
|
{"23:59", "23:59"},
|
||||||
|
{"0:00", "00:00"},
|
||||||
|
} {
|
||||||
|
got, err := riderShiftClock(tc.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q: %v", tc.in, err)
|
||||||
|
}
|
||||||
|
if got != tc.want {
|
||||||
|
t.Fatalf("%q became %q, want %q", tc.in, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSomethingThatIsNotATimeIsRefused(t *testing.T) {
|
||||||
|
for _, bad := range []string{"", " ", "morning", "25:00", "09:60", "9", "9:00:00:00", "-1:00"} {
|
||||||
|
if _, err := riderShiftClock(bad); err == nil {
|
||||||
|
t.Fatalf("%q was accepted as a time", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHoursAreWorkedOutRatherThanAskedFor(t *testing.T) {
|
||||||
|
// `shifthours` feeds rider pay. It is the one field a person gets wrong and
|
||||||
|
// nothing downstream checks, so it is computed and the request's own number
|
||||||
|
// is discarded.
|
||||||
|
shift, err := validateRiderShift(models.Ridershifts{
|
||||||
|
Applocationid: 2, Starttime: "09:00", Endtime: "17:00",
|
||||||
|
Shifthours: 4, // wrong, and sent anyway
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("a good shift was refused: %v", err)
|
||||||
|
}
|
||||||
|
if shift.Shifthours != 8 {
|
||||||
|
t.Fatalf("09:00-17:00 came out as %v hours, want 8", shift.Shifthours)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestANightShiftCrossesMidnightRatherThanGoingNegative(t *testing.T) {
|
||||||
|
// 22:00-06:00 is eight hours. Subtracting the clocks gives minus sixteen,
|
||||||
|
// which would pay a night rider for a negative shift.
|
||||||
|
if got := riderShiftHours("22:00", "06:00"); got != 8 {
|
||||||
|
t.Fatalf("22:00-06:00 came out as %v hours, want 8", got)
|
||||||
|
}
|
||||||
|
if got := riderShiftHours("09:30", "17:00"); got != 7.5 {
|
||||||
|
t.Fatalf("09:30-17:00 came out as %v hours, want 7.5", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAShiftNeedsARegion(t *testing.T) {
|
||||||
|
// Region 0 arrives from a form field nobody filled in. A shift there would
|
||||||
|
// be offered to nobody and joined to nothing.
|
||||||
|
_, err := validateRiderShift(models.Ridershifts{Starttime: "09:00", Endtime: "17:00"})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "region") {
|
||||||
|
t.Fatalf("a shift with no region was accepted: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAShiftCannotStartAndEndAtTheSameTime(t *testing.T) {
|
||||||
|
// Would compute as a 24-hour window under the midnight rule, which is not
|
||||||
|
// what anybody who typed the same time twice meant.
|
||||||
|
_, err := validateRiderShift(models.Ridershifts{
|
||||||
|
Applocationid: 2, Starttime: "09:00", Endtime: "9:00",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("a zero-length window was accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPayCannotBeNegative(t *testing.T) {
|
||||||
|
for _, shift := range []models.Ridershifts{
|
||||||
|
{Applocationid: 2, Starttime: "09:00", Endtime: "17:00", Basefare: -1},
|
||||||
|
{Applocationid: 2, Starttime: "09:00", Endtime: "17:00", Additionalcharges: -5},
|
||||||
|
{Applocationid: 2, Starttime: "09:00", Endtime: "17:00", Fuelcharge: -0.5},
|
||||||
|
} {
|
||||||
|
if _, err := validateRiderShift(shift); err == nil {
|
||||||
|
t.Fatalf("negative pay was accepted: %+v", shift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTheNormalisedTimesComeBackOnTheShift(t *testing.T) {
|
||||||
|
// The caller inserts what validation returned, not what it was handed —
|
||||||
|
// otherwise the normalising is computed and then thrown away.
|
||||||
|
shift, err := validateRiderShift(models.Ridershifts{
|
||||||
|
Applocationid: 2, Starttime: "9:0", Endtime: "17:00:00",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("refused: %v", err)
|
||||||
|
}
|
||||||
|
if shift.Starttime != "09:00" || shift.Endtime != "17:00" {
|
||||||
|
t.Fatalf("times were not normalised on the way out: %q-%q", shift.Starttime, shift.Endtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ func RegisterPartnerRoutes(api fiber.Router, f *facade.Facade) {
|
|||||||
partner.Get("/getriders", f.PartnerController.GetActiveRiders)
|
partner.Get("/getriders", f.PartnerController.GetActiveRiders)
|
||||||
partner.Get("/getpartners", f.PartnerController.GetPartners)
|
partner.Get("/getpartners", f.PartnerController.GetPartners)
|
||||||
partner.Get("/getridershifts", f.PartnerController.GetRiderShifts)
|
partner.Get("/getridershifts", f.PartnerController.GetRiderShifts)
|
||||||
|
// Opening a shift window. Riders cannot be hired without one, and this table
|
||||||
|
// was read-only until now — see partnerController.CreateRiderShift.
|
||||||
|
partner.Post("/createridershift", f.PartnerController.CreateRiderShift)
|
||||||
partner.Get("/getlocations", f.PartnerController.GetLocationConfig)
|
partner.Get("/getlocations", f.PartnerController.GetLocationConfig)
|
||||||
partner.Get("/getriderlogs", f.PartnerController.GetRiderLogs)
|
partner.Get("/getriderlogs", f.PartnerController.GetRiderLogs)
|
||||||
partner.Get("/getfleetsummary", f.PartnerController.GetFleetSummary)
|
partner.Get("/getfleetsummary", f.PartnerController.GetFleetSummary)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type PartnerService interface {
|
|||||||
GetActiveRiders(partnerid, aid, uid, tid int) ([]models.RiderInfo, error)
|
GetActiveRiders(partnerid, aid, uid, tid int) ([]models.RiderInfo, error)
|
||||||
GetPartners(aid, pid, uid int) ([]models.Partnerinfo, error)
|
GetPartners(aid, pid, uid int) ([]models.Partnerinfo, error)
|
||||||
GetRiderShifts(aid int) ([]models.Ridershifts, error)
|
GetRiderShifts(aid int) ([]models.Ridershifts, error)
|
||||||
|
CreateRiderShift(shift models.Ridershifts) (models.Ridershifts, error)
|
||||||
GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error)
|
GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error)
|
||||||
GetRiderLogs(pid, aid int, fdate, tdate string) ([]models.RiderlogDetails, error)
|
GetRiderLogs(pid, aid int, fdate, tdate string) ([]models.RiderlogDetails, error)
|
||||||
GetRiderInfo(userid int) (models.RiderInfo, error)
|
GetRiderInfo(userid int) (models.RiderInfo, error)
|
||||||
@@ -48,6 +49,17 @@ func (s *partnerService) GetRiderShifts(aid int) ([]models.Ridershifts, error) {
|
|||||||
return s.repo.GetRiderShifts(aid)
|
return s.repo.GetRiderShifts(aid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRiderShift opens a working window in a region.
|
||||||
|
//
|
||||||
|
// The counterpart the table never had. `CreateRider` refuses a rider with no
|
||||||
|
// shift, so a region shipped without shift rows could not be given a rider from
|
||||||
|
// the product at all — the dropdown said "No shifts set up for this region" and
|
||||||
|
// that was the end of it.
|
||||||
|
func (s *partnerService) CreateRiderShift(shift models.Ridershifts) (models.Ridershifts, error) {
|
||||||
|
|
||||||
|
return s.repo.CreateRiderShift(shift)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *partnerService) GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error) {
|
func (s *partnerService) GetLocationConfig(uid, cid int) ([]models.Locationconfigs, error) {
|
||||||
|
|
||||||
return s.repo.GetLocationConfig(uid, cid)
|
return s.repo.GetLocationConfig(uid, cid)
|
||||||
|
|||||||
Reference in New Issue
Block a user