I’ve been trying out the BETA functionality for generating commerce reports in Episerver. By default, it will create an CSV export of all orders made that day and it allows for some extensibility. In this post I’ll show you how you can add extra data to the report and I’ll show you how to customize the report completely by implementing your own IReportingService.

Reports for Commerce

Sales reports in Commerce Manager (CM) have existed for a long time. But, as Episerver is slowly phasing out CM, it comes to no surprise that they’ve started to move some of that functionality to the CMS as well. Episerver has enabled the BETA functionality for Commerce Reports by default. Reports are created by the scheduled job called Collect Report Data, which will create a zipped up CSV file with the orders for that day:

Commerce report

It’s not hard to add custom data to this report, as described on this page on World:

// Implement your own additional data handler
public class MyReportingAdditionalDataHandler : ReportingAdditionalDataHandler
{
    private readonly IOrderRepository _orderRepository;

    public MyReportingAdditionalDataHandler(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

    public override IEnumerable<string> GetAdditionalData(int lineItemId, int orderGroupId)
    {
        var order = _orderRepository.Load<IPurchaseOrder>(orderGroupId);
        return new string[]
        {
            // Add additional data here
            order?.Properties["MyCustomProp"]?.ToString()
        };
    }
}

//Register it in
public class StructureMapRegistry : Registry
{
    public StructureMapRegistry()
    {
        //...
        For<ReportingAdditionalDataHandler>().Singleton().Use<MyReportingAdditionalDataHandler>();
    }
}

However, the extensibility is a bit lacking a bit as you can’t add any headers to the csv file. Next to that, it doesn’t seem to escape CSV data properly yet, for which a bug has been created (COM-11284, not public yet). I ended up with invalid CSV as some product names contained a comma, which broke the CSV.

Implementing a custom reporting service

Fortunately it is really easy to implement your own reporting service! All you have to do is implement a IReportingService yourself. By doing so, you’re free to implement anything you want. What I’ll do is:

  • Re-use the code from Epi in order to gather order data
    • My service will be quite similar to the one from Episerver, with some additional fields
  • Map it to a custom object
    • Simple POCO object with some header names
  • Write it as CSV inside a zip file
    • I’ll use the CsvHelper library, which will handle writing CSV for me

The code:

public class ReportingService : IReportingService
{
    private readonly IBlobFactory _blobFactory;
    private readonly IContentRepository _contentRepository;
    private readonly IUrlSegmentGenerator _urlSegmentGenerator;
    private readonly ReportingDataLoader _reportingDataLoader;
    private readonly OrderReportingMapper _orderReportingMapper;

    public ReportingService(
        IBlobFactory blobFactory,
        IContentRepository contentRepository,
        IUrlSegmentGenerator urlSegmentGenerator,
        ReportingDataLoader reportingDataLoader,
        OrderReportingMapper orderReportingMapper)
    {
        _blobFactory = blobFactory;
        _contentRepository = contentRepository;
        _urlSegmentGenerator = urlSegmentGenerator;
        _reportingDataLoader = reportingDataLoader;
        _orderReportingMapper = orderReportingMapper;
    }

    public virtual ContentReference ExportOrderDataAsCsv(
        DateTime fromDate,
        DateTime toDate)
    {
        // This is where the reports are stored by epi
        var reportingMediaData = _contentRepository.GetDefault<ReportingMediaData>(CommerceReportingFolder.ReportingRoot);
        // We can create a blob to hold our data
        var blob = _blobFactory.CreateBlob(reportingMediaData.BinaryDataContainer, ".commercereport");
        // Default naming
        var str = "OrderData-from-" + fromDate.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture) + "-to-" + toDate.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture);

        // Open blob, create an zip archive and a csv entry
        using (var stream = blob.OpenWrite())
        using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Create, false))
        using (var entryStream = zipArchive.CreateEntry(str + ".csv").Open())
        using (var streamWriter = new StreamWriter(entryStream))
        using (var csv = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
        {
            // Use default functionality to load order report data
            var data = _reportingDataLoader.GetReportingData(
                fromDate.ToUniversalTime(),
                toDate.ToUniversalTime()
            );
            // Use a custom mapper and write csv
            csv.WriteRecords(_orderReportingMapper.Map(data));
        }

        // Default functionality, 'overwrite' any existing report
        reportingMediaData.BinaryData = blob;
        reportingMediaData.Name = str + ".zip";
        DeleteDuplicatedReport(reportingMediaData.Name);
        return _contentRepository.Save(reportingMediaData, SaveAction.Publish, AccessLevel.NoAccess);
    }
    //Removed the rest for brevity
}

// Simple mapper to create a POCO
public class OrderReportingMapper
{
    public IEnumerable<OrderLineItemRecord> Map(IEnumerable<LineItemReportingModel> orders)
    {
        return orders.Select(MapItem);
    }

    private OrderLineItemRecord MapItem(LineItemReportingModel item)
    {
        // Add additional vipps data to commerce-reports
        var order = _orderRepository.Load<IPurchaseOrder>(item.OrderGroupId);

        return new OrderLineItemRecord
        {
            LineItemId = item.LineItemId,
            MyCustomProp = order?.Properties["MyCustomProp"]?.ToString()
        };
    }
}

// POCO for writing csv
using CsvHelper.Configuration.Attributes;
public class OrderLineItemRecord
{
    [Name("Line Item Id")]
    public int LineItemId { get; set; }
    [Name("My Custom Prop")]
    public int MyCustomProp { get; set; }
}

A complete implementation can be found on on GitHub. When you run the Collect Report Data job, your custom report will be generated and can be downloaded from the reports tab.

Conclusion

In this blog post we’ve looked at the BETA functionality for generating reports in the CMS. We’ve seen how to add additional data to the default CSV report. And we’ve implemented a custom reporting service which will allow you to easily export files whatever you need.