feat: 14 new endpoints closing jupiter->Doormile API gaps, plus two tenant-scoping bug fixes
New endpoints: - Admin: partner CRUD (GET/POST /admin/partners, GET/PUT/DELETE /admin/partners/:id), bulk express booking create (POST /admin/expressbooking/bulk), bulk cancel (POST /admin/bookings/bulk-cancel), reports (GET /admin/reports), password change (PUT /admin/profile/password), miler notify (POST /admin/milers/:id/notify) - Miler: PIN reset (POST /miler/reset-pin), cancel assignment (POST /miler/bookings/:bookingid/cancel), skip delivery (POST /miler/consignments/:id/skip) - Hub: batch assign (POST /hub/bookings/batch-assign) - greedy nearest-rider queue clearing, capped per rider Bug fixes: - BookingPickupComplete now sets Consignment.Tenantid from the booking's tenant instead of the completing miler's own tenant (fixes cross-tenant shipment mis-attribution) - GetHubUnassignedBookings/GetHubBookingsRange now scoped via scopeBookingsToOwnTenant (fixes partner hub staff seeing other tenants' bookings) Also: CRM booking routes renamed to expressbooking to end the naming collision with the separate CRM clients feature; PickupBooking gains nullable Tenantid; adds CLAUDE.md project memory. Verified: go build ./... and go vet ./... both clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -376,6 +376,91 @@ func MilerDeliverConsignment(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// MilerSkipDelivery records a failed/incomplete final-mile delivery attempt
|
||||
// (customer unavailable, gate locked, wrong address, etc.) without closing
|
||||
// out the consignment — the miler still holds the parcel, the consignment
|
||||
// stays Out_for_Delivery, and the attempt is logged for a retry. This is the
|
||||
// "skipped" half of the old system's 8-in-1 status endpoint; MilerDeliverConsignment
|
||||
// above is the "delivered" half, and MilerCancelAssignment (milerController.go)
|
||||
// is the pre-pickup "cancelled"/"rejected" half.
|
||||
func MilerSkipDelivery(c *fiber.Ctx) error {
|
||||
milerUserID := c.Locals("userid").(int)
|
||||
consignmentID, err := strconv.Atoi(c.Params("id"))
|
||||
if err != nil {
|
||||
return utils.BadRequest(c, "invalid consignment ID")
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Reason string `json:"reason"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return utils.BadRequest(c, "invalid request body")
|
||||
}
|
||||
if req.Reason == "" {
|
||||
return utils.BadRequest(c, "reason is required")
|
||||
}
|
||||
|
||||
var consignment models.Consignment
|
||||
if err := db.DB.First(&consignment, consignmentID).Error; err != nil {
|
||||
return utils.NotFound(c, "consignment not found")
|
||||
}
|
||||
|
||||
var booking models.PickupBooking
|
||||
if err := db.DB.Where("consignmentid = ? AND assignedmileruserid = ?", consignment.Consignmentid, milerUserID).
|
||||
First(&booking).Error; err != nil {
|
||||
return utils.NotFound(c, "assigned consignment not found")
|
||||
}
|
||||
|
||||
if consignment.Status != constants.ConsignmentOutForDelivery {
|
||||
return utils.BadRequest(c, "consignment is not out for delivery")
|
||||
}
|
||||
|
||||
tx := db.DB.Begin()
|
||||
|
||||
consignment.Attemptcount += 1
|
||||
consignment.Updatedat = time.Now()
|
||||
if err := tx.Save(&consignment).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return utils.Internal(c, "failed to record skipped attempt")
|
||||
}
|
||||
|
||||
history := models.ConsignmentHistory{
|
||||
Consignmentid: consignment.Consignmentid,
|
||||
Userid: &milerUserID,
|
||||
Eventstatus: "Delivery_Skipped",
|
||||
Remarks: fmt.Sprintf("Attempt %d skipped at (%.5f, %.5f): %s", consignment.Attemptcount, req.Lat, req.Lon, req.Reason),
|
||||
}
|
||||
if err := tx.Create(&history).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return utils.Internal(c, "failed to log skipped attempt")
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return utils.Internal(c, "failed to commit skipped attempt")
|
||||
}
|
||||
|
||||
// After 3 failed attempts, flag it for hub attention rather than leaving
|
||||
// it silently retrying forever.
|
||||
if consignment.Attemptcount >= 3 {
|
||||
exception := models.ConsignmentException{
|
||||
Consignmentid: consignment.Consignmentid,
|
||||
Reportedbyuserid: &milerUserID,
|
||||
Exceptiontype: constants.ExceptionUndeliverable,
|
||||
Severity: "Medium",
|
||||
Description: fmt.Sprintf("3 delivery attempts failed. Last reason: %s", req.Reason),
|
||||
}
|
||||
db.DB.Create(&exception)
|
||||
}
|
||||
|
||||
return utils.OK(c, fiber.Map{
|
||||
"consignmentid": consignment.Consignmentid,
|
||||
"attemptcount": consignment.Attemptcount,
|
||||
"status": consignment.Status,
|
||||
})
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// EARNINGS
|
||||
// --------------------
|
||||
|
||||
Reference in New Issue
Block a user