Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Add SmartRate endpoints functions #225

Merged
merged 8 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Next Release

- Adds new `EstimateDeliveryDateForZipPair`, `RecommendShipDateForShipment` and `RecommendShipDateForZipPair`
- New `CreateUpsCarrierAccount` and `UpdateUpsCarrierAccount` methods and associated parameter structs, required to use for UPS accounts due to new `/ups_oauth_registrations` endpoint.
- Starting `2024-08-05`, UPS accounts will require a new payload to register or update. See [UPS OAuth 2.0 Update](https://support.easypost.com/hc/en-us/articles/26635027512717-UPS-OAuth-2-0-Update) for more details.
- Attempting to use the generic `CreateCarrierAccount` and `UpdateCarrierAccount` methods with UPS accounts will throw an `InvalidFunctionError`.
Expand Down
149 changes: 81 additions & 68 deletions datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,75 +16,10 @@ func (dt *DateTime) UnmarshalJSON(b []byte) (err error) {
var t time.Time

// try to parse
// 2006-01-02
t, err = time.Parse(`"2006-01-02"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse
// 2006-01-02T15:04:05Z
t, err = time.Parse(`"2006-01-02T15:04:05Z"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC3339 (default for time.Time)
// 2006-01-02T15:04:05Z07:00
t, err = time.Parse(`"`+time.RFC3339+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC3339Nano
// 2006-01-02T15:04:05.999999999Z07:00
t, err = time.Parse(`"`+time.RFC3339Nano+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC1123
// Mon, 02 Jan 2006 15:04:05 MST
t, err = time.Parse(`"`+time.RFC1123+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC1123Z
// Mon, 02 Jan 2006 15:04:05 -0700
t, err = time.Parse(`"`+time.RFC1123Z+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC822
// 02 Jan 06 15:04 MST
t, err = time.Parse(`"`+time.RFC822+`"`, string(b))
asDateTime, err := DateTimeFromString(string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC822Z
// 02 Jan 06 15:04 -0700
t, err = time.Parse(`"`+time.RFC822Z+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
}

// try to parse as RFC850
// Monday, 02-Jan-06 15:04:05 MST
t, err = time.Parse(`"`+time.RFC850+`"`, string(b))
if err == nil {
*dt = DateTime(t)
return
*dt = asDateTime
return nil
}

// last ditch effort, fallback to whatever the JSON marshaller thinks
Expand Down Expand Up @@ -141,3 +76,81 @@ func NewDateTime(year int, month time.Month, day, hour, min, sec, nsec int, loc
func DateTimeFromTime(t time.Time) DateTime {
return DateTime(t)
}

func DateTimeFromString(s string) (dt DateTime, err error) {
var t time.Time

// try to parse
// 2006-01-02
t, err = time.Parse(`"2006-01-02"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse
// 2006-01-02T15:04:05Z
t, err = time.Parse(`"2006-01-02T15:04:05Z"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC3339 (default for time.Time)
// 2006-01-02T15:04:05Z07:00
t, err = time.Parse(`"`+time.RFC3339+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC3339Nano
// 2006-01-02T15:04:05.999999999Z07:00
t, err = time.Parse(`"`+time.RFC3339Nano+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC1123
// Mon, 02 Jan 2006 15:04:05 MST
t, err = time.Parse(`"`+time.RFC1123+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC1123Z
// Mon, 02 Jan 2006 15:04:05 -0700
t, err = time.Parse(`"`+time.RFC1123Z+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC822
// 02 Jan 06 15:04 MST
t, err = time.Parse(`"`+time.RFC822+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC822Z
// 02 Jan 06 15:04 -0700
t, err = time.Parse(`"`+time.RFC822Z+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

// try to parse as RFC850
// Monday, 02-Jan-06 15:04:05 MST
t, err = time.Parse(`"`+time.RFC850+`"`, s)
if err == nil {
dt = DateTime(t)
return
}

return
}
28 changes: 24 additions & 4 deletions shipment.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ type EstimatedDeliveryDate struct {
Rate SmartRate `json:"rate,omitempty"`
}

// RecommendShipDateForShipmentResult is the result of the RecommendShipDateForShipment method.
type RecommendShipDateForShipmentResult struct {
Rate *SmartRate `json:"rate,omitempty"`
TimeInTransitDetails *TimeInTransitDetailsForShipDate `json:"easypost_time_in_transit_data,omitempty"`
}

// CreateShipment creates a new Shipment object. The ToAddress, FromAddress and
// Parcel attributes are required. These objects may be fully-specified to
// create new ones at the same time as creating the Shipment, or they can refer
Expand Down Expand Up @@ -385,13 +391,12 @@ func (c *Client) GenerateShipmentFormWithOptionsWithContext(ctx context.Context,
return
}

// GetShipmentEstimatedDeliveryDate retrieves the estimated delivery date of each Rate via SmartRate.
func (c *Client) GetShipmentEstimatedDeliveryDate(shipmentID, plannedShipDate string) (out []*EstimatedDeliveryDate, err error) {
// GetShipmentEstimatedDeliveryDate retrieves the estimated delivery date of each rate for a Shipment via the Delivery Date Estimator API, based on a specific ship date.
func (c *Client) GetShipmentEstimatedDeliveryDate(shipmentID string, plannedShipDate string) (out []*EstimatedDeliveryDate, err error) {
return c.GetShipmentEstimatedDeliveryDateWithContext(context.Background(), shipmentID, plannedShipDate)
}

// GetShipmentEstimatedDeliveryDateWithContext performs the same operation as GetShipmentEstimatedDeliveryDate,
// but allows specifying a context that can interrupt the request.
// GetShipmentEstimatedDeliveryDateWithContext performs the same operation as EstimateDeliveryDateForShipment, but allows specifying a context that can interrupt the request.
func (c *Client) GetShipmentEstimatedDeliveryDateWithContext(ctx context.Context, shipmentID string, plannedShipDate string) (out []*EstimatedDeliveryDate, err error) {
vals := url.Values{"planned_ship_date": []string{plannedShipDate}}
res := struct {
Expand All @@ -400,3 +405,18 @@ func (c *Client) GetShipmentEstimatedDeliveryDateWithContext(ctx context.Context
err = c.do(ctx, http.MethodGet, "shipments/"+shipmentID+"/smartrate/delivery_date", vals, &res)
return
}

// RecommendShipDateForShipment retrieves the recommended ship date of each rate for a Shipment via the Precision Shipping API, based on a specific desired delivery date.
func (c *Client) RecommendShipDateForShipment(shipmentID string, desiredDeliveryDate string) (out []*RecommendShipDateForShipmentResult, err error) {
return c.RecommendShipDateForShipmentWithContext(context.Background(), shipmentID, desiredDeliveryDate)
}

// RecommendShipDateForShipmentWithContext performs the same operation as RecommendShipDateForShipment, but allows specifying a context that can interrupt the request.
func (c *Client) RecommendShipDateForShipmentWithContext(ctx context.Context, shipmentID string, desiredDeliveryDate string) (out []*RecommendShipDateForShipmentResult, err error) {
vals := url.Values{"desired_delivery_date": []string{desiredDeliveryDate}}
res := struct {
Results *[]*RecommendShipDateForShipmentResult `json:"rates,omitempty"`
}{Results: &out}
err = c.do(ctx, http.MethodGet, "shipments/"+shipmentID+"/smartrate/precision_shipping", vals, &res)
return
}
95 changes: 95 additions & 0 deletions smart_rate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package easypost

import (
"context"
)

// TimeInTransitDetailsForDeliveryDate contains the time-in-transit details and estimated delivery date for a specific DeliveryDateForZipPairEstimate.
type TimeInTransitDetailsForDeliveryDate struct {
PlannedShipDate *DateTime `json:"planned_ship_date,omitempty"`
EasyPostEstimatedDeliveryDate *DateTime `json:"easypost_estimated_delivery_date,omitempty"`
DaysInTransit *TimeInTransit `json:"days_in_transit,omitempty"`
}

// DeliveryDateForZipPairEstimate is a single zip-pair-based delivery date estimate for a carrier-service level combination.
type DeliveryDateForZipPairEstimate struct {
Carrier string `json:"carrier,omitempty"`
Service string `json:"service,omitempty"`
TimeInTransitDetails *TimeInTransitDetailsForDeliveryDate `json:"easypost_time_in_transit_data,omitempty"`
}

// EstimateDeliveryDateForZipPairResult is the result of the EstimateDeliveryDateForZipPair method, containing the estimated delivery date of each carrier-service level combination and additional metadata.
type EstimateDeliveryDateForZipPairResult struct {
CarriersWithoutEstimates []string `json:"carriers_without_tint_estimates,omitempty"`
nwithan8 marked this conversation as resolved.
Show resolved Hide resolved
FromZip string `json:"from_zip,omitempty"`
ToZip string `json:"to_zip,omitempty"`
SaturdayDelivery bool `json:"saturday_delivery,omitempty"`
PlannedShipDate *DateTime `json:"planned_ship_date,omitempty"`
Results []*DeliveryDateForZipPairEstimate `json:"results,omitempty"`
}

// EstimateDeliveryDateForZipPairParams are used in the EstimateDeliveryDateForZipPair method.
type EstimateDeliveryDateForZipPairParams struct {
FromZip string `json:"from_zip,omitempty"`
ToZip string `json:"to_zip,omitempty"`
Carriers []string `json:"carriers,omitempty"`
PlannedShipDate string `json:"planned_ship_date,omitempty"`
SaturdayDelivery bool `json:"saturday_delivery,omitempty"`
}

// TimeInTransitDetailsForShipDate contains the time-in-transit details and estimated delivery date for a specific ShipDateForZipPairRecommendation or RecommendShipDateForShipmentResult.
type TimeInTransitDetailsForShipDate struct {
DesiredDeliveryDate *DateTime `json:"desired_delivery_date,omitempty"`
EasyPostRecommendedShipDate *DateTime `json:"ship_on_date,omitempty"`
nwithan8 marked this conversation as resolved.
Show resolved Hide resolved
DeliveryDateConfidence float64 `json:"delivery_date_confidence,omitempty"`
EstimatedTransitDays int `json:"estimated_transit_days,omitempty"`
DaysInTransit *TimeInTransit `json:"days_in_transit,omitempty"`
}

// ShipDateForZipPairRecommendation is a single zip-pair-based ship date recommendation for a carrier-service level combination.
type ShipDateForZipPairRecommendation struct {
Carrier string `json:"carrier,omitempty"`
Service string `json:"service,omitempty"`
TimeInTransitDetails *TimeInTransitDetailsForShipDate `json:"easypost_time_in_transit_data,omitempty"`
nwithan8 marked this conversation as resolved.
Show resolved Hide resolved
}

// RecommendShipDateForZipPairResult is the result of the RecommendShipDateForZipPair method, containing the recommended ship date of each carrier-service level combination and additional metadata.
type RecommendShipDateForZipPairResult struct {
CarriersWithoutEstimates []string `json:"carriers_without_tint_estimates,omitempty"`
nwithan8 marked this conversation as resolved.
Show resolved Hide resolved
FromZip string `json:"from_zip,omitempty"`
ToZip string `json:"to_zip,omitempty"`
SaturdayDelivery bool `json:"saturday_delivery,omitempty"`
DesiredDeliveryDate *DateTime `json:"desired_delivery_date,omitempty"`
Results []*ShipDateForZipPairRecommendation `json:"results,omitempty"`
}

// RecommendShipDateForZipPairParams are used in the RecommendShipDateForZipPair method.
type RecommendShipDateForZipPairParams struct {
FromZip string `json:"from_zip,omitempty"`
ToZip string `json:"to_zip,omitempty"`
Carriers []string `json:"carriers,omitempty"`
DesiredDeliveryDate string `json:"desired_delivery_date,omitempty"`
SaturdayDelivery bool `json:"saturday_delivery,omitempty"`
}

// EstimateDeliveryDateForZipPair retrieves the estimated delivery date of each carrier-service level combination via the Smart Deliver By API, based on a specific ship date and origin-destination postal code pair.
func (c *Client) EstimateDeliveryDateForZipPair(params *EstimateDeliveryDateForZipPairParams) (out *EstimateDeliveryDateForZipPairResult, err error) {
return c.EstimateDeliveryDateForZipPairWithContext(context.Background(), params)
}

// EstimateDeliveryDateForZipPairWithContext performs the same operation as EstimateDeliveryDateForZipPair, but allows specifying a context that can interrupt the request.
func (c *Client) EstimateDeliveryDateForZipPairWithContext(ctx context.Context, params *EstimateDeliveryDateForZipPairParams) (out *EstimateDeliveryDateForZipPairResult, err error) {
err = c.post(ctx, "smartrate/deliver_by", params, &out)
return
}

// RecommendShipDateForZipPair retrieves the recommended ship date of each carrier-service level combination via the Smart Deliver On API, based on a specific desired delivery date and origin-destination postal code pair.
func (c *Client) RecommendShipDateForZipPair(params *RecommendShipDateForZipPairParams) (out *RecommendShipDateForZipPairResult, err error) {
return c.RecommendShipDateForZipPairWithContext(context.Background(), params)
}

// RecommendShipDateForZipPairWithContext performs the same operation as RecommendShipDateForZipPair, but allows specifying a context that can interrupt the request.
func (c *Client) RecommendShipDateForZipPairWithContext(ctx context.Context, params *RecommendShipDateForZipPairParams) (out *RecommendShipDateForZipPairResult, err error) {
err = c.post(ctx, "smartrate/deliver_on", params, &out)
return
}
56 changes: 56 additions & 0 deletions tests/cassettes/TestEstimateDeliveryDateForZipPair.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading