Shipment tracking

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.
12 years ago
I would like to see a tracking service.
I've made one myself but it would be nice to have it as a default feature. At least have the possiblity to create it as a plugin.

As we already have the field for "Tracking Number", we can quite easily create a "TrackingService" that just gets a list of shipment events.

A very pluggable way of making this would be to have an interface, (I call it ITracker but IShipmentTracker might be a better name).

If needed, I will be more than happy to contribute with whatever is needed to create this possiblity.

The interface could look like:

    public interface ITracker
    {
        /// <summary>
        /// Gets if the current tracker can track the tracking number.
        /// </summary>
        /// <param name="trackingNumber">The tracking number to track.</param>
        /// <returns>True if the tracker can track, otherwise false.</returns>
        bool IsMatch(string trackingNumber);
        /// <summary>
        /// Gets a url for a page to show tracking info (third party tracking page).
        /// </summary>
        /// <param name="trackingNumber">The tracking number to track.</param>
        /// <param name="language">The language to show.</param>
        /// <returns>A url to a trackning page.</returns>
        string GetUrl(string trackingNumber, Language language);
        /// <summary>
        /// Gets all events for a tracking number.
        /// </summary>
        /// <param name="trackingNumber">The tracking number to track</param>
        /// <returns>List of Shipment Events.</returns>
        IList<ShipmentStatusEvent> GetShipmentEvents(string trackingNumber);
    }

A simple code for the tracking service might be:

    public class TrackingService : ITrackingService
    {
        private IList<ITracker> _trackers;
        public TrackingService()
        {
            _trackers = GetAllTrackers();
        }

        public bool CanTrack(string trackingNumber)
        {
            if (string.IsNullOrEmpty(trackingNumber))
                return false;
            return _trackers.Any(c => c.IsMatch(trackingNumber));
        }

        public string GetUrl(string trackingNumber, Language language)
        {
            var tracker = GetTrackerByTrackingNumber(trackingNumber);
            return tracker.GetUrl(trackingNumber, language);
        }

        private static IList<ITracker> GetAllTrackers()
        {
            //Code to get all tracker plugins
        }

        private ITracker GetTrackerByTrackingNumber(string trackingNumber)
        {
            var tracker = _trackers.FirstOrDefault(c => c.IsMatch(trackingNumber));
            if (tracker == null)
                throw new ArgumentException(string.Format("Could not find any tracker for tracking number {0}", trackingNumber));
            return tracker;
        }

        public IList<ShipmentStatusEvent> GetEvents(string trackingNumber)
        {
            try
            {
                var tracker = GetTrackerByTrackingNumber(trackingNumber);
                return tracker.GetShipmentEvents(trackingNumber);
            }
            catch
            {
                return new List<ShipmentStatusEvent>();
            }
        }
    }
12 years ago
Hi Oskar,

Thanks a lot for this suggestion. Good idea. I'll create a work item

UPDATE: here is the work item
12 years ago
BTW, have you implemented ITracker for some of the existing shipping rate computation providers (UPS or Fedex, for example)?
12 years ago
a.m. wrote:
BTW, have you implemented ITracker for some of the existing shipping rate computation providers (UPS or Fedex, for example)?

I have for UPS and Schenker.
UPS

public class UpsTracker : ITracker
    {
        public bool IsMatch(string trackingNumber)
        {
      //Not sure if this is true for every shipment, but it is true for all of our shipments
            return trackingNumber.ToUpperInvariant().StartsWith("1Z");
        }

        public string GetUrl(string trackingNumber, Core.Domain.Localization.Language language)
        {
            string url = "http://wwwapps.ups.com/WebTracking/track?HTMLVersion=5.0&loc={0}&Requester=UPSHome&WBPM_lid=homepage%2Fct1.html_pnl_trk&trackNums={1}&track.x=S%C3%B6k";
            string loc = language.LanguageCulture.Replace('-', '_');
            url = string.Format(url, loc, trackingNumber);
            return url;
        }



        public IList<ShipmentStatusEvent> GetShipmentEvents(string trackingNumber)
        {
            TrackService track = new TrackService();
            TrackRequest tr = new TrackRequest();
            UPSSecurity upss = new UPSSecurity();
            UPSSecurityServiceAccessToken upssSvcAccessToken = new UPSSecurityServiceAccessToken();
            upssSvcAccessToken.AccessLicenseNumber = "ACCESSCODE";
            upss.ServiceAccessToken = upssSvcAccessToken;
            UPSSecurityUsernameToken upssUsrNameToken = new UPSSecurityUsernameToken();
            upssUsrNameToken.Username = "USERNAME";
            upssUsrNameToken.Password = "PASSWORD";
            upss.UsernameToken = upssUsrNameToken;
            track.UPSSecurityValue = upss;
            RequestType request = new RequestType();
            String[] requestOption = { "15" };
            request.RequestOption = requestOption;
            tr.Request = request;
            tr.InquiryNumber = trackingNumber;
            System.Net.ServicePointManager.ServerCertificateValidationCallback += delegate { return true; };
            TrackResponse trackResponse = track.ProcessTrack(tr);
            return trackResponse.Shipment.SelectMany(c => c.Package[0].Activity.Select(x => ToStatusEvent(x))).ToList();
        }

        private ShipmentStatusEvent ToStatusEvent(ActivityType activity)
        {
            var ev = new ShipmentStatusEvent();
            switch (activity.Status.Type)
            {
                case "I":
                    if (activity.Status.Code == "DP")
                    {
                        ev.EventType = ShipmentEventType.Departed;
                    }
                    else if (activity.Status.Code == "EP")
                    {
                        ev.EventType = ShipmentEventType.ExportScanned;
                    }
                    else if (activity.Status.Code == "OR")
                    {
                        ev.EventType = ShipmentEventType.OriginScanned;
                    }
                    else
                    {
                        ev.EventType = ShipmentEventType.Arrived;
                    }
                    break;
                case "X":
                    ev.EventType = ShipmentEventType.NotDelivered;
                    break;
                case "M":
                    ev.EventType = ShipmentEventType.Booked;
                    break;
                case "D":
                    ev.EventType = ShipmentEventType.Delivered;
                    break;
            }
            string dateString = string.Concat(activity.Date, " ", activity.Time);
            ev.Date = DateTime.ParseExact(dateString, "yyyyMMdd HHmmss", CultureInfo.InvariantCulture);
            ev.CountryCode = activity.ActivityLocation.Address.CountryCode;
            ev.Location = activity.ActivityLocation.Address.City;
            return ev;
        }
    }


Schenker


    public class SchenkerTracker : ITracker
    {
        public bool IsMatch(string trackingNumber)
        {
            //Return true if it starts with your serie
        }

        public string GetUrl(string trackingNumber, Core.Domain.Localization.Language language)
        {
            string url = "http://was.schenker.nu/ctts-a/com.dcs.servicebroker.http.HttpXSLTServlet?request.service=CTTSTYPEA&request.method=search&clientid=&language={0}&country=SE&reference_type=*DWB&reference_number={1}";
            string lang = language.UniqueSeoCode;
            url = string.Format(url, lang, trackingNumber);
            return url;
        }
        private string GetXml(string referenceType, string reference)
        {
      //DOn't forget to put the password and username in the url below (might be good to put this as a setting)
            string url = "http://was.schenker.nu/ctts-a/com.dcs.servicebroker.http.HttpXSLTServlet?request.service=CTTSTYPEA_PLUGIN&request.method=search&request.format=xmledifact&reference_type=*{0}&reference_number={1}&request.user.id=USERID&request.user.password=USERPASSWORD";
            url = string.Format(url, referenceType, reference);
            using (WebClient client = new WebClient())
            {
                client.Encoding = Encoding.UTF8;
                return client.DownloadString(url);
            }
        }

        public IList<ShipmentStatusEvent> GetShipmentEvents(string trackingNumber)
        {
            
            List<ShipmentStatusEvent> events = new List<ShipmentStatusEvent>();

            string xml = GetXml("DWB", trackingNumber);
            StringReader reader = new StringReader(xml);
            XPathDocument doc = new XPathDocument(reader);
            XPathNavigator nav = doc.CreateNavigator();
            var countryelement = nav.SelectSingleNode("//country_of_destination");
            string destCountry = countryelement == null ? string.Empty : countryelement.Value;
            foreach (XPathNavigator node in nav.Select("//status_event"))
            {
                ShipmentStatusEvent ev = new ShipmentStatusEvent();
                string eventType = node.SelectSingleNode(".//status_event_type").Value;
                string eventCode = node.SelectSingleNode(".//status_event_code").Value;
                switch (eventCode)
                {
                    case "*800":
                        if (eventType == "*001")
                        {
                            ev.EventType = ShipmentEventType.Booked;
                        }
                        else
                        {
                            ev.EventType = ShipmentEventType.Departed;
                        }
                        break;
                    case "MAN":
                        ev.EventType = ShipmentEventType.Departed;
                        break;
                    case "COL":
                        ev.EventType = ShipmentEventType.Collected;
                        break;
                    case "ENM":
                    case "*950":
                        ev.EventType = ShipmentEventType.Arrived;
                        break;
                    case "DOT":
                        ev.EventType = ShipmentEventType.OutForDelivery;
                        break;
                    case "DLV":
                        ev.EventType = ShipmentEventType.Delivered;
                        break;
                    case "NDL":
                        ev.EventType = ShipmentEventType.NotDelivered;
                        break;
                    case "PUP":
                        ev.EventType = ShipmentEventType.PickedUpByCustomer;
                        break;
                    case "DET":
                    case "TIN":
                        continue;
                }
                string dateElement = node.SelectSingleNode(".//status_event_date").Value;
                string timeElement = node.SelectSingleNode(".//status_event_time").Value;
                string date = string.Concat(dateElement, " ", timeElement);
                ev.Date = DateTime.ParseExact(date, "yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
                
                ev.CountryCode = node.SelectSingleNode(".//status_event_country_code").Value;
                ev.Location = node.SelectSingleNode(".//status_event_place_name").Value;
                events.Add(ev);
            }
            if (destCountry.Trim() == "Norway" && !trackingNumber.StartsWith("26460"))
                events.AddRange(GetShipmentEvents(string.Concat("26460", trackingNumber)));
            return events;
        }
    }


ShipmentStatusEvent

    public class ShipmentStatusEvent
    {
        public ShipmentEventType EventType { get; set; }
        public string Location { get; set; }
        public string CountryCode { get; set; }
        public DateTime Date { get; set; }
    }


ShipmentEventType


    public enum ShipmentEventType
    {
        Booked = 0,
        Collected = 1,
        Departed = 2,
        Arrived = 3,
        OutForDelivery = 4,
        NotDelivered = 5,
        Delivered = 6,
        OriginScanned = 7,
        ExportScanned = 8,
        PickedUpByCustomer = 9
    }
12 years ago
Thanks a lot for sharing it!
12 years ago
a.m. wrote:
Thanks a lot for sharing it!

Np. If you need more code, please tell me. I have code for the display as well
12 years ago
Sure, it would be great and can save a couple of hours when adding to the official release!
12 years ago
a.m. wrote:
Sure, it would be great and can save a couple of hours when adding to the official release!

In OrderDetailsModel

public string TrackingUrl { get; set; }
public IList<ShipmentEventModel> ShipmentEvents { get; set; }

In PrepareOrderDetailsModel

if (order.ShippingStatus != ShippingStatus.ShippingNotRequired)
{
    .......
   model.TrackingNumber = order.TrackingNumber;
                if (_trackingService.CanTrack(model.TrackingNumber))
                {
                    model.TrackingUrl = _trackingService.GetUrl(model.TrackingNumber, _workContext.WorkingLanguage);
                    model.ShipmentEvents = _trackingService.GetEvents(model.TrackingNumber).Select(c => PrepareShipmentModel(c)).ToList();
                }
                else
                {
                    model.ShipmentEvents = new List<OrderDetailsModel.ShipmentEventModel>();
                }
}


PrepareShipmentModel

[NonAction]
        private OrderDetailsModel.ShipmentEventModel PrepareShipmentModel(ShipmentStatusEvent ev)
        {
            var model = new OrderDetailsModel.ShipmentEventModel();

            model.Country = _countryService.GetCountryByTwoLetterIsoCode(ev.CountryCode).Name;
            model.Date = ev.Date;
            model.EventType = _localizationService.GetResource(ev.EventType.GetLocalizedEnum(_localizationService, _workContext));
            model.Location = ev.Location;

            return model;
        }


In the order details view (Details.cshtml)
Where the tracking number is printed

@if (!String.IsNullOrEmpty(Model.TrackingNumber))
{
    <br />
    <b>
        @T("Order.TrackingNumber")</b>
    <br />
    if (string.IsNullOrEmpty(Model.TrackingUrl))
    {
        <text>@Model.TrackingNumber</text>
    }
    else
    {
        <a href="@Model.TrackingUrl" target="_blank">@Model.TrackingNumber</a>
    }
}

After the first table in order-details-box

@if (Model.ShipmentEvents.Any())
{
    <table width="100%" cellspacing="0" cellpadding="2" border="0">
        <tr>
            <td>
                <b>@T("Tracking.Tracking")</b>
            </td>
        </tr>
        <tr>
            <td>
                <b>@T("Tracking.Event")</b>
            </td>
            <td>
                <b>@T("Tracking.Location")</b>
            </td>
            <td>
                <b>@T("Tracking.Country")</b>
            </td>
            <td>
                <b>@T("Tracking.Date")</b>
            </td>
        </tr>
        <tbody>
            @foreach (var ev in Model.ShipmentEvents)
            {
                <tr>
                    <td>
                        @ev.EventType
                    </td>
                    <td>
                        @ev.Location
                    </td>
                    <td>
                        @ev.Country
                    </td>
                    <td>
                        @ev.Date
                    </td>
                </tr>
            }
        </tbody>
    </table>
}
12 years ago
Oskar,

I started working on integrating this source code into nopCommerce, but I cannot find referenced classes in UpsTracker (such as UPSSecurity, UPSSecurityServiceAccessToken, etc). I presume they were generated when you added a web service reference, but I cannot find its URL. What is it?
12 years ago
a.m. wrote:
Oskar,

I started working on integrating this source code into nopCommerce, but I cannot find referenced classes in UpsTracker (such as UPSSecurity, UPSSecurityServiceAccessToken, etc). I presume they were generated when you added a web service reference, but I cannot find its URL. What is it?

You need to apply for an account on www.ups.com. Then you can download the classes.


Oskar
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.