<?xml version="1.0" encoding="utf-8"?>
<AlvaoApplication xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ModelVersion="1">
  <Applications>
    <Application id="10">
      <Name>Automatic device warranty discovery</Name>
      <Description>The application periodically retrieves warranties for Dell devices from the support system using their serial numbers (service tags). To ensure the application functions correctly, request client_id and client_secret from the Dell TechDirect API team and enter them into the DellWarrantyApi.ClientId and DellWarrantyApi.ClientSecret settings in the Advanced settings.</Description>
      <UniqueId>0078649d-c4d5-4637-8f0b-e5dcb1169cf2</UniqueId>
      <Version>2</Version>
      <AdvancedSettings>
        <Setting>
          <Name>DellWarrantyApi.ClientId</Name>
          <Value />
        </Setting>
        <Setting>
          <Name>DellWarrantyApi.ClientSecret</Name>
          <Value />
        </Setting>
      </AdvancedSettings>
      <Scripts>
        <Script id="20">
          <Name>LoadWarrantyPeriodicAction</Name>
          <Code>using System;
using System.Data;
using Microsoft.Data.SqlClient;
using Alvao.Global;
using Alvao.API.Common;
using Alvao.Apps.API;
using Alvao.API.AM;
using Alvao.API.Common.Model.Database;
using Alvao.Context;
using System.Collections.Generic;
using Dapper;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Linq;
using Newtonsoft.Json;

public class LoadWarrantyPeriodicAction : IPeriodicAction
{
    public string Name 
    { 
        get =&gt; "LoadWarrantyPeriodicAction";
        set { }
    }

    public void OnPeriod(SqlConnection con)
    {
        var logger = Alvao.API.Internal.TenantDiagnosticsLog.Get();
        
        if(!Activation.IsModuleActivated(ModuleInfo.ModuleId.AMCustomApps))
        {
            logger.Warn($"Automatic device warranty discovery cannot be used. Module AM Custom Apps is not activated.");
            return;
        }
        
        if(DateTime.Now.Hour != 0)
            return;
        
        var integrations = GetVendorIntegrations();
        foreach(var integration in integrations)
        {
            try {
                logger.Info($"Loading warranty data for {integration.VendorName} devices.");
                var warranties = integration.GetDeviceWarranties();
                foreach(var warranty in warranties)
                {
                    ObjectProperty.Update(warranty.Key, tblKind.KindCode.WarrantyUntil, warranty.Value, true, true);
                }
                logger.Info($"Successfully loaded warranty data for {warranties.Count()} devices.");
            }
            catch(Exception ex)
            {
                logger.Error(ex, $"Error while loading warranty data for {integration.VendorName} devices: {ex.Message}");
            }
        }
    }   

    internal static IEnumerable&lt;IVendorWarrantyIntegration&gt; GetVendorIntegrations()
    {
        var vendorInterface = typeof(IVendorWarrantyIntegration);
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        var integrations = new List&lt;IVendorWarrantyIntegration&gt;();
        foreach(var assembly in assemblies)
        {
            foreach(var at in assembly.GetTypes())
            {
                if (vendorInterface.IsAssignableFrom(at))
                {
                    if (!at.IsInterface)
                        integrations.Add((IVendorWarrantyIntegration)Activator.CreateInstance(at));
                }
            }
        }
 
        return integrations;
    }
}

public interface IVendorWarrantyIntegration 
{
    public string VendorName { get; }
    public IDictionary&lt;int, DateTime?&gt; GetDeviceWarranties(); // nodeId =&gt; warranty
}

public class DellWarrantyIntegration : IVendorWarrantyIntegration 
{
    private static readonly HttpClient client = new HttpClient();
    private const int BatchSize = 100; // 100 = Dell Api limit
    private const string DellApiUrl = "https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5";
    public string VendorName =&gt; "Dell";
    public IDictionary&lt;int, DateTime?&gt; GetDeviceWarranties() 
    {
        Dictionary&lt;int, DateTime?&gt; warranties = new();

        var devices = GetDellDeviceServiceTags();
        int currentBatchSize = 0;
        int processed = 0;
        do {
            var batch = devices.Skip(processed).Take(BatchSize);
            currentBatchSize = batch.Count();

            warranties = warranties.Concat(LoadWarrantiesFromApi(batch)).ToDictionary(kvp =&gt; kvp.Key, kvp =&gt; kvp.Value);

            processed += currentBatchSize;
        }
        while(currentBatchSize &gt; 0);

        return warranties.Where(w =&gt; w.Value.HasValue).ToDictionary(w =&gt; w.Key, w =&gt; w.Value);
    }
    
    private Dictionary&lt;int, DateTime?&gt; LoadWarrantiesFromApi(IEnumerable&lt;Device&gt; devices)
    {
        if(!devices.Any())
            return new Dictionary&lt;int, DateTime?&gt;();

        var serialNumberDeviceDict = devices.ToDictionary(d =&gt; d.SerialNumber, d =&gt; d);
            
        string tags = string.Join(",", devices.Select(d =&gt; d.SerialNumber));
        var httpRequest = new HttpRequestMessage(HttpMethod.Get, $"{DellApiUrl}/asset-entitlements?servicetags={tags}");
        httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GetToken());
        var response = client.SendAsync(httpRequest).Result;
        var responseContent = response.Content.ReadAsStringAsync().Result;
        if (response.IsSuccessStatusCode)
        {
            var apiResponse = JsonConvert.DeserializeObject&lt;IEnumerable&lt;DellWarranty&gt;&gt;(responseContent);

            foreach(var warranty in apiResponse)
            {
                if(!serialNumberDeviceDict.ContainsKey(warranty.ServiceTag))
                {
                    // data for this device is not available
                    continue;
                }

                serialNumberDeviceDict[warranty.ServiceTag].Warranty = warranty.GetWarranty();
            }

            return serialNumberDeviceDict.ToDictionary(d =&gt; d.Value.Id, d =&gt; d.Value.Warranty);

        }
        else 
            throw new Exception($"Unsuccessful response ({response.StatusCode}) received from Dell Api: {responseContent}");
    }


    static string GetToken()
    {
        using (var client = new HttpClient())
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "https://apigtwb2c.us.dell.com/auth/oauth/v2/token");
            var requestBody = new FormUrlEncodedContent(new[]
            {
            new KeyValuePair&lt;string, string&gt;("grant_type", "client_credentials"),
            new KeyValuePair&lt;string, string&gt;("client_id", DbProperty.VendorsDellWarrantyApiClientId),
            new KeyValuePair&lt;string, string&gt;("client_secret", DbProperty.VendorsDellWarrantyApiClientSecret)
            });

            request.Content = requestBody;
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            var response = client.Send(request);
            if(!response.IsSuccessStatusCode)
            {
                throw new Exception("Invalid credentials provided. Check Vendors.Dell.WarrantyApi.ClientId and Vendors.Dell.WarrantyApi.ClientSecret.");
            }

            var responseContent = response.Content.ReadAsStringAsync().Result;
            var tokenResponse = Newtonsoft.Json.JsonConvert.DeserializeObject&lt;TokenResponse&gt;(responseContent);

            return tokenResponse.AccessToken;
        }
    }
    public class TokenResponse
    {
        [Newtonsoft.Json.JsonProperty("access_token")]
        public string AccessToken { get; set; }
    } 

    private IEnumerable&lt;Device&gt; GetDellDeviceServiceTags()
    {
        using var scope = AlvaoContext.GetConnectionScope();
        return scope.Connection.Query&lt;Device&gt;(
            @"select NodeId as Id, SerialNumber 
            from NodeCust nc
            join tblNode n on nc.NodeId = n.intNodeId
            where Manufacturer like 'Dell%' and SerialNumber is not null and n.IsDiscarded = 0 and n.IsRemoved=0", null, scope.Transaction);
    }

    public class DellWarranty 
    {
        public string ServiceTag { get; set; }
        public IEnumerable&lt;Entitlement&gt; Entitlements { get; set; }

        public DateTime? GetWarranty()
        {
            return Entitlements.Any() ? Entitlements.Max(e =&gt; e.EndDate) : null;
        }
    }

    public class Entitlement 
    {
        public DateTime EndDate { get; set; }
    }
}

public class Device 
{
    public int Id { get; set; }
    public string SerialNumber { get; set; }
    public DateTime? Warranty { get; set;}
}</Code>
          <IsLibCode>false</IsLibCode>
          <Codesign>g0Vqh1m9Oxg+QySD+lxQmM92kmEiW69FHIu9DJobpteClAGuXrT/aRVTMwytsohGXfAQ1chxTsgCdfbzlMZAAAwJNIlgzu9FQTOjwqQjd3lJF4qoeDYsKAIKf2y/QwhzMor9qJQStj/RXX8wIc8f5TmWQ8a3yd+wHryuEPyZXCSANE7KhjBPzlYOSKulErTTb8HVHlJ/R9Z0yfglaKG1GSpAppXDIuGW3P2jdtbNuM9rVLveIUUMZJXJRcoJQvVniHYDblCWd46N5DfQZn7+dL9AhbbrJt1htwDMYtJ6VPngv/NnANnKbvUMk21cxIZOai5Ugg107IjBljrVNGZBpA==</Codesign>
        </Script>
      </Scripts>
    </Application>
  </Applications>
</AlvaoApplication>