private static async Task <IEnumerable <IMiragtionProviderItem> > GetProvidersFromCsv(
            string filename,
            IBlobStorageHelper blobStorageHelper,
            StringBuilder logFile,
            CloudBlobContainer containerProviderFiles)
        {
            var migrationProviderCsv = filename;

            logFile.AppendLine($"Attempting to get content from Migration Provider CSV file: {migrationProviderCsv}");

            var migrationProvidersCsvContent =
                await blobStorageHelper.ReadFileAsync(containerProviderFiles, migrationProviderCsv);

            logFile.AppendLine(
                $"Got content from Migration Provider CSV file: {migrationProviderCsv} [content length: {migrationProvidersCsvContent.Length}]");

            logFile.AppendLine(migrationProviderCsv);

            logFile.AppendLine(migrationProvidersCsvContent);

            logFile.AppendLine($"Attempting to deserialise content into a IEnumerable<{nameof(MiragtionProviderItem)}>");

            var mpItems = MigrationProviderItemHelper.GetMiragtionProviderItems(migrationProvidersCsvContent, logFile);

            logFile.AppendLine(
                $"Start of deserialised content into: {mpItems.Count()} items in IEnumerable<{nameof(MiragtionProviderItem)}>");

            logFile.Append(string.Join("," + Environment.NewLine, mpItems));

            logFile.AppendLine($"{Environment.NewLine}End of deserialised content");
            return(mpItems);
        }
        public XmlTransformHelper(AppSettings settings, IBlobStorageHelper helper)
        {
            this._settings = settings ?? throw new ArgumentNullException(nameof(settings));
            this._helper   = helper ?? throw new ArgumentNullException(nameof(helper));

            this._xslt      = new XslCompiledTransform(enableDebug: true);
            this._arguments = new XsltArgumentList();
        }
 public DeviceGroupsWriter(
     IBlobStorageConfig blobStorageConfig,
     IBlobStorageHelper blobStorageHelper,
     IFileWrapper fileWrapper,
     ILogger logger)
 {
     this.blobStorageConfig = blobStorageConfig;
     this.logger            = logger;
     this.blobStorageHelper = blobStorageHelper;
     this.fileWrapper       = fileWrapper;
 }
Пример #4
0
        public static async Task Run(
            [TimerTrigger("%ApprenticeshipMigrationSchedule%")] TimerInfo myTimer,
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IBlobStorageHelper blobStorageHelper,
            [Inject] IApprenticeshipServiceWrapper apprenticeshipServiceWrapper)
        {
            var logFile = new StringBuilder();

            logFile.AppendLine($"Starting {nameof(ApprenticeshipDeltaExport)} at {DateTime.Now}");

            var fileNames         = new List <string>();
            var last24HoursAgo    = DateTime.Today.AddDays(-1);
            var providersFileName = $"{DateTime.Today.ToString("yyyyMMdd")}\\Apprenticeships\\Apprenticeships_for_Providers_{DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss")}.json";

            logFile.AppendLine($"24 Hours ago: {last24HoursAgo}");
            logFile.AppendLine($"Apprenticeship Delta filename: {providersFileName}");

            var containerNameExporter = configuration["ContainerNameExporter"];
            var containerExporter     = blobStorageHelper.GetBlobContainer(containerNameExporter);

            try
            {
                logFile.AppendLine($"Attempting to call Apprenticeship API to gather apprenticeship delta updates");
                var apprenticeshipDelta = apprenticeshipServiceWrapper.GetApprenticeshipDeltaUpdatesAsJson();
                logFile.AppendLine($"Successful call to apprenticeship API");
                if (apprenticeshipDelta != null)
                {
                    logFile.AppendLine($"Apprenticeship Delta JSON returned");
                    var providersBlob = containerExporter.GetBlockBlobReference(providersFileName);
                    logFile.AppendLine($"Attempting to upload apprenticeship delta json");
                    await providersBlob.UploadTextAsync(apprenticeshipDelta);

                    logFile.AppendLine($"Upload successful");
                }
                else
                {
                    logFile.AppendLine($"No updated apprenticeships between {last24HoursAgo} and {DateTime.Now} ");
                }
            }
            catch (Exception e)
            {
                logFile.AppendLine(e.Message);
                logFile.AppendLine(e.ToString());
            }
            finally
            {
                logFile.AppendLine($"Ending {nameof(ApprenticeshipDeltaExport)} at {DateTime.Now}");
                var logFileName     = $"{DateTime.Today.ToString("yyyyMMdd")}\\Apprenticeships\\Logs\\Log_{DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss")}.txt";
                var logFileNameBolb = containerExporter.GetBlockBlobReference(logFileName);
                await logFileNameBolb.UploadTextAsync(logFile.ToString());
            }
        }
Пример #5
0
 public StatusService(
     ILogger logger,
     IHttpClient httpClient,
     IBlobStorageHelper blobStorageHelper,
     ICosmosDbSql cosmosDbSql,
     IServicesConfig servicesConfig
     )
 {
     this.log               = logger;
     this.httpClient        = httpClient;
     this.blobStorageHelper = blobStorageHelper;
     this.cosmosDbSql       = cosmosDbSql;
     this.servicesConfig    = servicesConfig;
     this.timeout           = this.servicesConfig.ConfigServiceTimeout;
 }
Пример #6
0
 public StatusService(
     ILogger logger,
     IStorageClient storageClient,
     ITimeSeriesClient timeSeriesClient,
     IHttpClient httpClient,
     IServicesConfig servicesConfig,
     IBlobStorageHelper blobStorageHelper)
 {
     this.log               = logger;
     this.storageClient     = storageClient;
     this.timeSeriesClient  = timeSeriesClient;
     this.httpClient        = httpClient;
     this.servicesConfig    = servicesConfig;
     this.blobStorageHelper = blobStorageHelper;
 }
Пример #7
0
 public static async Task Run(
     [TimerTrigger("%MigrationReportSchedule%")] TimerInfo myTimer,
     ILogger log,
     [Inject] IConfigurationRoot configuration,
     [Inject] ICosmosDbHelper cosmosDbHelper,
     [Inject] IBlobStorageHelper blobHelper,
     [Inject] IProviderCollectionService providerCollectionService,
     [Inject] ICourseCollectionService courseCollectionService,
     [Inject] IApprenticeshipCollectionService apprenticeshipCollectionService,
     [Inject] IMigrationReportCollectionService migrationReportCollectionService)
 {
     await new MigrationReportGeneratorService().Run(
         log, configuration, cosmosDbHelper, blobHelper, providerCollectionService, courseCollectionService,
         apprenticeshipCollectionService, migrationReportCollectionService
         );
 }
        private static async Task <IList <int> > GetProviderWhiteList(IBlobStorageHelper blobStorageHelper, CloudBlobContainer cloudBlobContainer)
        {
            var whiteList = await blobStorageHelper.ReadFileAsync(cloudBlobContainer, "ProviderWhiteList.txt");

            if (string.IsNullOrEmpty(whiteList))
            {
                return(new List <int>());
            }

            var list = new List <int>();

            foreach (var line in whiteList.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
            {
                if (int.TryParse(line, out int id))
                {
                    list.Add(id);
                }
            }

            return(list);
        }
Пример #9
0
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper,
            [Inject] IUkrlpApiService ukrlpApiService
            )
        {
            const string WHITE_LIST_FILE = "ProviderWhiteList.txt";
            const string ProviderAppName = "Provider.Migrator";

            var stopWatch = new Stopwatch();

            // TODO : Change to correct collection below
            var databaseId           = configuration["CosmosDbSettings:DatabaseId"];
            var providerCollectionId = configuration["CosmosDbCollectionSettings:ProvidersCollectionId"];
            var connectionString     = configuration.GetConnectionString("TribalRestore");
            var venueExportFileName  = $"ProviderExport-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var _cosmosClient        = cosmosDbHelper.GetClient();
            var blobContainer        = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);

            log.LogInformation($"WhitelistProviders : Start loading...");
            var whiteListProviders = await GetProviderWhiteList();

            log.LogInformation($"WhitelistProviders : Finished loading.");

            // Get all changed data from UKRLP API

            stopWatch.Reset();
            log.LogInformation($"UKRLApiService: Start getting data..");
            stopWatch.Start();
            var ukrlpApiProviders = ukrlpApiService.GetAllProviders(whiteListProviders.Select(p => p.ToString()).ToList());

            stopWatch.Stop();
            log.LogInformation($"UKRLApiService: Finished getting datain {stopWatch.ElapsedMilliseconds / 1000}.");

            int totalTribalCount    = 0;
            int totalAttemptedCount = 0;
            int totalUpdatedCount   = 0;
            int totalInsertedCount  = 0;

            var result = new List <ProviderResultMessage>();

            using (var sqlConnection = new SqlConnection(connectionString))
            {
                using (var command = sqlConnection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = @"SELECT 
                                                P.ProviderId,
                                                P.Ukprn,
                                                P.ProviderName,
                                                RS.RecordStatusId,
                                                RS.RecordStatusName,
		                                        P.RoATPFFlag,
		                                        P.RoATPProviderTypeId,
		                                        P.RoATPStartDate,
		                                        p.PassedOverallQAChecks,
		                                        P.MarketingInformation,
		                                        P.NationalApprenticeshipProvider,
		                                        P.TradingName,
		                                        P.UPIN,
												Ar.AddressLine1,
												Ar.AddressLine2,
												Ar.Town,
												Ar.County,
												Ar.Postcode,
												P.Email,
												P.Website,
												P.Telephone,
		                                        CASE   
		                                          WHEN Count(C.CourseId) > 0 THEN 1 
		                                          WHEN Count(C.CourseId) = 0 THEN 0   
	                                            END As HasCourse,
				                                CASE   
		                                          WHEN Count(A.ApprenticeshipId) > 0 THEN 1 
		                                          WHEN Count(A.ApprenticeshipId) = 0 THEN 0   
	                                            END As HasApprenticeship
                                        FROM [Provider] P
                                        JOIN [RecordStatus] RS
                                        ON P.RecordStatusId = RS.RecordStatusId
										JOIN [Address] Ar
										ON P.AddressId = Ar.AddressId
                                        LEFT JOIN [Course] C
                                        ON P.ProviderId = C.ProviderId
                                        LEFT JOIN [Apprenticeship] A
                                        ON P.ProviderId = A.ProviderId
                                        WHERE P.RecordStatusId = 2
                                        GROUP BY P.ProviderId,
                                                P.Ukprn,
                                                P.ProviderName,
                                                RS.RecordStatusId,
                                                RS.RecordStatusName,
		                                        P.RoATPFFlag,
		                                        P.RoATPProviderTypeId,
		                                        P.RoATPStartDate,
		                                        p.PassedOverallQAChecks,
		                                        P.MarketingInformation,
		                                        P.NationalApprenticeshipProvider,
		                                        P.TradingName,
		                                        P.UPIN,
												Ar.AddressLine1,
												Ar.AddressLine2,
												Ar.Town,
												Ar.County,
												Ar.Postcode,
												P.Email,
												P.Website,
												P.Telephone
                                            ";

                    try
                    {
                        //Open connection.
                        sqlConnection.Open();

                        stopWatch.Reset();
                        log.LogInformation($"Tribal Data: Start....");
                        stopWatch.Start();

                        using (SqlDataReader dataReader = command.ExecuteReader())
                        {
                            while (dataReader.Read())
                            {
                                // 1) Read provider data from Tribal
                                var item = ProviderSource.FromDataReader(dataReader);
                                totalTribalCount++;

                                try
                                {
                                    // 2) Check if in Whitelist
                                    if (!whiteListProviders.Any(x => x == item.UKPRN))
                                    {
                                        AddResultMessage(item.ProviderId, "SKIPPED-NotOnWhitelist", $"Provider {item.ProviderId} not on whitelist, ukprn {item.UKPRN}");
                                        continue;
                                    }

                                    totalAttemptedCount++;

                                    // 3) Check againts API ? If no match Add to Result Message, skip next
                                    var ukrlpProviderItem = ukrlpApiProviders.FirstOrDefault(p => p.UnitedKingdomProviderReferenceNumber.Trim() == item.UKPRN.ToString());
                                    if (ukrlpProviderItem == null)
                                    {
                                        AddResultMessage(item.ProviderId, "SKIPPED-NotInUkrlpApi", $"Provider {item.ProviderId} cannot be found in UKRLP Api, ukprn {item.UKPRN}");
                                        continue;
                                    }

                                    // 4) Build Cosmos collection record
                                    var providerToUpsert   = BuildNewCosmosProviderItem(ukrlpProviderItem, item);
                                    var cosmosProviderItem = await providerCollectionService.GetDocumentByUkprn(item.UKPRN);

                                    if (cosmosProviderItem != null)
                                    {
                                        Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, providerCollectionId);

                                        await _cosmosClient.UpsertDocumentAsync(collectionUri, UpdateCosmosProviderItem(cosmosProviderItem, providerToUpsert));

                                        totalUpdatedCount++;

                                        AddResultMessage(item.ProviderId, "PROCESSED-Updated", $"Provider {item.ProviderId} updated in Cosmos Collection, ukprn {item.UKPRN}");
                                    }
                                    else
                                    {
                                        await cosmosDbHelper.CreateDocumentAsync(_cosmosClient, providerCollectionId, providerToUpsert);

                                        totalInsertedCount++;

                                        AddResultMessage(item.ProviderId, "PROCESSED-Inserted", $"Provider {item.ProviderId} inserted in Cosmos Collection, ukprn {item.UKPRN}");
                                    }
                                }
                                catch (Exception ex)
                                {
                                    string errorMessage = $"Error processing Provider {item.ProviderId} with Ukprn {item.UKPRN}. {ex.Message}";
                                    AddResultMessage(item.ProviderId, "ERRORED", errorMessage);
                                    log.LogInformation(errorMessage);
                                }
                            }

                            dataReader.Close();
                        }

                        stopWatch.Stop();
                        log.LogInformation($"Tribal Data: Processing completed in {stopWatch.ElapsedMilliseconds / 1000}");

                        AddResultMessage(0, "SUMMARY", $"Total Time : {stopWatch.ElapsedMilliseconds / 1000} seconds, Tribal : {totalTribalCount}, URLP : {ukrlpApiProviders.Count}, Processed : {totalAttemptedCount}, Updated : {totalUpdatedCount}, Inserted : {totalInsertedCount}");
                    }
                    catch (Exception ex)
                    {
                        log.LogError(ex.Message);
                    }

                    var resultsObjBytes = GetResultAsByteArray(result);
                    await WriteResultsToBlobStorage(resultsObjBytes);
                }
            }

            void AddResultMessage(int providerId, string status, string message = "")
            {
                var validateResult = new ProviderResultMessage()
                {
                    ProviderId = providerId, Status = status, Message = message
                };

                result.Add(validateResult);
            }

            Provider BuildNewCosmosProviderItem(ProviderRecordStructure ukrlpData, ProviderSource tribalData)
            {
                // Build contacts
                List <Providercontact> providercontacts = new List <Providercontact>();
                var ukrlpDataContacts = ukrlpData.ProviderContact
                                        .Where(p => p.ContactType == "P")
                                        .OrderByDescending(c => c.LastUpdated);

                // Load UKRLP api contacts if available
                if (ukrlpDataContacts.Any())
                {
                    var ukrlpContact = ukrlpDataContacts.First();

                    // Build contact address
                    Contactaddress contactaddress = new Contactaddress()
                    {
                        SAON = new SAON()
                        {
                            Description = ukrlpContact.ContactAddress.SAON.Description
                        },
                        PAON = new PAON()
                        {
                            Description = ukrlpContact.ContactAddress.PAON.Description
                        },
                        StreetDescription             = ukrlpContact.ContactAddress.StreetDescription,
                        UniqueStreetReferenceNumber   = ukrlpContact.ContactAddress.UniqueStreetReferenceNumber,
                        UniquePropertyReferenceNumber = ukrlpContact.ContactAddress.UniquePropertyReferenceNumber,
                        Locality         = ukrlpContact.ContactAddress.Locality,
                        Items            = ukrlpContact.ContactAddress.Items,
                        ItemsElementName = ukrlpContact.ContactAddress.ItemsElementName?.Select(i => (int)i).ToArray(),
                        PostTown         = ukrlpContact.ContactAddress.PostTown,
                        PostCode         = ukrlpContact.ContactAddress.PostCode,
                    };

                    // Build contact personal details
                    Contactpersonaldetails contactpersonaldetails = new Contactpersonaldetails()
                    {
                        PersonNameTitle     = ukrlpContact.ContactPersonalDetails.PersonNameTitle,
                        PersonGivenName     = ukrlpContact.ContactPersonalDetails.PersonGivenName,
                        PersonFamilyName    = ukrlpContact.ContactPersonalDetails.PersonFamilyName,
                        PersonNameSuffix    = ukrlpContact.ContactPersonalDetails.PersonNameSuffix,
                        PersonRequestedName = ukrlpContact.ContactPersonalDetails.PersonRequestedName,
                    };

                    var providerContact = new Providercontact(contactaddress, contactpersonaldetails);
                    providerContact.ContactType           = ukrlpContact.ContactType;
                    providerContact.ContactRole           = ukrlpContact.ContactRole;
                    providerContact.ContactTelephone1     = ukrlpContact.ContactTelephone1;
                    providerContact.ContactTelephone2     = ukrlpContact.ContactTelephone2;
                    providerContact.ContactWebsiteAddress = ukrlpContact.ContactWebsiteAddress;
                    providerContact.ContactEmail          = ukrlpContact.ContactEmail;
                    providerContact.LastUpdated           = ukrlpContact.LastUpdated;

                    providercontacts.Add(providerContact);
                }
                else
                {
                    // Check if valid Tribal Address exists
                    if (tribalData.IsValidAddress)
                    {
                        // Build contact address
                        Contactaddress tribalContactaddress = new Contactaddress()
                        {
                            StreetDescription = tribalData.AddressLine1,
                            Locality          = tribalData.County,
                            PostTown          = tribalData.Town,
                            PostCode          = tribalData.PostCode,
                        };

                        var tribalContact = new Providercontact(tribalContactaddress, null);
                        tribalContact.ContactType           = "P";
                        tribalContact.ContactTelephone1     = tribalData.Telephone;
                        tribalContact.ContactWebsiteAddress = tribalData.Website;
                        tribalContact.ContactEmail          = tribalData.Email;
                        tribalContact.LastUpdated           = DateTime.UtcNow;

                        providercontacts.Add(tribalContact);
                    }
                    else
                    {
                        // Cannot find address in UKRLP api or tribal so raise alert
                        AddResultMessage(tribalData.ProviderId, "WARNING", $"Cannot find contact address details in Api or Tribal data for Provider {tribalData.ProviderId}, ukprn {tribalData.UKPRN}.");
                    }
                }

                // Build provider aliases
                List <Provideralias> provideraliases = new List <Provideralias>();

                foreach (ProviderAliasesStructure ukrlpProviderAlias in ukrlpData.ProviderAliases)
                {
                    provideraliases.Add(new Provideralias()
                    {
                        ProviderAlias = ukrlpProviderAlias.ProviderAlias,
                        LastUpdated   = ukrlpProviderAlias.LastUpdated,
                    });
                }

                // Build provider Verificationdetail
                List <Verificationdetail> providerVerificationdetails = new List <Verificationdetail>();

                foreach (VerificationDetailsStructure providerVerificationDetail in ukrlpData.VerificationDetails)
                {
                    providerVerificationdetails.Add(new Verificationdetail()
                    {
                        VerificationAuthority = providerVerificationDetail.VerificationAuthority,
                        VerificationID        = providerVerificationDetail.VerificationID,
                    });
                }

                Provider providerToUpsert = new Provider(providercontacts.ToArray(), provideraliases.ToArray(), providerVerificationdetails.ToArray());

                providerToUpsert.ProviderId = tribalData.ProviderId;
                providerToUpsert.id         = Guid.NewGuid();
                providerToUpsert.UnitedKingdomProviderReferenceNumber = tribalData.UKPRN.ToString();
                providerToUpsert.ProviderType                      = GetProviderType(tribalData.HasCourse, tribalData.HasApprenticeship);
                providerToUpsert.ProviderName                      = ukrlpData.ProviderName;
                providerToUpsert.ProviderStatus                    = ukrlpData.ProviderStatus;
                providerToUpsert.ProviderVerificationDate          = ukrlpData.ProviderVerificationDate;
                providerToUpsert.ProviderVerificationDateSpecified = ukrlpData.ProviderVerificationDateSpecified;
                providerToUpsert.ExpiryDateSpecified               = ukrlpData.ExpiryDateSpecified;
                providerToUpsert.ProviderAssociations              = ukrlpData.ProviderAssociations;
                providerToUpsert.Alias  = ukrlpData.ProviderAliases?.FirstOrDefault()?.ProviderAlias;
                providerToUpsert.Status = Status.Onboarded; // TODO : is this correct ?
                providerToUpsert.PassedOverallQAChecks          = tribalData.PassedOverallQAChecks;
                providerToUpsert.RoATPFFlag                     = tribalData.RoATPFFlag;
                providerToUpsert.RoATPProviderTypeId            = tribalData.RoATPProviderTypeId;
                providerToUpsert.RoATPStartDate                 = tribalData.RoATPStartDate;
                providerToUpsert.MarketingInformation           = tribalData.MarketingInformation;
                providerToUpsert.NationalApprenticeshipProvider = tribalData.NationalApprenticeshipProvider;
                providerToUpsert.TradingName                    = tribalData.TradingName;
                providerToUpsert.UPIN = tribalData.UPIN;


                providerToUpsert.LastUpdatedBy = ProviderAppName;
                providerToUpsert.DateUpdated   = DateTime.UtcNow;

                return(providerToUpsert);
            }

            Provider UpdateCosmosProviderItem(Provider cosmosProviderItem, Provider providerToUpsert)
            {
                cosmosProviderItem.Alias = providerToUpsert.Alias;
                cosmosProviderItem.ExpiryDateSpecified            = providerToUpsert.ExpiryDateSpecified;
                cosmosProviderItem.MarketingInformation           = providerToUpsert.MarketingInformation;
                cosmosProviderItem.NationalApprenticeshipProvider = providerToUpsert.NationalApprenticeshipProvider;
                cosmosProviderItem.PassedOverallQAChecks          = providerToUpsert.PassedOverallQAChecks;
                cosmosProviderItem.ProviderAliases                   = providerToUpsert.ProviderAliases;
                cosmosProviderItem.ProviderAssociations              = providerToUpsert.ProviderAssociations;
                cosmosProviderItem.ProviderContact                   = providerToUpsert.ProviderContact;
                cosmosProviderItem.ProviderId                        = providerToUpsert.ProviderId;
                cosmosProviderItem.ProviderName                      = providerToUpsert.ProviderName;
                cosmosProviderItem.ProviderStatus                    = providerToUpsert.ProviderStatus;
                cosmosProviderItem.ProviderType                      = providerToUpsert.ProviderType;
                cosmosProviderItem.ProviderVerificationDate          = providerToUpsert.ProviderVerificationDate;
                cosmosProviderItem.ProviderVerificationDateSpecified = providerToUpsert.ProviderVerificationDateSpecified;
                cosmosProviderItem.RoATPFFlag                        = providerToUpsert.RoATPFFlag;
                cosmosProviderItem.RoATPProviderTypeId               = providerToUpsert.RoATPProviderTypeId;
                cosmosProviderItem.RoATPStartDate                    = providerToUpsert.RoATPStartDate;
                cosmosProviderItem.Status      = providerToUpsert.Status;
                cosmosProviderItem.TradingName = providerToUpsert.TradingName;
                cosmosProviderItem.UnitedKingdomProviderReferenceNumber = providerToUpsert.UnitedKingdomProviderReferenceNumber;
                cosmosProviderItem.UPIN = providerToUpsert.UPIN;
                cosmosProviderItem.VerificationDetails = providerToUpsert.VerificationDetails;

                cosmosProviderItem.LastUpdatedBy = providerToUpsert.LastUpdatedBy;
                cosmosProviderItem.DateUpdated   = providerToUpsert.DateUpdated;

                return(cosmosProviderItem);
            }

            async Task <IList <int> > GetProviderWhiteList()
            {
                var list      = new List <int>();
                var whiteList = await blobhelper.ReadFileAsync(blobContainer, WHITE_LIST_FILE);

                if (!string.IsNullOrEmpty(whiteList))
                {
                    var lines = whiteList.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
                    foreach (string line in lines)
                    {
                        if (int.TryParse(line, out int id))
                        {
                            list.Add(id);
                        }
                    }
                }
                return(list);
            }

            byte[] GetResultAsByteArray(IList <ProviderResultMessage> message)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <ProviderResultMessage>(message);
                        }
                    return(memoryStream.ToArray());
                }
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, venueExportFileName, data);
            }
        }
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory)
        {
            var whitelistFileName   = "ProviderWhiteList.txt";
            var blobContainer       = configuration["BlobStorageSettings:Container"];
            var databaseId          = configuration["CosmosDbSettings:DatabaseId"];
            var coursesCollectionId = "courses";
            var documentClient      = cosmosDbHelper.GetClient();

            var coursesCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);

            var logger = loggerFactory.CreateLogger(typeof(ArchiveCourses));

            var whitelist = await GetProviderWhiteList();

            foreach (var ukprn in whitelist)
            {
                var updated = 0;

                string continuation = null;
                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    foreach (var doc in queryResponse)
                    {
                        foreach (var run in doc.CourseRuns)
                        {
                            run.RecordStatus = CourseDirectory.Models.Enums.RecordStatus.Archived;
                        }

                        var documentLink = UriFactory.CreateDocumentUri(databaseId, coursesCollectionId, doc.id.ToString());

                        await documentClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions()
                        {
                            PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                        });

                        updated++;
                    }

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);

                logger.LogInformation($"Archived {updated} courses for {ukprn}");
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }

                return(results);
            }
        }
Пример #11
0
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IApprenticeshipCollectionService apprenticeshipCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper,
            [Inject] IUkrlpApiService ukrlpApiService
            )
        {
            const string apprenticeshipAppName = "Validate.Apprenticeship";

            var apprenticeshipValidationFileName = $"{apprenticeshipAppName}-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var blobContainer = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var validator     = new ApprenticeshipValidator();

            List <ApprenticeshipValidationResult> validationEResult = new List <ApprenticeshipValidationResult>();

            var stopWatch = new Stopwatch();

            stopWatch.Start();

            var resultData = await apprenticeshipCollectionService.GetAllApprenticeshipsAsync();

            foreach (var item in resultData)
            {
                //item.ContactEmail = "testing";
                //item.Url = "testing";
                //item.ContactWebsite = "testing";
                //item.ContactTelephone = "101101abc";
                //item.ApprenticeshipTitle = item.ApprenticeshipTitle + " @";

                var validationResult = validator.Validate(item);

                if (!validationResult.IsValid)
                {
                    foreach (var error in validationResult.Errors)
                    {
                        validationEResult.Add(new ApprenticeshipValidationResult()
                        {
                            ApprenticeshipId = item.ApprenticeshipId,
                            ProviderUkprn    = item.ProviderUKPRN,
                            FieldName        = error.PropertyName,
                            ErrorMessage     = error.ErrorMessage
                        });
                    }
                }
            }

            var resultsObjBytes = GetResultAsByteArray(validationEResult);

            await WriteResultsToBlobStorage(resultsObjBytes);

            stopWatch.Stop();

            byte[] GetResultAsByteArray(IList <ApprenticeshipValidationResult> message)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <ApprenticeshipValidationResult>(message);
                        }
                    return(memoryStream.ToArray());
                }
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, apprenticeshipValidationFileName, data);
            }
        }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger logger,
            [Inject] IConfigurationRoot configuration,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            logger.LogDebug("VenueRestorer: Starting...");
            var connectionString   = configuration.GetConnectionString("TribalRestore");
            var venuesCollectionId = configuration["CosmosDbCollectionSettings:VenuesCollectionId"];
            var blobContainer      = configuration["BlobStorageSettings:Container"];
            var container          = configuration["BlobStorageSettings:Container"];
            var whiteListProviders = await GetProviderWhiteList();

            logger.LogDebug($"VenueRestorer: {whiteListProviders.Count} white-listed providers to process");
            var          coursesCollectionId        = "courses";
            var          apprenticeshipCollectionId = "apprenticeship";
            var          databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var          documentClient              = cosmosDbHelper.GetClient();
            var          venueCollectionUri          = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
            var          venueCollection_OldUri      = UriFactory.CreateDocumentCollectionUri(databaseId, "venues_old");
            var          coursesCollectionUri        = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var          apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var          logFileName                 = $"VenueRestorer--{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var          allVenues       = GetAllOldVenues();
            var          result          = new List <ResultMessage>();
            const string WHITE_LIST_FILE = "ProviderWhiteList.txt";
            var          ukprnCache      = new List <int>();
            var          updatedBy       = "VenueRestorer";

            //grand totals
            var invalidCourseRunReferences      = 0;
            var totalInvalidCourseRunReferences = 0;
            var uniqueInvalidVenues             = new HashSet <string>();
            var replacedInvalidVenues           = new HashSet <Venue>();
            var references = new List <VenueRestorerReference>();
            var rereferencedApprenticeshipLocations = 0;

            //provider scoped totals
            var totalInvalidApprenticeshipLocationReferences = 0;
            var invalidApprenticeshipLocationReferences      = 0;

            int processedProviderCount = 0;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        foreach (var ukprn in whiteListProviders)
                        {
                            //reference for old venue so that courseruns & apprenticeship locations can be
                            //re-referenced
                            var venuesReplacedForProvider = new List <Tuple <Venue, Venue, Guid> >();
                            try
                            {
                                //reset counters
                                invalidCourseRunReferences = 0;
                                invalidApprenticeshipLocationReferences = 0;

                                //fetch data for ukprn
                                var allCoursesForProvider = await GetCourses(ukprn);

                                var allApprenticeshipsForProvider = await GetApprenticeships(ukprn);

                                var venues = await GetVenues(ukprn);

                                var old_venues = GetOldVenues(ukprn);

                                //courses
                                foreach (var course in allCoursesForProvider)
                                {
                                    //course runs
                                    foreach (var courserun in course.CourseRuns)
                                    {
                                        //only courses that references a venue (classroom based or both)
                                        if (courserun.VenueId != null && courserun.VenueId != Guid.Empty)
                                        {
                                            //current venue & old venue (pre migration)
                                            var invalidReferencedVenue = await GetVenueById(courserun.VenueId?.ToString());

                                            var restoredVenue = old_venues.FirstOrDefault(x => new Guid(x.ID) == courserun.VenueId);

                                            //if current venues provider is different to course then attempt to replace it with
                                            //old venue from cosmos backup.
                                            if (invalidReferencedVenue != null && invalidReferencedVenue.UKPRN != course.ProviderUKPRN)
                                            {
                                                //replace existing venue with old venue if a match is found
                                                if (restoredVenue != null && restoredVenue.UKPRN == course.ProviderUKPRN)
                                                {
                                                    await ReplaceVenue(invalidReferencedVenue.ID, restoredVenue, updatedBy);

                                                    //the venue that was referenced needs to be inserted again but with a new id.
                                                    var newId = Guid.NewGuid();
                                                    await ReplaceVenue(newId.ToString(), invalidReferencedVenue, updatedBy);

                                                    //reload venues as we have just replaced a venue
                                                    venues = await GetVenues(ukprn);

                                                    replacedInvalidVenues.Add(invalidReferencedVenue);

                                                    //store old venue so that apprenticeship locations can be re-referenced
                                                    venuesReplacedForProvider.Add(Tuple.Create(restoredVenue, invalidReferencedVenue, newId));

                                                    //log changes
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN              = course.ProviderUKPRN,
                                                        VenueId            = courserun.VenueId.ToString(),
                                                        CurrentVenueUKPRN  = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1    = invalidReferencedVenue.Address1,
                                                        CurrentPostcode    = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName   = invalidReferencedVenue.VenueName,
                                                        RestoredAddress1   = restoredVenue.Address1,
                                                        RestoredVenueName  = restoredVenue.VenueName,
                                                        RestoredPostcode   = restoredVenue.PostCode,
                                                        RestoredVenueUKPRN = restoredVenue.UKPRN,
                                                        UKPRNMatched       = (course.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message            = "Replaced Venue",
                                                        Type        = "Course",
                                                        CourseId    = course.id,
                                                        CourseRunId = courserun.id,
                                                    });
                                                }
                                                else
                                                {
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN             = course.ProviderUKPRN,
                                                        VenueId           = courserun.VenueId.ToString(),
                                                        CurrentVenueUKPRN = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1   = invalidReferencedVenue.Address1,
                                                        CurrentPostcode   = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName  = invalidReferencedVenue.VenueName,
                                                        UKPRNMatched      = (course.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message           = "Unable to replace Venue, as old venue was not found in backup",
                                                        Type        = "Course",
                                                        CourseId    = course.id,
                                                        CourseRunId = courserun.id,
                                                    });
                                                }

                                                //invalid references
                                                uniqueInvalidVenues.Add(invalidReferencedVenue.ID);
                                                invalidCourseRunReferences++;
                                            }
                                        }
                                    }

                                    //total for all providers
                                    totalInvalidCourseRunReferences += invalidCourseRunReferences;
                                }

                                //apprenticeships
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    //apprenticeship locations
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        //only apprenticeshiplocations that references a venue (classroom based or both)
                                        if (location.VenueId.HasValue && location.LocationGuidId.HasValue && location.LocationGuidId != Guid.Empty)
                                        {
                                            var invalidReferencedVenue = await GetVenueById(location.LocationGuidId?.ToString());

                                            if (invalidReferencedVenue != null && invalidReferencedVenue.UKPRN != apprenticeship.ProviderUKPRN)
                                            {
                                                var restoredVenue = old_venues.FirstOrDefault(x => new Guid(x.ID) == location.LocationGuidId);

                                                //replace existing venue with old venue if a match is found
                                                if (restoredVenue != null && restoredVenue.UKPRN == apprenticeship.ProviderUKPRN)
                                                {
                                                    logger.LogDebug($"VenueRestorer: Invalid Apprenticeship location, apprenticeship should reference {restoredVenue.VenueName}");

                                                    //old venue from json backup is the correct venue that should be referenced
                                                    //swap invalid venue, with restoredVenue
                                                    await ReplaceVenue(invalidReferencedVenue.ID, restoredVenue, updatedBy);

                                                    //the venue that was referenced needs to be inserted again but with a new id.
                                                    var newId = Guid.NewGuid();
                                                    await ReplaceVenue(newId.ToString(), invalidReferencedVenue, updatedBy);

                                                    //store old venue so that apprenticeship locations can be re-referenced
                                                    venuesReplacedForProvider.Add(Tuple.Create(restoredVenue, invalidReferencedVenue, newId));

                                                    //reload venues as we have just replaced a venue
                                                    venues = await GetVenues(ukprn);

                                                    //keep a track of the incorrect venue, these will be inserted with a new id.
                                                    replacedInvalidVenues.Add(invalidReferencedVenue);

                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN = apprenticeship.ProviderUKPRN,
                                                        ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                        VenueId            = location.LocationGuidId.ToString(),
                                                        CurrentVenueUKPRN  = invalidReferencedVenue.UKPRN,
                                                        CurrentAddress1    = invalidReferencedVenue.Address1,
                                                        CurrentPostcode    = invalidReferencedVenue.PostCode,
                                                        CurrentVenueName   = invalidReferencedVenue.VenueName,
                                                        RestoredVenueUKPRN = restoredVenue.UKPRN,
                                                        RestoredAddress1   = restoredVenue?.Address1,
                                                        RestoredPostcode   = restoredVenue?.PostCode,
                                                        RestoredVenueName  = restoredVenue.VenueName,
                                                        UKPRNMatched       = (apprenticeship.ProviderUKPRN == invalidReferencedVenue.UKPRN),
                                                        Message            = "Replaced Venue",
                                                        Type             = "Apprenticeship",
                                                        ApprenticeshipId = apprenticeship.id
                                                    });
                                                }
                                                else
                                                {
                                                    references.Add(new VenueRestorerReference()
                                                    {
                                                        UKPRN = apprenticeship.ProviderUKPRN,
                                                        ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                        UKPRNMatched      = false,
                                                        CurrentVenueUKPRN = -1,
                                                        VenueId           = location.LocationGuidId.ToString(),
                                                        Type             = "Apprenticeship",
                                                        Message          = "Unable to replace Venue, as old venue was not found in backup",
                                                        ApprenticeshipId = apprenticeship.id
                                                    });
                                                }
                                            }
                                        }
                                    }
                                    totalInvalidApprenticeshipLocationReferences += invalidApprenticeshipLocationReferences;
                                }

                                //rereference apprenticeship locations
                                //if there is a venue that has been replaced but is referenced by an apprenticeshiplocation then the apprenticeship
                                //record needs to be updated to point the the new venue record to save data loss.
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    var updated = false;
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        var replacedVenue = venuesReplacedForProvider.FirstOrDefault(x => new Guid(x.Item2.ID) == location.LocationGuidId);
                                        if (replacedVenue != null)
                                        {
                                            updated = true;
                                            location.LocationGuidId = replacedVenue.Item3;
                                        }
                                    }

                                    if (updated)
                                    {
                                        var documentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, apprenticeship.id.ToString());
                                        await documentClient.ReplaceDocumentAsync(documentLink, apprenticeship, new RequestOptions()
                                        {
                                            PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                                        });
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                logger.LogError($"VenueRestorer: error for ukprn {ukprn}: {e.Message}", e);
                            }

                            processedProviderCount++;
                            if (processedProviderCount % 100 == 0)
                            {
                                logger.LogInformation($"VenueRestorer: {processedProviderCount}/{whiteListProviders.Count} providers processed.");
                            }
                            logger.LogDebug(
                                $"VenueRestorer: completed for UKPRN {ukprn}, {processedProviderCount}/{whiteListProviders.Count}. " +
                                $"{invalidCourseRunReferences} invalid venue references, " +
                                $"{invalidApprenticeshipLocationReferences} invalid apprenticeship references, " +
                                $"{rereferencedApprenticeshipLocations} apprenticeship locations were re-referenced.");
                        }

                        //write csv file
                        logCsvWriter.WriteHeader(typeof(VenueRestorerReference));
                        logCsvWriter.NextRecord();
                        foreach (var reference in references)
                        {
                            logCsvWriter.WriteRecord(reference);
                            logCsvWriter.NextRecord();
                        }

                        // Upload log CSV to blob storage
                        logStreamWriter.Flush();
                        logStream.Seek(0L, SeekOrigin.Begin);
                        var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                        await blob.UploadFromStreamAsync(logStream);

                        logger.LogInformation($"VenueRestorer: log uploaded as {logFileName}");
                    }

            logger.LogInformation(
                $"VenueRestorer: completed. " +
                $"{totalInvalidCourseRunReferences} invalid CourseRun references in total, " +
                $"{totalInvalidApprenticeshipLocationReferences} Apprenticeship location invalid references in total, " +
                $"{replacedInvalidVenues.Count()} Venues have been reverted back to old venues, " +
                $"{uniqueInvalidVenues.Count()} Venues were invalid");

            async Task <List <Course> > GetCourses(int ukprn)
            {
                var courses = new List <Course>();
                //Get all courses
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    courses.AddRange(queryResponse.ToList());
                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(courses);
            }

            async Task <List <Venue> > GetVenues(int ukprn)
            {
                var    venues       = new List <Venue>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Venue>(venueCollectionUri, feedOptions)
                                        .Where(p => p.UKPRN == ukprn)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Venue>();

                    venues.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(venues);
            }

            List <Venue> GetOldVenues(int ukprn)
            {
                return(allVenues.Where(x => x.UKPRN == ukprn).ToList());
            }

            async Task <Venue> GetVenueById(string id)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);

                var query = documentClient
                            .CreateDocumentQuery <Venue>(collectionLink, new FeedOptions()
                {
                    EnableCrossPartitionQuery = true
                })
                            .Where(d => d.ID == id)
                            .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn)
            {
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.RecordStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        apprenticeships.AddRange(queryResponse);
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(WHITE_LIST_FILE);
                var ms   = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }

            async Task ReplaceVenue(string id, Venue matchedVenue, string updatedby)
            {
                Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
                var editedVenue   = new Dfc.CourseDirectory.Models.Models.Venues.Venue()
                {
                    ID               = id,
                    UKPRN            = matchedVenue.UKPRN,
                    VenueName        = matchedVenue.VenueName,
                    Address1         = matchedVenue.Address1,
                    Address2         = matchedVenue.Address2,
                    Town             = matchedVenue.Town,
                    County           = matchedVenue.County,
                    PostCode         = matchedVenue.PostCode,
                    Latitude         = matchedVenue.Latitude,
                    Longitude        = matchedVenue.Longitude,
                    Status           = matchedVenue.Status,
                    UpdatedBy        = updatedBy,
                    DateUpdated      = matchedVenue.DateUpdated,
                    VenueID          = matchedVenue.VenueID,
                    ProviderID       = matchedVenue.ProviderID,
                    ProvVenueID      = matchedVenue.ProvVenueID,
                    Email            = matchedVenue.Email,
                    Website          = matchedVenue.Website,
                    Telephone        = matchedVenue.Telephone,
                    CreatedBy        = matchedVenue.CreatedBy,
                    CreatedDate      = DateTime.Now,
                    LocationId       = matchedVenue.LocationId,
                    TribalLocationId = matchedVenue.TribalLocationId
                };
                await documentClient.UpsertDocumentAsync(collectionUri, editedVenue);
            }

            IList <Venue> GetAllOldVenues()
            {
                var list = new List <Venue>();
                var lookupFileResourceName = "Dfc.ProviderPortal.TribalExporter.2020-01-24_0325-venues-backup.json";

                using (var stream = typeof(VenueReferenceChecker).Assembly.GetManifestResourceStream(lookupFileResourceName))
                    using (StreamReader file = new StreamReader(stream))
                    {
                        using (JsonTextReader reader = new JsonTextReader(file))
                        {
                            while (reader.Read())
                            {
                                JArray o2 = (JArray)JToken.ReadFrom(reader);

                                foreach (var item in o2)
                                {
                                    Venue v = item.ToObject <Venue>();
                                    list.Add(v);
                                }
                            }
                        }
                    }
                return(list);
            }
        }
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IBlobStorageHelper blobhelper,
            [Inject] ILoggerFactory loggerFactory,
            [Inject] IConfiguration configuration,
            [Inject] IFaocSearchServiceWrapper searchService
            )
        {
            var logger            = loggerFactory.CreateLogger(typeof(UpdateFaocSearch));
            var blobContainername = configuration["BlobStorageSettings:Container"];
            var blobContainer     = blobhelper.GetBlobContainer(blobContainername);
            var faocFileName      = "Courses.csv";
            var faocEntries       = await GetFaocList();

            var validator        = new FaocValidator();
            var validationErrors = new List <FaocValidationError>();
            var validFaocEntries = new List <FaocEntry>();


            foreach (var onlineCourse in faocEntries)
            {
                var isValid = validator.Validate(onlineCourse);
                if (!isValid.IsValid)
                {
                    validationErrors.Add(new FaocValidationError {
                        ID = onlineCourse.id, Error = isValid.Errors.Select(x => $"{x.ErrorMessage}{Environment.NewLine}").ToList()
                    });
                    continue;
                }
                else
                {
                    validFaocEntries.Add(onlineCourse);
                }
            }

            if (validFaocEntries.Count() > 0)
            {
                //upload coureses
                var results = await searchService.UploadFaocBatch(validFaocEntries);

                //delete old courses
                await searchService.DeleteStaleDocuments(validFaocEntries);

                logger.LogWarning($"{ validationErrors.Count } Courses Failed To Import");

                if (validationErrors.Count() == 0)
                {
                    await blobhelper.MoveFile(blobContainer, faocFileName, $"ProcessedCourses/courses-{DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")}.csv");
                }
                else
                {
                    await blobhelper.MoveFile(blobContainer, faocFileName, $"Errors/courses-{DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")}.csv");
                }
            }

            var totalImported = faocEntries.Count() - validationErrors.Count();

            logger.LogWarning($"Finished Importing {totalImported} of {faocEntries.Count()} online courses.");

            async Task <IList <FaocEntry> > GetFaocList()
            {
                var blob = blobhelper.GetBlobContainer(blobContainername).GetBlockBlobReference(faocFileName);
                var ms   = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);
                using (var reader = new StreamReader(ms))
                {
                    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
                    {
                        //incase header is lower case
                        csv.Configuration.PrepareHeaderForMatch = (string header, int index) => header.ToLower();
                        var records = csv.GetRecords <FaocEntry>();
                        return(records.ToList());
                    }
                }
            }
        }
Пример #14
0
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory,
            [Inject] IBlobStorageHelper blobhelper)
        {
            var whitelistFileName      = "ProviderWhiteList.txt";
            var pendingCoursesFileName = $"CoursesWithNoCostOrCostDecription-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var blobContainer          = configuration["BlobStorageSettings:Container"];
            var outputContainer        = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var databaseId             = configuration["CosmosDbSettings:DatabaseId"];
            var coursesCollectionId    = "courses";
            var documentClient         = cosmosDbHelper.GetClient();
            var result = new List <MigrationPendingCourseRunResult>();

            var    coursesCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var    logger       = loggerFactory.CreateLogger(typeof(ArchiveCourses));
            string continuation = null;
            int    count        = 0;

            var whitelist = await GetProviderWhiteList();

            foreach (var ukprn in whitelist)
            {
                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation       = continuation,
                        EnableCrossPartitionQuery = true,
                        PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    //find courses that do not have a cost desciption or cost.
                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.CourseRuns.Any(x => x.Cost == null && (x.CostDescription == "" || x.CostDescription == null)) &&
                                               (p.CourseStatus == RecordStatus.Live || p.CourseStatus == RecordStatus.MigrationPendingAndLive))
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    //update course run to be migration pending.
                    foreach (var doc in queryResponse)
                    {
                        var currentStatus = doc.CourseStatus;

                        //Course instance id that caused the course to go to migration pending.
                        var message = string.Join("\n", doc.CourseRuns.Where(x => x.Cost == null && (x.CostDescription == "" || x.CostDescription == null))
                                                  .ToList()
                                                  .Select(x => $"Course Instance {x.CourseInstanceId} Invalid"));

                        doc.CourseRuns.Where(x => x.Cost == null && (x.CostDescription == "" || x.CostDescription == null))
                        .ToList()
                        .ForEach(x => x.RecordStatus = CourseDirectory.Models.Enums.RecordStatus.MigrationPending);


                        var documentLink = UriFactory.CreateDocumentUri(databaseId, coursesCollectionId, doc.id.ToString());
                        await documentClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions()
                        {
                            PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                        });

                        result.Add(new MigrationPendingCourseRunResult {
                            CourseId = doc.CourseId, StatusId = doc.CourseStatus, Message = message
                        });

                        count++;
                    }
                    continuation = queryResponse.ResponseContinuation;
                } while (continuation != null);
            }

            //Log Results to blob storage
            var resultsObjBytes = GetResultAsByteArray(result);

            await WriteResultsToBlobStorage(resultsObjBytes);


            logger.LogInformation($"{count} courses Have been made pending");

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }

                return(results);
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(outputContainer, pendingCoursesFileName, data);
            }

            byte[] GetResultAsByteArray(IList <MigrationPendingCourseRunResult> ob)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <MigrationPendingCourseRunResult>(ob);
                            csvWriter.Flush();
                        }

                    return(memoryStream.ToArray());
                }
            }
        }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IFEChoicesDataCollectionService feChoicesDataCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            const string AppName = "FEChoicesData.Migrator";

            var stopWatch = new Stopwatch();

            var databaseId                    = configuration["CosmosDbSettings:DatabaseId"];
            var feChoicesCollectionId         = configuration["CosmosDbCollectionSettings:FEChoicesDataCollectionId"];
            var connectionString              = configuration.GetConnectionString("TribalRestore");
            var fechoicesDataMigrationLogFile = $"FEChoicesDataMigrator-{DateTime.Now.ToString("dd-MM-yy HHmm")}";

            List <Guid> feDataRecordsToDeleteByGuid  = new List <Guid>();
            List <int>  feDataRecordsToDeleteByUkprn = new List <int>();

            var blobContainer = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);

            var _cosmosClient = cosmosDbHelper.GetClient();

            // Get all changed data from UKRLP API
            stopWatch.Reset();
            log.LogInformation($"FEChoicesData Migrator: Start synching data..");

            var result = new List <FeChoicesDataResultMessage>();

            using (var sqlConnection = new SqlConnection(connectionString))
            {
                using (var command = sqlConnection.CreateCommand())
                {
                    // Get non duplicate UKPRN data only
                    command.CommandType = CommandType.Text;
                    command.CommandText = @"SELECT	P.Ukprn, 
                                                        P.RecordStatusId,
                                                        D.UPIN, 
                                                        D.LearnerSatisfaction, 
                                                        D.LearnerDestination, 
                                                        D.EmployerSatisfaction,
                                                        D.CreatedDateTimeUtc,
                                                        Count(p.UKprn)
                                                 FROM [Provider] P
                                                 JOIN [FEChoices] D
                                                 ON P.UPIN = D.UPIN
                                                 WHERE p.RecordStatusId = 2
                                                 GROUP BY 
		                                                P.Ukprn, 
                                                        P.RecordStatusId,
                                                        D.UPIN, 
                                                        D.LearnerSatisfaction, 
                                                        D.LearnerDestination, 
                                                        D.EmployerSatisfaction,
                                                        D.CreatedDateTimeUtc
                                                HAVING Count(P.Ukprn) = 1
                                                ORDER BY D.CreatedDateTimeUtc DESC, p.Ukprn
                                            ";

                    try
                    {
                        //Open connection.
                        sqlConnection.Open();

                        stopWatch.Reset();
                        log.LogInformation($"Tribal Data: Start....");
                        stopWatch.Start();

                        List <FEChoicesSourceData> sourceData = new List <FEChoicesSourceData>();
                        using (SqlDataReader dataReader = command.ExecuteReader())
                        {
                            while (dataReader.Read())
                            {
                                // Read FE data from Tribal
                                var item = FEChoicesSourceData.FromDataReader(dataReader);
                                sourceData.Add(item);
                            }

                            dataReader.Close();
                        }

                        var destinationData = await feChoicesDataCollectionService.GetAllDocument();

                        var uniqueSourceUkprns = sourceData.Select(s => s.UKPRN).Distinct().ToList();

                        foreach (var ukprn in uniqueSourceUkprns)
                        {
                            // filter out duplicate form source
                            if (sourceData.Count(s => s.UKPRN == ukprn) > 1)
                            {
                                // mark for removal from destination if exists
                                feDataRecordsToDeleteByUkprn.Add(ukprn);
                                continue;
                            }

                            // pick the first as there might still be duplicates
                            var sourceItem = sourceData.First(s => s.UKPRN == ukprn);

                            try
                            {
                                // Check if item exists in both (could be duplicate in destination)
                                var itemsToUpdate = destinationData.Where(s => s.UKPRN == sourceItem.UKPRN);

                                // Update
                                if (itemsToUpdate != null && itemsToUpdate.Any())
                                {
                                    var itemToUpdate = itemsToUpdate.First();

                                    itemToUpdate.EmployerSatisfaction = sourceItem.EmployerSatisfaction;
                                    itemToUpdate.LearnerSatisfaction  = sourceItem.LearnerSatisfaction;
                                    itemToUpdate.CreatedDateTimeUtc   = sourceItem.CreatedDateTimeUtc;
                                    itemToUpdate.LastUpdatedBy        = AppName;
                                    itemToUpdate.LastUpdatedOn        = DateTime.UtcNow;

                                    Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, feChoicesCollectionId);
                                    await _cosmosClient.UpsertDocumentAsync(collectionUri, itemToUpdate);

                                    feDataRecordsToDeleteByGuid.AddRange(itemsToUpdate.Where(i => i.id != itemToUpdate.id).Select(i => i.id).ToList());

                                    AddResultMessage(sourceItem.UKPRN, "PROCESSED-Updated", $"Provider {sourceItem.UKPRN} updated in Cosmos Collection");
                                }
                                // Insert new entry
                                else
                                {
                                    var newRecord = new FEChoicesData()
                                    {
                                        id    = Guid.NewGuid(),
                                        UKPRN = sourceItem.UKPRN,
                                        LearnerSatisfaction  = sourceItem.LearnerSatisfaction,
                                        EmployerSatisfaction = sourceItem.EmployerSatisfaction,
                                        CreatedDateTimeUtc   = sourceItem.CreatedDateTimeUtc,
                                        CreatedBy            = AppName,
                                        CreatedOn            = DateTime.UtcNow,
                                        LastUpdatedBy        = AppName,
                                        LastUpdatedOn        = DateTime.UtcNow,
                                    };

                                    await cosmosDbHelper.CreateDocumentAsync(_cosmosClient, feChoicesCollectionId, newRecord);

                                    AddResultMessage(sourceItem.UKPRN, "PROCESSED-Created", $"Provider {sourceItem.UKPRN} updated in Cosmos Collection");
                                }
                            }
                            catch (Exception ex)
                            {
                                AddResultMessage(sourceItem.UKPRN, "ERRORED", $"Error while inserting/updating for provider {ex.Message}");
                                log.LogError($"Tribal Data: Error processing data.", ex);
                            }
                        }

                        // Remove data that is not in source
                        var howManyToDelete = destinationData.Where(d => !sourceData.Select(s => s.UKPRN).Contains(d.UKPRN));
                        foreach (var existingItem in howManyToDelete)
                        {
                            try
                            {
                                Uri docUri       = UriFactory.CreateDocumentUri(databaseId, feChoicesCollectionId, existingItem.id.ToString());
                                var deleteResult = await _cosmosClient.DeleteDocumentAsync(docUri, new RequestOptions()
                                {
                                    PartitionKey = new PartitionKey(existingItem.UKPRN)
                                });

                                AddResultMessage(existingItem.UKPRN, "DELETE", $"Record {existingItem.id} with UKPRN {existingItem.UKPRN} deleted as not in source.");
                            }
                            catch (Exception)
                            {
                                AddResultMessage(existingItem.UKPRN, "ERROR", $"Error deleting in destination record {existingItem.id} with UKPRN {existingItem.UKPRN}.");
                            }
                        }

                        // Remove data that is duplicate in destination using Id
                        var duplicatesToDeleteByGuid = destinationData.Where(d => feDataRecordsToDeleteByGuid.Contains(d.id));
                        foreach (var existingItem in duplicatesToDeleteByGuid)
                        {
                            try
                            {
                                Uri docUri       = UriFactory.CreateDocumentUri(databaseId, feChoicesCollectionId, existingItem.id.ToString());
                                var deleteResult = await _cosmosClient.DeleteDocumentAsync(docUri, new RequestOptions()
                                {
                                    PartitionKey = new PartitionKey(existingItem.UKPRN)
                                });

                                AddResultMessage(existingItem.UKPRN, "DELETE", $"Record {existingItem.id} with UKPRN {existingItem.UKPRN} deleted as duplicate in Cosmos.");
                            }
                            catch (Exception)
                            {
                                AddResultMessage(existingItem.UKPRN, "ERROR", $"Error deleting in destination record {existingItem.id} with UKPRN {existingItem.UKPRN}.");
                            }
                        }

                        // Remove data that is duplicate in source so needs to be removed from destination using UKPRN
                        var duplicatesToDeleteByUkprn = destinationData.Where(d => feDataRecordsToDeleteByUkprn.Contains(d.UKPRN));
                        foreach (var existingItem in duplicatesToDeleteByUkprn)
                        {
                            try
                            {
                                Uri docUri       = UriFactory.CreateDocumentUri(databaseId, feChoicesCollectionId, existingItem.id.ToString());
                                var deleteResult = await _cosmosClient.DeleteDocumentAsync(docUri, new RequestOptions()
                                {
                                    PartitionKey = new PartitionKey(existingItem.UKPRN)
                                });

                                AddResultMessage(existingItem.UKPRN, "DELETE", $"Record {existingItem.id} with UKPRN {existingItem.UKPRN} deleted as duplicate in Cosmos.");
                            }
                            catch (Exception)
                            {
                                AddResultMessage(existingItem.UKPRN, "ERROR", $"Error deleting in destination record {existingItem.id} with UKPRN {existingItem.UKPRN}.");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        AddResultMessage(-1, "ERRORED-Unknown", $"{ex.Message}");
                        log.LogError($"Tribal Data: Error processing data.", ex);
                    }

                    stopWatch.Stop();
                    log.LogInformation($"Tribal Data: Processing completed in {stopWatch.ElapsedMilliseconds / 1000}");

                    var resultsObjBytes = GetResultAsByteArray(result);
                    await WriteResultsToBlobStorage(resultsObjBytes);
                }
            }

            void AddResultMessage(int providerId, string status, string message = "")
            {
                var validateResult = new FeChoicesDataResultMessage()
                {
                    ProviderId = providerId, Status = status, Message = message
                };

                result.Add(validateResult);
            }

            byte[] GetResultAsByteArray(IList <FeChoicesDataResultMessage> message)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <FeChoicesDataResultMessage>(message);
                        }
                    return(memoryStream.ToArray());
                }
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, fechoicesDataMigrationLogFile, data);
            }
        }
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory,
            [Inject] IUkrlpApiService ukrlpApiService)
        {
            var blobContainer         = configuration["BlobStorageSettings:Container"];
            var databaseId            = configuration["CosmosDbSettings:DatabaseId"];
            var coursesCollectionId   = "courses";
            var providerCollectionId  = "ukrlp";
            var documentClient        = cosmosDbHelper.GetClient();
            var coursesCollectionUri  = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var providerCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, providerCollectionId);
            var logger      = loggerFactory.CreateLogger(typeof(ArchiveCourses));
            var count       = 0;
            var logFileName = $"CoursesWithMissingLarsCodes";

            string continuation = null;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        // Log CSV headers
                        logCsvWriter.WriteField("UKPRN");
                        logCsvWriter.WriteField("ProviderName");
                        logCsvWriter.WriteField("CourseId");
                        logCsvWriter.WriteField("Provider course ID");
                        logCsvWriter.WriteField("Course name");
                        logCsvWriter.WriteField("Start date");
                        logCsvWriter.WriteField("Cost");
                        logCsvWriter.WriteField("Cost description");
                        logCsvWriter.WriteField("Delivery mode");
                        logCsvWriter.WriteField("Attendance mode");
                        logCsvWriter.NextRecord();

                        do
                        {
                            try
                            {
                                var feedOptions = new FeedOptions()
                                {
                                    RequestContinuation       = continuation,
                                    EnableCrossPartitionQuery = true
                                };

                                var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                                    .Where(p => (p.LearnAimRef == null || p.QualificationCourseTitle == null) && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                                    .AsDocumentQuery()
                                                    .ExecuteNextAsync <Course>();

                                foreach (var doc in queryResponse)
                                {
                                    var providers = ukrlpApiService.GetAllProviders(new List <string> {
                                        doc.ProviderUKPRN.ToString()
                                    });
                                    var provider = providers.FirstOrDefault();

                                    foreach (var courserun in doc.CourseRuns)
                                    {
                                        logCsvWriter.WriteField(doc.ProviderUKPRN);
                                        logCsvWriter.WriteField(provider?.ProviderName);
                                        logCsvWriter.WriteField(courserun.CourseInstanceId);
                                        logCsvWriter.WriteField(doc.CourseId);
                                        logCsvWriter.WriteField(courserun.CourseName);
                                        logCsvWriter.WriteField(courserun.StartDate);
                                        logCsvWriter.WriteField(courserun.Cost);
                                        logCsvWriter.WriteField(courserun.CostDescription);
                                        logCsvWriter.WriteField(courserun.DeliveryMode);
                                        logCsvWriter.WriteField(courserun.AttendancePattern);
                                        logCsvWriter.NextRecord();

                                        courserun.RecordStatus = CourseDirectory.Models.Enums.RecordStatus.Archived;
                                        count++;
                                    }

                                    count++;
                                    var documentLink = UriFactory.CreateDocumentUri(databaseId, coursesCollectionId, doc.id.ToString());
                                    await documentClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions()
                                    {
                                        PartitionKey = new Microsoft.Azure.Documents.PartitionKey(doc.ProviderUKPRN)
                                    });
                                }
                                continuation = queryResponse.ResponseContinuation;
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e.Message);
                                logger.LogError(e.Message);
                                continuation = null;
                            }
                        }while (continuation != null);

                        // Upload log CSV to blob storage
                        {
                            logStreamWriter.Flush();

                            logStream.Seek(0L, SeekOrigin.Begin);

                            var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                            await blob.UploadFromStreamAsync(logStream);
                        }
                    }

            Console.WriteLine($"{count} courses have been archived");
        }
        public static async Task Run(
            string input,                              // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory,
            [Inject] IBlobStorageHelper blobhelper)
        {
            var whitelistFileName           = "ProviderWhiteList.txt";
            var pendingCoursesFileName      = $"CoursesWithNoCostOrCostDecription-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var blobContainer               = configuration["BlobStorageSettings:Container"];
            var outputContainer             = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var apprenticeshipCollectionId  = "apprenticeship";
            var documentClient              = cosmosDbHelper.GetClient();
            var apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var logger    = loggerFactory.CreateLogger(typeof(ArchiveCourses));
            int count     = 0;
            var whitelist = await GetProviderWhiteList();

            var updatedBy = "ArchiveApprenticeships";


            foreach (var ukprn in whitelist)
            {
                string continuation = null;
                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    //try/catch required as there are Apprenticeship records that are not valid (venueId is null in cosmos).
                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.CreatedBy != "ApprenticeshipMigrator" && p.RecordStatus == CourseDirectory.Models.Enums.RecordStatus.Live)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        foreach (var doc in queryResponse)
                        {
                            //mark every location as arhived
                            foreach (var loc in doc.ApprenticeshipLocations)
                            {
                                loc.RecordStatus = CourseDirectory.Models.Enums.RecordStatus.Archived;
                                loc.UpdatedBy    = updatedBy;
                            }
                            doc.UpdatedBy = updatedBy;

                            var documentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, doc.id.ToString());
                            await documentClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions()
                            {
                                PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                            });

                            count++;
                        }
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);
            }

            logger.LogInformation($"Archived {count} Apprenticeships");
            Console.WriteLine($"Archived {count} Apprenticeships");

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }
        }
 public BlobAccessController(IBlobStorageHelper bobStorageHelper)
 {
     this.bobStorageHelper = bobStorageHelper;
 }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            var venuesCollectionId = configuration["CosmosDbCollectionSettings:VenuesCollectionId"];
            var connectionString   = configuration.GetConnectionString("TribalRestore");
            var blobContainer      = configuration["BlobStorageSettings:Container"];
            var container          = configuration["BlobStorageSettings:Container"];
            var whiteListProviders = await GetProviderWhiteList();

            var coursesCollectionId        = "courses";
            var apprenticeshipCollectionId = "apprenticeship";
            var databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var documentClient              = cosmosDbHelper.GetClient();
            var venueCollectionUri          = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
            var coursesCollectionUri        = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var logFileName                 = $"ProvidersWithInvalidVenueReferences--{DateTime.Now.ToString("dd-MM-yy HHmm")}";

            var          result          = new List <VenueReference>();
            const string WHITE_LIST_FILE = "ProviderWhiteList.txt";

            //counters
            var ukprnsThatFailedToFetchVenues = 0;
            var uniqueInvalidVenues           = new HashSet <string>();
            var replacedInvalidVenues         = new HashSet <string>();

            var allVenues = GetAllOldVenues();
            //grand totals
            var totalInvalidApprenticeshipLocationReferences = 0;
            var totalInvalidCourseRunReferences = 0;

            //provider scoped totals
            var invalidCourseRunReferences = 0;
            var invalidApprenticeshipLocationReferences = 0;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        //every provider
                        foreach (var ukprn in whiteListProviders)
                        {
                            try
                            {
                                //reset counters
                                invalidCourseRunReferences = 0;
                                invalidApprenticeshipLocationReferences = 0;

                                //fetch data for ukprn
                                var allCoursesForProvider = await GetCourses(ukprn);

                                var allApprenticeshipsForProvider = await GetApprenticeships(ukprn);

                                //courses
                                foreach (var course in allCoursesForProvider)
                                {
                                    //course runs
                                    foreach (var courserun in course.CourseRuns)
                                    {
                                        //only courses that references a venue (classroom based or both)
                                        if (courserun.VenueId != null && courserun.VenueId != Guid.Empty)
                                        {
                                            //current venue & old venue (pre migration)
                                            var currentVenue = await GetVenueById(courserun.VenueId?.ToString());

                                            if (currentVenue != null)
                                            {
                                                if (course.ProviderUKPRN != currentVenue.UKPRN)
                                                {
                                                    uniqueInvalidVenues.Add(courserun.VenueId.ToString());
                                                    invalidCourseRunReferences++;
                                                }

                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN        = course.ProviderUKPRN,
                                                    VenueId      = courserun.VenueId.ToString(),
                                                    VenueUKPRN   = currentVenue.UKPRN,
                                                    Address1     = currentVenue.Address1,
                                                    Postcode     = currentVenue.PostCode,
                                                    VenueName    = currentVenue.VenueName,
                                                    UKPRNMatched = (course.ProviderUKPRN == currentVenue.UKPRN),
                                                    Message      = (course.ProviderUKPRN == currentVenue.UKPRN) ? "Venue UKPRN Matches Course UKPRN" : "Venue UKPRN Does not match Course UKPRN",
                                                    Type         = "Course",
                                                    CourseId     = course.id,
                                                    CourseRunId  = courserun.id
                                                });
                                            }
                                            else
                                            {
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN        = course.ProviderUKPRN,
                                                    UKPRNMatched = false,
                                                    VenueUKPRN   = -1,
                                                    VenueId      = courserun.VenueId.ToString(),
                                                    Message      = "VenueId does not exist in venues",
                                                    Type         = "Course",
                                                    CourseId     = course.id,
                                                    CourseRunId  = courserun.id
                                                });
                                            }
                                        }
                                    }
                                    //total for all providers
                                    totalInvalidCourseRunReferences += invalidCourseRunReferences;
                                }

                                //apprenticeships
                                foreach (var apprenticeship in allApprenticeshipsForProvider)
                                {
                                    //apprenticeship locations
                                    foreach (var location in apprenticeship.ApprenticeshipLocations)
                                    {
                                        //only apprenticeshiplocations that references a venue (classroom based or both)
                                        if (location.VenueId.HasValue && location.LocationGuidId.HasValue && location.LocationGuidId != Guid.Empty)
                                        {
                                            var currentVenue = await GetVenueById(location.LocationGuidId.ToString());

                                            //venue exists in cosmos
                                            if (currentVenue != null)
                                            {
                                                if (location.ProviderUKPRN != currentVenue.UKPRN)
                                                {
                                                    uniqueInvalidVenues.Add(location.VenueId.ToString());
                                                    invalidApprenticeshipLocationReferences++;
                                                }

                                                //apprenticeshipId
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN = apprenticeship.ProviderUKPRN,
                                                    ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                    VenueId          = location.LocationGuidId.ToString(),
                                                    VenueUKPRN       = currentVenue.UKPRN,
                                                    Address1         = currentVenue.Address1,
                                                    Postcode         = currentVenue.PostCode,
                                                    VenueName        = currentVenue.VenueName,
                                                    UKPRNMatched     = (apprenticeship.ProviderUKPRN == currentVenue.UKPRN),
                                                    Message          = (apprenticeship.ProviderUKPRN == currentVenue.UKPRN) ? "Venue UKPRN Matches Apprenticeship UKPRN" : "Venue UKPRN Does not match Apprenticeship UKPRN",
                                                    Type             = "Apprenticeship",
                                                    ApprenticeshipId = apprenticeship.id
                                                });
                                            }
                                            else
                                            {
                                                result.Add(new VenueReference()
                                                {
                                                    UKPRN = apprenticeship.ProviderUKPRN,
                                                    ApprenticeshipLocationUKPRN = location.ProviderUKPRN,
                                                    UKPRNMatched     = false,
                                                    VenueUKPRN       = -1,
                                                    VenueId          = location.LocationGuidId.ToString(),
                                                    Type             = "Apprenticeship",
                                                    Message          = "VenueId does not exist in venues",
                                                    ApprenticeshipId = apprenticeship.id
                                                });
                                            }
                                        }
                                    }
                                    totalInvalidApprenticeshipLocationReferences += invalidApprenticeshipLocationReferences;
                                }

                                //total for provider
                                Console.WriteLine($"{invalidCourseRunReferences} invalid venue references for {ukprn}");
                                Console.WriteLine($"{invalidApprenticeshipLocationReferences} invalid apprenticeship references for {ukprn}");
                                Console.WriteLine($"{uniqueInvalidVenues.Count()} unique venues");
                            }
                            catch (Exception e)
                            {
                                log.LogError(e.Message, e);
                            }
                        }

                        //block to try and fetch all venues for every provider
                        //to make sure that venues can be fetched without error.
                        foreach (var ukprn in whiteListProviders)
                        {
                            try
                            {
                                var venues = await GetVenues(ukprn);
                            }
                            catch (Exception e)
                            {
                                log.LogError(e.Message, e);
                                Console.WriteLine($"{ukprn} - failed to fetch venues");
                                ukprnsThatFailedToFetchVenues++;
                            }
                        }

                        //write venue reference documents
                        logCsvWriter.WriteHeader(typeof(VenueReference));
                        logCsvWriter.NextRecord();
                        foreach (var id in result)
                        {
                            logCsvWriter.WriteRecord(id);
                            logCsvWriter.NextRecord();
                        }

                        // Upload log CSV to blob storage
                        {
                            logStreamWriter.Flush();

                            logStream.Seek(0L, SeekOrigin.Begin);

                            var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                            await blob.UploadFromStreamAsync(logStream);
                        }
                    }

            //
            Console.WriteLine($"{totalInvalidCourseRunReferences} courserun invalid references in total");
            Console.WriteLine($"{totalInvalidApprenticeshipLocationReferences} apprenticeship location invalid references in total");
            Console.WriteLine($"{replacedInvalidVenues.Count()} venues have been reverted back to old venues");
            Console.WriteLine($"{uniqueInvalidVenues.Count()} Venues were invalid");
            Console.WriteLine($"{ukprnsThatFailedToFetchVenues} ukprns failed to fetch venues");

            async Task <List <Course> > GetCourses(int ukprn)
            {
                var    courses      = new List <Course>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    courses.AddRange(queryResponse.ToList());
                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(courses);
            }

            async Task <List <Venue> > GetVenues(int ukprn)
            {
                var    venues       = new List <Venue>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Venue>(venueCollectionUri, feedOptions)
                                        .Where(p => p.UKPRN == ukprn)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Venue>();

                    venues.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(venues);
            }

            async Task <Venue> GetVenueById(string id)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
                var query          = documentClient
                                     .CreateDocumentQuery <Venue>(collectionLink, new FeedOptions()
                {
                    EnableCrossPartitionQuery = true
                })
                                     .Where(d => d.ID == id)
                                     .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn)
            {
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.RecordStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        apprenticeships.AddRange(queryResponse);
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobhelper.GetBlobContainer(blobContainer).GetBlockBlobReference(WHITE_LIST_FILE);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }

            IList <Venue> GetAllOldVenues()
            {
                var list = new List <Venue>();
                var lookupFileResourceName = "Dfc.ProviderPortal.TribalExporter.2020-01-24_0325-venues-backup.json";

                using (var stream = typeof(VenueReferenceChecker).Assembly.GetManifestResourceStream(lookupFileResourceName))
                    using (StreamReader file = new StreamReader(stream))
                    {
                        using (JsonTextReader reader = new JsonTextReader(file))
                        {
                            while (reader.Read())
                            {
                                JArray o2 = (JArray)JToken.ReadFrom(reader);

                                foreach (var item in o2)
                                {
                                    Venue v = item.ToObject <Venue>();
                                    list.Add(v);
                                }
                            }
                        }
                    }
                return(list);
            }
        }
Пример #20
0
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory)
        {
            var tribalConnectionString  = configuration.GetConnectionString("TribalRestore");
            var defaultConnectionString = configuration.GetConnectionString("DefaultConnection");
            var qaStatuses        = new List <ProviderQAStatus>();
            var cosmosDbClient    = cosmosDbHelper.GetClient();
            var databaseId        = configuration["CosmosDbSettings:DatabaseId"];
            var ukrlp             = "ukrlp";
            var apprenticehipsUri = "apprenticeship";
            var logFileName       = $"QAStatusMigrator-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var blobContainer     = configuration["BlobStorageSettings:Container"];
            var whitelistFileName = "ProviderWhiteList.txt";
            var whitelist         = await GetProviderWhiteList();

            using (var sqlConnection = new SqlConnection(tribalConnectionString))
            {
                var sql = @"SELECT DISTINCT PassedOverallQAChecks,Ukprn
                            FROM [Provider]";
                qaStatuses = sqlConnection.Query <ProviderQAStatus>(sql).ToList();
            }

            try
            {
                using (var logStream = new MemoryStream())
                    using (var logStreamWriter = new StreamWriter(logStream))
                        using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                        {
                            logCsvWriter.WriteField("UKPRN");
                            logCsvWriter.WriteField("CosmosID");
                            logCsvWriter.WriteField("PassedOverallQAChecks");
                            logCsvWriter.WriteField("Message");
                            logCsvWriter.NextRecord();

                            using (var sqlConnection = new SqlConnection(defaultConnectionString))
                            {
                                foreach (var s in qaStatuses)
                                {
                                    var message = "";
                                    if (!whitelist.Contains(s.UKPRN))
                                    {
                                        continue;
                                    }

                                    var provider = await GetExistingProvider(s.UKPRN.ToString(), cosmosDbClient);

                                    if (s.PassedOverallQAChecks && provider != null)
                                    {
                                        var sql    = @"IF NOT EXISTS (SELECT 1 FROM [Pttcd].[Providers] WHERE ProviderID = @ID) 
                                            INSERT INTO [Pttcd].[Providers] (ProviderId,ApprenticeshipQAStatus) SELECT @ID,@Status
                                        ELSE
                                            UPDATE [Pttcd].[Providers] SET ApprenticeshipQAStatus = @Status WHERE ProviderId = @ID";
                                        var result = sqlConnection.Execute(sql, new
                                        {
                                            ID     = provider.id,
                                            Status = 16
                                        });
                                    }
                                    else
                                    {
                                        var apprenticeships = await GetApprenticeships(s.UKPRN, cosmosDbClient);

                                        message = $"Found {apprenticeships.Count()} Apprenticeship that is either Live or PendingMigration";
                                    }

                                    logCsvWriter.WriteField(s.UKPRN);
                                    logCsvWriter.WriteField(provider?.id);
                                    logCsvWriter.WriteField(s.PassedOverallQAChecks);
                                    logCsvWriter.WriteField(message);
                                    logCsvWriter.NextRecord();
                                }
                            }

                            // Upload log CSV to blob storage
                            {
                                logStreamWriter.Flush();

                                logStream.Seek(0L, SeekOrigin.Begin);

                                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                                await blob.UploadFromStreamAsync(logStream);
                            }
                        }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
                throw;
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }

                return(results);
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn, IDocumentClient documentClient)
            {
                var    collectionLink  = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticehipsUri);
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(collectionLink, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && (p.RecordStatus == RecordStatus.Live || p.RecordStatus == RecordStatus.MigrationPending))
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Apprenticeship>();

                    apprenticeships.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <Provider> GetExistingProvider(string ukprn, IDocumentClient documentClient)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, ukrlp);

                var query = documentClient
                            .CreateDocumentQuery <Provider>(collectionLink, new FeedOptions()
                {
                })
                            .Where(d => d.UnitedKingdomProviderReferenceNumber == ukprn)
                            .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }
        }
        public static async Task Run(
            [TimerTrigger("%schedule%")] TimerInfo myTimer,
            ILogger log,
            [Inject] IOptions <ExporterSettings> exporterSettings,
            [Inject] IBlobStorageHelper blobStorageHelper,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICourseCollectionService courseCollectionService,
            [Inject] IVenueCollectionService venueCollectionService)
        {
            var configuration = exporterSettings.Value;

            log.LogInformation("[Export] waiting for trigger...");

            //TODO: add more logging after you get this working ...
            var logFile = new StringBuilder();

            logFile.AppendLine($"Starting {nameof(Export)} at {DateTime.Now}");

            var fileNames = new List <string>();
            var startDate = configuration.ExporterStartDate;

            var providersFileName = $"{DateTime.Today.ToString("yyyyMMdd")}\\Generated\\Providers_{DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss")}.json";

            logFile.AppendLine($"Start date: {startDate:dd/MM/yyyy hh:mm}");
            logFile.AppendLine($"Provider filename: {providersFileName}");

            var containerNameExporter      = configuration.ContainerNameExporter;
            var containerNameProviderFiles = configuration.ContainerNameProviderFiles;
            var migrationProviderCsv       = configuration.MigrationProviderCsv;

            logFile.AppendLine($"Attempting to get reference to blob containers: {containerNameExporter}, {containerNameProviderFiles}");

            var containerExporter      = blobStorageHelper.GetBlobContainer(containerNameExporter);
            var containerProviderFiles = blobStorageHelper.GetBlobContainer(containerNameProviderFiles);

            logFile.AppendLine($"Got references to blob containers: {containerNameExporter}, {containerNameProviderFiles}");

            try
            {
                log.LogInformation("[Export] grabbing providers");
                var providersForExport = await GetProvidersFromCsv(migrationProviderCsv, blobStorageHelper, logFile, containerProviderFiles);

                var providerFileName = await GenerateProvidersExport(providerCollectionService, providersForExport, logFile, providersFileName, containerExporter, containerNameExporter);

                fileNames.Add(providerFileName);

                var count = 0;
                var total = providersForExport.Count();

                // N.B. Deliberately not doing these in parallel to avoid creating too many DocumentClients...
                foreach (var provider in providersForExport)
                {
                    count++;

                    log.LogInformation($"[Export] checking {provider.Ukprn} [{count} of {total}]");

                    var export = await CheckForProviderUpdates(log, courseCollectionService,
                                                               venueCollectionService, logFile, provider, startDate, containerExporter,
                                                               containerNameExporter)
                                 .ConfigureAwait(false);

                    fileNames.AddRange(export);
                }

                var fileNamesFileName = $"{DateTime.Today.ToString("yyyyMMdd")}\\Generated\\FileNames.json";
                var fileNamesBlob     = containerExporter.GetBlockBlobReference(fileNamesFileName);
                await fileNamesBlob.UploadTextAsync(JsonConvert.SerializeObject(fileNames, Formatting.Indented));
            }
            catch (Exception e)
            {
                logFile.AppendLine(e.Message);
                logFile.AppendLine(e.ToString());
                throw;
            }
            finally
            {
                logFile.AppendLine($"Ending {nameof(Export)} at {DateTime.Now}");
                var logFileName     = $"{DateTime.Today.ToString("yyyyMMdd")}\\Generated\\Log_{DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss")}.txt";
                var logFileNameBlob = containerExporter.GetBlockBlobReference(logFileName);
                await logFileNameBlob.UploadTextAsync(logFile.ToString());
            }
        }
Пример #22
0
 public FileUploadController(IBlobStorageHelper blobStorageHelper)
 {
     _blobStorageHelper = blobStorageHelper;
 }
Пример #23
0
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] ILarsSearchService larsSearchService,
            [Inject] IBlobStorageHelper blobHelper)
        {
            var databaseId          = configuration["CosmosDbSettings:DatabaseId"];
            var coursesCollectionId = "courses";
            var logFileName         = $"CourseMigrator-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var blobContainer       = configuration["BlobStorageSettings:Container"];
            var whitelistFileName   = "ProviderWhiteList.txt";

            var connectionString = configuration.GetConnectionString("TribalRestore");

            var cosmosDbClient = cosmosDbHelper.GetClient();

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                        using (var conn1 = new SqlConnection(connectionString))
                            using (var conn2 = new SqlConnection(connectionString))
                            {
                                // Log CSV headers
                                logCsvWriter.WriteField("CourseId");
                                logCsvWriter.WriteField("UKPRN");
                                logCsvWriter.WriteField("Success");
                                logCsvWriter.WriteField("Status");
                                logCsvWriter.WriteField("Course instances");
                                logCsvWriter.WriteField("Error list");
                                logCsvWriter.NextRecord();

                                var whitelist = await GetProviderWhiteList();

                                await conn1.OpenAsync();

                                await conn2.OpenAsync();

                                using (var coursesCmd = conn1.CreateCommand())
                                    using (var coursesInstancesCmd = conn2.CreateCommand())
                                    {
                                        coursesCmd.CommandTimeout          = 60 * 60; // 1 hour
                                        coursesInstancesCmd.CommandTimeout = 60 * 60; // 1 hour

                                        coursesCmd.CommandText = @"
SELECT
    c.CourseId,
    c.CourseTitle,
    c.CourseSummary,
    c.LearningAimRefId,
    c.QualificationLevelId,
    c.EntryRequirements,
    c.ProviderOwnCourseRef,
    c.Url,
    p.UKPRN,
    c.EquipmentRequired,
    c.AssessmentMethod,
    p.Loans24Plus
FROM Course c
JOIN Provider p ON c.ProviderId = p.ProviderId
WHERE c.RecordStatusId = 2  --Live
--Last updated within 24 months of data freeze 28/02
AND (c.ModifiedDateTimeUtc >= '2018-02-28' OR EXISTS (
    SELECT 1 FROM CourseInstance ci
    WHERE ci.CourseId = c.CourseId
    AND ci.RecordStatusId = 2
    AND ci.ModifiedDateTimeUtc >= '2018-02-28'
))
ORDER BY c.CourseId, c.ProviderId";

                                        coursesInstancesCmd.CommandText = @"
SELECT
    ci.CourseInstanceId,
    ci.CourseId,
    ci.ProviderOwnCourseInstanceRef,
    ci.StudyModeId,
    ci.AttendanceTypeId,
    ci.AttendancePatternId,
    ci.DurationUnit,
    ci.DurationUnitId,
    ci.DurationAsText,
    ci.StartDateDescription,
	cisd.StartDate,
    ci.Price,
    ci.PriceAsText,
    ci.Url,
    civ.VenueId,
    ci.VenueLocationId
FROM CourseInstance ci
LEFT JOIN CourseInstanceVenue civ ON ci.CourseInstanceId = civ.CourseInstanceId
LEFT JOIN CourseInstanceStartDate cisd ON ci.CourseInstanceId = cisd.CourseInstanceId
WHERE ci.RecordStatusId = 2  --Live
ORDER BY ci.CourseId, ci.OfferedByProviderId";

                                        using (var coursesReader = coursesCmd.ExecuteReader())
                                            using (var courseInstanceReader = coursesInstancesCmd.ExecuteReader())
                                            {
                                                var instanceReader     = new CourseInstanceReader(courseInstanceReader);
                                                var instanceEnumerator = instanceReader.ProcessReader().GetEnumerator();
                                                var courseRowReader    = coursesReader.GetRowParser <CourseResult>();

                                                while (await coursesReader.ReadAsync())
                                                {
                                                    var course = courseRowReader(coursesReader);

                                                    // If provider is not on whitelist - skip this course
                                                    if (!whitelist.Contains(course.UKPRN))
                                                    {
                                                        continue;
                                                    }

                                                    var instances = instanceReader.ConsumeReader(instanceEnumerator, course.CourseId);

                                                    var errors = new List <string>();
                                                    CourseMigrationResult result;

                                                    try
                                                    {
                                                        // Tribal don't have any Courses with zero CourseInstances...
                                                        if (instances.Count == 0)
                                                        {
                                                            errors.Add("Found zero CourseInstances.");
                                                        }

                                                        // Check LARS
                                                        var larsSearchResults = !string.IsNullOrEmpty(course.LearningAimRefId) ?
                                                                                await QueryLars(course.LearningAimRefId) :
                                                                                Array.Empty <LarsSearchResultItem>();

                                                        // Check the venues exist
                                                        Dictionary <int, Guid> venueIdMap = new Dictionary <int, Guid>();
                                                        foreach (var venueId in instances.Where(i => i.VenueId.HasValue).Select(i => i.VenueId.Value))
                                                        {
                                                            if (venueIdMap.ContainsKey(venueId))
                                                            {
                                                                continue;
                                                            }

                                                            var cosmosVenue = await venueCollectionService.GetDocumentByVenueId(venueId);

                                                            if (cosmosVenue == null)
                                                            {
                                                                errors.Add($"Missing venue {venueId}.");
                                                            }
                                                            else
                                                            {
                                                                venueIdMap.Add(venueId, Guid.Parse(cosmosVenue.ID));
                                                            }
                                                        }

                                                        if (errors.Count == 0)
                                                        {
                                                            // Got the course in Cosmos already?
                                                            var existingCourseRecord = await GetExistingCourse(course.CourseId, course.UKPRN, cosmosDbClient);

                                                            var mappedCourseRuns = instances
                                                                                   .Select(i =>
                                                            {
                                                                Guid?venueId = null;
                                                                if (i.VenueId.HasValue)
                                                                {
                                                                    venueId = venueIdMap[i.VenueId.Value];
                                                                }

                                                                // Retain the existing Cosmos ID if there is one
                                                                // N.B. We can have more than one match on CourseInstanceId since we 'explode' on multiple start dates
                                                                var courseRunId =
                                                                    existingCourseRecord?.CourseRuns.SingleOrDefault(r => r.CourseInstanceId == i.CourseInstanceId && r.StartDate == i.StartDate)?.id ??
                                                                    Guid.NewGuid();

                                                                return(MapCourseInstance(course, i, courseRunId, venueId, errors));
                                                            })
                                                                                   .ToList();

                                                            var courseId     = existingCourseRecord?.id ?? Guid.NewGuid();
                                                            var mappedCourse = MapCourse(course, mappedCourseRuns, larsSearchResults, courseId, errors);

                                                            var added = await UpsertCourse(mappedCourse, cosmosDbClient);

                                                            result = added ? CourseMigrationResult.Inserted : CourseMigrationResult.Updated;
                                                        }
                                                        else
                                                        {
                                                            result = CourseMigrationResult.SkippedDueToErrors;
                                                        }
                                                    }
                                                    catch (Exception ex)
                                                    {
                                                        errors.Add(ex.ToString().Replace("\n", " "));
                                                        result = CourseMigrationResult.Exception;
                                                    }

                                                    // Write to log
                                                    logCsvWriter.WriteField(course.CourseId);
                                                    logCsvWriter.WriteField(course.UKPRN);
                                                    logCsvWriter.WriteField(result == CourseMigrationResult.Inserted || result == CourseMigrationResult.Updated);
                                                    logCsvWriter.WriteField(result.ToString());
                                                    logCsvWriter.WriteField(instances.Count);
                                                    logCsvWriter.WriteField(string.Join(", ", errors));
                                                    logCsvWriter.NextRecord();
                                                }
                                            }
                                    }

                                // Upload log CSV to blob storage
                                {
                                    logStreamWriter.Flush();

                                    logStream.Seek(0L, SeekOrigin.Begin);

                                    var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                                    await blob.UploadFromStreamAsync(logStream);
                                }
                            }


            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }

                return(results);
            }

            async Task <IReadOnlyCollection <LarsSearchResultItem> > QueryLars(string learningAimRef)
            {
                var result = await larsSearchService.SearchAsync(new LarsSearchCriteria(learningAimRef, top : 1, skip : 0));

                if (result.IsFailure)
                {
                    throw new Exception($"LARS search failed:\n{result.Error}");
                }

                return(result.Value.Value.ToList());
            }

            async Task <bool> UpsertCourse(Course course, IDocumentClient documentClient)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);

                var result = await documentClient.UpsertDocumentAsync(collectionLink, course, new RequestOptions()
                {
                    PartitionKey = new Microsoft.Azure.Documents.PartitionKey(course.ProviderUKPRN)
                });

                return(result.StatusCode == HttpStatusCode.Created);
            }

            async Task <Course> GetExistingCourse(int courseId, int ukprn, IDocumentClient documentClient)
            {
                var collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);

                var query = documentClient
                            .CreateDocumentQuery <Course>(collectionLink, new FeedOptions()
                {
                    PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                })
                            .Where(d => d.CourseId == courseId)
                            .AsDocumentQuery();

                return((await query.ExecuteNextAsync()).FirstOrDefault());
            }

            AttendancePattern MapAttendancePattern(DeliveryMode deliveryMode, int?attendancePatternId, out bool hasError)
            {
                if (deliveryMode != DeliveryMode.ClassroomBased)
                {
                    hasError = false;
                    return(AttendancePattern.Undefined);
                }

                if (!attendancePatternId.HasValue)
                {
                    hasError = true;
                    return(AttendancePattern.Undefined);
                }

                switch (attendancePatternId.Value)
                {
                case 1:
                    hasError = false;
                    return(AttendancePattern.Daytime);

                case 2:
                    hasError = false;
                    return(AttendancePattern.DayOrBlockRelease);

                case 3:
                case 4:
                    hasError = false;
                    return(AttendancePattern.Evening);

                case 5:
                    hasError = false;
                    return(AttendancePattern.Weekend);

                case 6:
                case 7:
                case 8:
                default:
                    hasError = true;
                    return(AttendancePattern.Undefined);
                }
            }

            DeliveryMode MapDeliveryMode(int?attendanceTypeId, out bool hasError)
            {
                if (!attendanceTypeId.HasValue)
                {
                    hasError = true;
                    return(DeliveryMode.Undefined);
                }

                switch (attendanceTypeId.Value)
                {
                case 1:
                    hasError = false;
                    return(DeliveryMode.ClassroomBased);

                case 2:
                case 3:
                    hasError = false;
                    return(DeliveryMode.WorkBased);

                case 7:
                case 8:
                    hasError = false;
                    return(DeliveryMode.Online);

                case 4:
                case 5:
                case 6:
                case 9:
                default:
                    hasError = true;
                    return(DeliveryMode.Undefined);
                }
            }

            StudyMode MapStudyMode(DeliveryMode deliveryMode, int?studyModeId, out bool hasError)
            {
                if (deliveryMode != DeliveryMode.ClassroomBased)
                {
                    hasError = false;
                    return(StudyMode.Undefined);
                }

                if (!studyModeId.HasValue)
                {
                    hasError = true;
                    return(StudyMode.Undefined);
                }

                switch (studyModeId.Value)
                {
                case 1:
                    hasError = false;
                    return(StudyMode.FullTime);

                case 2:
                    hasError = false;
                    return(StudyMode.PartTime);

                case 3:
                    hasError = true;
                    return(StudyMode.Undefined);

                case 4:
                    hasError = false;
                    return(StudyMode.Flexible);

                default:
                    hasError = true;
                    return(StudyMode.Undefined);
                }
            }

            (DurationUnit, int?) MapDuration(int?durationUnit, int?durationValue, out bool hasError)
            {
                if (!durationUnit.HasValue)
                {
                    hasError = true;
                    return(DurationUnit.Undefined, null);
                }

                switch (durationUnit.Value)
                {
                case 1:
                    hasError = false;
                    return(DurationUnit.Hours, durationValue);

                case 2:
                    hasError = false;
                    return(DurationUnit.Days, durationValue);

                case 3:
                    hasError = false;
                    return(DurationUnit.Weeks, durationValue);

                case 4:
                    hasError = false;
                    return(DurationUnit.Months, durationValue);

                case 5:
                    hasError = false;
                    return(DurationUnit.Months, 3);

                case 7:
                    hasError = false;
                    return(DurationUnit.Years, durationValue);

                case 6:
                default:
                    hasError = true;
                    return(DurationUnit.Undefined, null);
                }
            }

            CourseRun MapCourseInstance(
                CourseResult course,
                CourseInstanceResult courseInstance,
                Guid id,
                Guid?venueId,
                List <string> errors)
            {
                var deliveryMode      = MapDeliveryMode(courseInstance.AttendanceTypeId, out var deliveryModeError);
                var attendancePattern = MapAttendancePattern(deliveryMode, courseInstance.AttendancePatternId, out var attendancePatternError);
                var studyMode         = MapStudyMode(deliveryMode, courseInstance.StudyModeId, out var studyModeError);

                var(durationUnit, durationValue) = MapDuration(courseInstance.DurationUnitId, courseInstance.DurationUnit, out var durationError);

                var hasErrors = false;

                if (attendancePatternError)
                {
                    errors.Add($"Invalid AttendancePattern");
                    hasErrors = true;
                }

                if (deliveryModeError)
                {
                    errors.Add($"Invalid DeliveryMode");
                    hasErrors = true;
                }

                if (studyModeError)
                {
                    errors.Add($"Invalid StudyMode");
                    hasErrors = true;
                }

                if (durationError)
                {
                    errors.Add($"Invalid Duration");
                    hasErrors = true;
                }

                bool     flexibleStartDate = default;
                DateTime?startDate         = default;

                if (deliveryMode == DeliveryMode.Online)
                {
                    flexibleStartDate = true;
                }
                else if (courseInstance.StartDate.HasValue)
                {
                    flexibleStartDate = false;
                    startDate         = courseInstance.StartDate;
                }
                else if (string.IsNullOrEmpty(courseInstance.StartDateDescription))
                {
                    errors.Add($"Empty StartDateDescription");
                    hasErrors = true;
                }
                else if (DateTime.TryParseExact(courseInstance.StartDateDescription, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var sd))
                {
                    flexibleStartDate = false;
                    startDate         = sd;
                }
                else
                {
                    flexibleStartDate = true;
                }

                if (deliveryMode == DeliveryMode.ClassroomBased && !venueId.HasValue)
                {
                    errors.Add($"No venue");
                    hasErrors = true;
                }

                // Work-based should have regions(s) or be national
                bool?national = null;
                IEnumerable <string>             regions    = Array.Empty <string>();
                IEnumerable <SubRegionItemModel> subRegions = Array.Empty <SubRegionItemModel>();

                if (deliveryMode == DeliveryMode.WorkBased)
                {
                    if (!courseInstance.VenueLocationId.HasValue)
                    {
                        errors.Add("No region found");
                        hasErrors = true;
                    }
                    else
                    {
                        if (RegionLookup.IsNational(courseInstance.VenueLocationId.Value))
                        {
                            national = true;
                        }
                        else
                        {
                            var lookupResult = RegionLookup.FindRegions(courseInstance.VenueLocationId.Value);

                            if (!lookupResult.HasValue)
                            {
                                errors.Add($"Cannot find sub-region(s) for VenueLocationId {courseInstance.VenueLocationId.Value}");
                                hasErrors = true;
                            }
                            else
                            {
                                regions    = lookupResult.Value.regionIds;
                                subRegions = lookupResult.Value.subRegions;
                                national   = false;
                            }
                        }
                    }
                }

                var recordStatus = hasErrors ? RecordStatus.MigrationPending : RecordStatus.Live;

                return(new CourseRun()
                {
                    AttendancePattern = attendancePattern,
                    Cost = courseInstance.Price,
                    CostDescription = courseInstance.PriceAsText,
                    CourseInstanceId = courseInstance.CourseInstanceId,
                    CourseName = course.CourseTitle,
                    CourseURL = courseInstance.Url,
                    CreatedBy = "CourseMigrator",
                    CreatedDate = DateTime.Now,
                    DeliveryMode = deliveryMode,
                    DurationUnit = durationUnit,
                    DurationValue = durationValue,
                    FlexibleStartDate = flexibleStartDate,
                    id = id,
                    ProviderCourseID = courseInstance.ProviderOwnCourseInstanceRef,
                    RecordStatus = recordStatus,
                    National = national,
                    Regions = regions,
                    StartDate = startDate,
                    StudyMode = studyMode,
                    SubRegions = subRegions,
                    //UpdatedBy
                    UpdatedDate = DateTime.Now,
                    VenueId = venueId
                });
            }

            Course MapCourse(
                CourseResult course,
                IReadOnlyCollection <CourseRun> courseRuns,
                IReadOnlyCollection <LarsSearchResultItem> larsSearchResults,
                Guid id,
                List <string> errors)
            {
                var isValid = courseRuns.All(r => r.RecordStatus.HasFlag(RecordStatus.Live));

                LarlessReason?larlessReason = string.IsNullOrEmpty(course.LearningAimRefId) ?
                                              LarlessReason.NoLars : larsSearchResults.Count == 0 ?
                                              LarlessReason.UnknownLars : larsSearchResults.Count > 1 ?
                                              LarlessReason.MultipleMatchingLars : // TODO Consider expired LARS
                                              LarlessReason.Undefined;

                var qualification = larsSearchResults.Count == 1 ? larsSearchResults.Single() : null;

                if (qualification == null)
                {
                    foreach (var cr in courseRuns)
                    {
                        cr.RecordStatus = RecordStatus.MigrationPending;
                    }

                    errors.Add("LARS lookup failed");
                    isValid = false;
                }

                return(new Course()
                {
                    AdultEducationBudget = default,
 public void UploadFile(string pathToFile, IBlobStorageHelper blobStorageHelper)
 {
     blobStorageHelper.UploadTo(pathToFile, Settings.BlobSasUri);
     UploadStatus = FileUploadStatus.Created;
 }
Пример #25
0
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory)
        {
            var whitelistFileName          = "ProviderWhiteList.txt";
            var venuesCollectionId         = "venues";
            var coursesCollectionId        = "courses";
            var apprenticeshipCollectionId = "apprenticeship";
            var blobContainer  = configuration["BlobStorageSettings:Container"];
            var databaseId     = configuration["CosmosDbSettings:DatabaseId"];
            var documentClient = cosmosDbHelper.GetClient();
            var updatedBy      = "ArchiveVenues";
            var logger         = loggerFactory.CreateLogger(typeof(ArchiveCourses));
            var whitelist      = await GetProviderWhiteList();

            var venueCollectionUri          = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
            var coursesCollectionUri        = UriFactory.CreateDocumentCollectionUri(databaseId, coursesCollectionId);
            var apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var totalArchived = 0;
            var totalCoursesReferencingOldVenue      = 0;
            var totalApprenticeshipReferenceoldVenue = 0;

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        // archived venues
                        logCsvWriter.WriteField("UKPRN");
                        logCsvWriter.WriteField("Archived VenueId");
                        logCsvWriter.WriteField("Archived Venue Name");
                        logCsvWriter.WriteField("Archived Venue Address1");
                        logCsvWriter.WriteField("New VenueId");
                        logCsvWriter.WriteField("New Venue Name");
                        logCsvWriter.WriteField("New Venue Address1");
                        logCsvWriter.WriteField("Course Run Id");
                        logCsvWriter.WriteField("ApprenticeshipLocation Id");
                        logCsvWriter.WriteField("Message");
                        logCsvWriter.WriteField("Type");

                        logCsvWriter.NextRecord();

                        foreach (var ukprn in whitelist)
                        {
                            try
                            {
                                int totalArchivedForProvider = 0;
                                var allVenuesForProvider     = await GetVenues(ukprn);

                                var allCoursesForProvider = await GetCourses(ukprn);

                                var allApprenticeshipsForProvider = await GetApprenticeships(ukprn);

                                //identify duplicates
                                var comp         = new VenueEqualityComparer();
                                var uniqueGroups = allVenuesForProvider.GroupBy(x => x, comp);

                                //archive duplicate venues
                                foreach (var item in uniqueGroups)
                                {
                                    //tribal venues & trival locations when venues were migrated, both locations and & venues from tribal
                                    //were migrated as seperate records even though the address was the same. The below attempts to merge the two.
                                    var migratedVenues      = item.ToList().Where(x => x.CreatedBy == "VenueMigrator" && x.UpdatedBy != updatedBy); //expecting more than one here.
                                    var tribalLocationVenue = migratedVenues.FirstOrDefault(x => x.LocationId != null);                             //Migrated Location
                                    var tribalVenue         = migratedVenues.FirstOrDefault(x => x.VenueID != 0);                                   //Migrated Venue
                                    var currentVenue        = MergeVenue(tribalLocationVenue, tribalVenue, out string venueType);

                                    //If there is no current venue, it means that either the venue was created by a previous migration
                                    //e.g. CreatedBy != VenueMigrator and all Venues must be Archived
                                    //OR
                                    //the Archiver has already archived this group e.g. UpdatedBy == "ArchiveVenues" and therefore we skip changing
                                    //this record to archived, as the duplicates have already been removed.
                                    if (currentVenue == null)
                                    {
                                        var venuesNotMigratedByMigrationProcess = item.ToList();
                                        foreach (var archivingVenue in venuesNotMigratedByMigrationProcess)
                                        {
                                            //only archive venues that haven't already been processed by archiveVenues function
                                            if (archivingVenue.UpdatedBy != updatedBy)
                                            {
                                                await ArchiveVenue(archivingVenue, ukprn);

                                                logCsvWriter.WriteField(ukprn);
                                                logCsvWriter.WriteField(archivingVenue.ID);
                                                logCsvWriter.WriteField(archivingVenue.VenueName);
                                                logCsvWriter.WriteField($"{archivingVenue.Address1},{archivingVenue.Address2}, {archivingVenue.PostCode}");
                                                logCsvWriter.WriteField("");
                                                logCsvWriter.WriteField("");
                                                logCsvWriter.WriteField("");
                                                logCsvWriter.WriteField("");
                                                logCsvWriter.WriteField(""); //ApprenticeshipLocationId
                                                logCsvWriter.WriteField($"All old Venues archived, there were {venuesNotMigratedByMigrationProcess.Count()} duplicate Venues.");
                                                logCsvWriter.WriteField("Venue");
                                                logCsvWriter.NextRecord();
                                            }
                                        }

                                        //continue to next ukprn as per the above logic
                                        continue;
                                    }

                                    var nonCurrentVenues = item.ToList().Where(x => x.ID != currentVenue.ID).ToList(); // All venues that will be archived

                                    //if there is a location venue & venue, add venue to list of non current venues
                                    //and update the currentVenue to indicate it has been merged.
                                    if (venueType == "Both")
                                    {
                                        nonCurrentVenues.Add(tribalVenue);

                                        await ReplaceMergedREcord(currentVenue);
                                    }

                                    //courses that have course runs with old venue references.
                                    var courseRunsOldVenue = allCoursesForProvider.Where(p => p.CourseRuns.Any(x => nonCurrentVenues.Where(y => Guid.Parse(y.ID) == x.VenueId).Count() > 0)).ToList();
                                    totalCoursesReferencingOldVenue += courseRunsOldVenue.Count();

                                    //apprenticeships that have locations with old venue refe
                                    var apprenticeshipsOldVenue = allApprenticeshipsForProvider.Where(p => p.ApprenticeshipLocations.Any(x => nonCurrentVenues.Where(y => Guid.Parse(y.ID) == x.LocationGuidId).Count() > 0)).ToList();
                                    totalApprenticeshipReferenceoldVenue += apprenticeshipsOldVenue.Count();

                                    Console.WriteLine($"Archiving {nonCurrentVenues.Count()} - {ukprn} - {currentVenue.Address1}");

                                    //handle archiving venue
                                    foreach (var archivingVenue in nonCurrentVenues)
                                    {
                                        await ArchiveVenue(archivingVenue, ukprn);

                                        logCsvWriter.WriteField(ukprn);
                                        logCsvWriter.WriteField(archivingVenue.ID);
                                        logCsvWriter.WriteField(archivingVenue.VenueName);
                                        logCsvWriter.WriteField($"{archivingVenue.Address1},{archivingVenue.Address2}, {archivingVenue.PostCode}");
                                        logCsvWriter.WriteField(currentVenue.ID);
                                        logCsvWriter.WriteField(currentVenue.VenueName);
                                        logCsvWriter.WriteField($"{currentVenue.Address1},{currentVenue.Address2}, {currentVenue.PostCode}");
                                        logCsvWriter.WriteField("");
                                        logCsvWriter.WriteField(""); //ApprenticeshipLocationId
                                        logCsvWriter.WriteField($"There were {nonCurrentVenues.Count()} duplicate Venues");
                                        logCsvWriter.WriteField("Venue");
                                        logCsvWriter.NextRecord();

                                        totalArchived++;
                                        totalArchivedForProvider++;

                                        //update courses that reference old venues
                                        foreach (var course in courseRunsOldVenue)
                                        {
                                            //update venue to point at new venue.
                                            course.CourseRuns.Where(p => nonCurrentVenues.Any(y => Guid.Parse(y.ID) == p.VenueId))
                                            .ToList()
                                            .ForEach(x =>
                                            {
                                                //update course instance
                                                x.VenueId   = Guid.Parse(currentVenue.ID);
                                                x.UpdatedBy = updatedBy;

                                                //log change
                                                logCsvWriter.WriteField(ukprn);
                                                logCsvWriter.WriteField(archivingVenue.ID);
                                                logCsvWriter.WriteField(archivingVenue.VenueName);
                                                logCsvWriter.WriteField($"{archivingVenue.Address1},{archivingVenue.Address2}, {archivingVenue.PostCode}");
                                                logCsvWriter.WriteField(currentVenue.ID);
                                                logCsvWriter.WriteField(currentVenue.VenueName);
                                                logCsvWriter.WriteField($"{currentVenue.Address1},{currentVenue.Address2}, {currentVenue.PostCode}");
                                                logCsvWriter.WriteField(x.CourseInstanceId);
                                                logCsvWriter.WriteField("");                                      //ApprenticeshipLocationId
                                                logCsvWriter.WriteField($"There were {nonCurrentVenues.Count()} duplicate Venues");
                                                logCsvWriter.WriteField("Course");
                                                logCsvWriter.NextRecord();
                                            });

                                            //update venue to reference currentVenue
                                            var coursedocumentLink = UriFactory.CreateDocumentUri(databaseId, coursesCollectionId, course.id.ToString());
                                            await documentClient.ReplaceDocumentAsync(coursedocumentLink, course, new RequestOptions()
                                            {
                                                PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                                            });
                                        }

                                        //update courses that reference old venues
                                        foreach (var apprenticeship in apprenticeshipsOldVenue)
                                        {
                                            //update venue to point at new venue for locations
                                            apprenticeship.ApprenticeshipLocations.Where(p => nonCurrentVenues.Any(y => Guid.Parse(y.ID) == p.LocationGuidId))
                                            .ToList()
                                            .ForEach(x =>
                                            {
                                                //update apprenticeship location
                                                x.LocationGuidId = Guid.Parse(currentVenue.ID);
                                                x.UpdatedBy      = updatedBy;
                                                x.LocationId     = currentVenue.LocationId;

                                                //log change
                                                logCsvWriter.WriteField(ukprn);
                                                logCsvWriter.WriteField(archivingVenue.ID);
                                                logCsvWriter.WriteField(archivingVenue.VenueName);
                                                logCsvWriter.WriteField($"{archivingVenue.Address1},{archivingVenue.Address2}, {archivingVenue.PostCode}");
                                                logCsvWriter.WriteField(currentVenue.ID);
                                                logCsvWriter.WriteField(currentVenue.VenueName);
                                                logCsvWriter.WriteField($"{currentVenue.Address1},{currentVenue.Address2}, {currentVenue.PostCode}");
                                                logCsvWriter.WriteField("");                                                           //Course Instance
                                                logCsvWriter.WriteField(x.Id);
                                                logCsvWriter.WriteField($"There were {nonCurrentVenues.Count()} duplicate Venues");
                                                logCsvWriter.WriteField("Apprenticeship");
                                                logCsvWriter.NextRecord();
                                            });


                                            //update apprenticeship to reference currentvenue
                                            var apprenticeshipDocumentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, apprenticeship.id.ToString());
                                            await documentClient.ReplaceDocumentAsync(apprenticeshipDocumentLink, apprenticeship, new RequestOptions());
                                        }
                                    }
                                }
                                Console.WriteLine($"Archived {totalArchivedForProvider} Venues for {ukprn}");
                                logger.LogInformation($"Archived {totalArchivedForProvider} Venues for {ukprn}");
                            }
                            catch (Exception e)
                            {
                                logger.LogError(e.Message);
                            }
                        }

                        // Upload log CSV to blob storage
                        {
                            logStreamWriter.Flush();

                            logStream.Seek(0L, SeekOrigin.Begin);

                            var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference("ArchivedVenues");
                            await blob.UploadFromStreamAsync(logStream);
                        }
                    }


            Console.WriteLine($"Total Course runs that reference an old venue: {totalCoursesReferencingOldVenue}");
            Console.WriteLine($"Total Apparenticeships that reference an old Venue {totalApprenticeshipReferenceoldVenue}");
            Console.WriteLine($"Total Archived Venues {totalArchived}");

            async Task ArchiveVenue(Venue archivingVenue, int ukprn)
            {
                //archive venue
                archivingVenue.Status      = VenueStatus.Archived;
                archivingVenue.UpdatedBy   = updatedBy;
                archivingVenue.DateUpdated = DateTime.Now;
                var documentLink = UriFactory.CreateDocumentUri(databaseId, venuesCollectionId, archivingVenue.ID.ToString());
                await documentClient.ReplaceDocumentAsync(documentLink, archivingVenue, new RequestOptions());
            }

            async Task ReplaceMergedREcord(Venue mergedRecord)
            {
                //archive venue
                mergedRecord.UpdatedBy   = updatedBy;
                mergedRecord.DateUpdated = DateTime.Now;
                var documentLink = UriFactory.CreateDocumentUri(databaseId, venuesCollectionId, mergedRecord.ID.ToString());
                await documentClient.ReplaceDocumentAsync(documentLink, mergedRecord, new RequestOptions());
            }

            Venue MergeVenue(Venue locationVenue, Venue venue, out string selectedVenue)
            {
                //default to first none null venue, location is chosen first.
                var ven = locationVenue ?? venue;

                if (locationVenue != null && venue != null)
                {
                    selectedVenue = "Both";
                }
                else if (locationVenue == null && venue != null)
                {
                    selectedVenue = "Venue";
                }
                else if (locationVenue != null && venue == null)
                {
                    selectedVenue = "Location";
                }
                else
                {
                    selectedVenue = "None";
                }

                //if there are two venues, one with a venue id & one with a location id.
                //merge them.
                if (locationVenue != null && venue != null)
                {
                    ven.VenueID = venue.VenueID;
                }

                return(ven);
            }

            async Task <List <Course> > GetCourses(int ukprn)
            {
                var courses = new List <Course>();
                //Get all courses
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Course>(coursesCollectionUri, feedOptions)
                                        .Where(p => p.ProviderUKPRN == ukprn && p.CourseStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Course>();

                    courses.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(courses);
            }

            async Task <List <Venue> > GetVenues(int ukprn)
            {
                var    venues       = new List <Venue>();
                string continuation = null;

                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation
                    };

                    var queryResponse = await documentClient.CreateDocumentQuery <Venue>(venueCollectionUri, feedOptions)
                                        .Where(p => p.UKPRN == ukprn && p.Status == VenueStatus.Live)
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Venue>();

                    venues.AddRange(queryResponse.ToList());

                    continuation = queryResponse.ResponseContinuation;
                }while (continuation != null);
                return(venues);
            }

            async Task <List <Apprenticeship> > GetApprenticeships(int ukprn)
            {
                var    apprenticeships = new List <Apprenticeship>();
                string continuation    = null;

                //get all apprenticeships for provider
                do
                {
                    var feedOptions = new FeedOptions()
                    {
                        RequestContinuation = continuation,
                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                    };

                    //try/catch required as there are Apprenticeship records that are not valid (venueId is null in cosmos).
                    try
                    {
                        var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                            .Where(p => p.ProviderUKPRN == ukprn && p.RecordStatus != CourseDirectory.Models.Enums.RecordStatus.Archived)
                                            .AsDocumentQuery()
                                            .ExecuteNextAsync <Apprenticeship>();

                        apprenticeships.AddRange(queryResponse);
                        continuation = queryResponse.ResponseContinuation;
                    }
                    catch (Exception)
                    {
                        continuation = null;
                    }
                }while (continuation != null);

                return(apprenticeships);
            }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }

                return(results);
            }
        }
        public async Task Run(
            ILogger log,
            IConfigurationRoot configuration,
            ICosmosDbHelper cosmosDbHelper,
            IBlobStorageHelper blobHelper,
            IProviderCollectionService providerCollectionService,
            ICourseCollectionService courseCollectionService,
            IApprenticeshipCollectionService apprenticeshipCollectionService,
            IMigrationReportCollectionService migrationReportCollectionService)
        {
            log.LogInformation("Starting Migration Report generation");

            var migrationLog = new StringBuilder();

            migrationLog.AppendLine("-------------------------------------------------------");
            migrationLog.AppendLine("Starting Migration Report generation");
            migrationLog.AppendLine("-------------------------------------------------------");

            var stopWatch = new Stopwatch();

            stopWatch.Start();

            IReadOnlyList <RecordStatus> migratedStatusList = new List <RecordStatus>
            {
                RecordStatus.Live,
                RecordStatus.MigrationPending,
            };

            var blobContainer = blobHelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);

            var whiteListedProviders = await GetProviderWhiteList(blobHelper, blobContainer);

            var cosmosClient = cosmosDbHelper.GetClient();

            log.LogDebug("Fetching migrated provider count...");
            var migratedProvidersCount = (await providerCollectionService.GetAllMigratedProviders("Provider.Migrator")).Count;

            log.LogDebug($"Migrated Provider count: {migratedProvidersCount}.");

            log.LogDebug("Fetching providers...");
            var providers = await providerCollectionService.GetDocumentsByUkprn(whiteListedProviders);

            var providerTypeCounts = providers.GroupBy(t => t.ProviderType).Select(g => new { type = g.Key, qty = g.Count() });

            log.LogDebug($"Provider counts: {string.Join("; ", providerTypeCounts.Select(c => $"{c.type}: {c.qty}"))}. Total: {providers.Count}");

            int progress = 1;
            int feCourseReportEntryCount        = 0;
            int apprenticeshipsReportEntryCount = 0;

            foreach (var ukprn in whiteListedProviders)
            {
                try
                {
                    var provider = providers.Single(p => int.Parse(p.UnitedKingdomProviderReferenceNumber) == ukprn);

                    var logStart = $"STARTED : Generating report for provider with UKPRN: {provider.UnitedKingdomProviderReferenceNumber}. Progress: {progress++}/{whiteListedProviders.Count}";
                    log.LogDebug(logStart);
                    migrationLog.AppendLine(logStart);

                    switch (provider.ProviderType)
                    {
                    case ProviderType.Both:
                        await GenerateApprenticeshipReport(
                            configuration, apprenticeshipCollectionService, migrationReportCollectionService, provider,
                            migratedStatusList, cosmosClient);

                        apprenticeshipsReportEntryCount++;
                        await GenerateFECourseReport(
                            configuration, courseCollectionService, migrationReportCollectionService, provider,
                            migratedStatusList, cosmosClient);

                        feCourseReportEntryCount++;
                        break;

                    case ProviderType.Apprenticeship:
                        await GenerateApprenticeshipReport(
                            configuration, apprenticeshipCollectionService, migrationReportCollectionService, provider,
                            migratedStatusList, cosmosClient);

                        apprenticeshipsReportEntryCount++;
                        break;

                    case ProviderType.FE:
                        await GenerateFECourseReport(
                            configuration, courseCollectionService, migrationReportCollectionService, provider,
                            migratedStatusList, cosmosClient);

                        feCourseReportEntryCount++;
                        break;

                    case ProviderType.Undefined:
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }

                    migrationLog.AppendLine($"COMPLETED : Report for provider {provider.UnitedKingdomProviderReferenceNumber}");
                }
                catch (Exception ex)
                {
                    migrationLog.AppendLine($"Error creating report for {ukprn}. {ex.GetBaseException().Message}");
                    log.LogError(ex, $"Error creating report for {ukprn}.");
                }
            }

            stopWatch.Stop();
            migrationLog.AppendLine("----------------------------------------------------------------");
            migrationLog.AppendLine($"Completed Migration Report generation in {stopWatch.Elapsed.TotalMinutes} minutes.");
            migrationLog.AppendLine($"Course Report Entries :  {feCourseReportEntryCount} for {migratedProvidersCount} migrated providers.");
            migrationLog.AppendLine($"Apps Report Entries :  {apprenticeshipsReportEntryCount} for {migratedProvidersCount} migrated providers");
            migrationLog.AppendLine("----------------------------------------------------------------");
            log.LogDebug(migrationLog.ToString());

            await blobHelper.UploadFile(
                blobContainer,
                $"MigrationReport_LogFile-{DateTime.Now:dd-MM-yy HHmm}.txt",
                GetResultAsByteArray(migrationLog));

            log.LogInformation($"Completed Migration Report generation. {feCourseReportEntryCount + apprenticeshipsReportEntryCount} records processed.");
        }
        public static async Task Run(
            string input,  // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory)
        {
            var cosmosDbClient             = cosmosDbHelper.GetClient();
            var databaseId                 = configuration["CosmosDbSettings:DatabaseId"];
            var apprenticeshipCollectionId = "apprenticeship";
            var whitelistFileName          = "ProviderWhiteList.txt";
            var blobContainer              = configuration["BlobStorageSettings:Container"];
            var whitelist = await GetProviderWhiteList();

            var logFileName       = $"SetRadiusToTen-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            var apprenticehipsUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var logger            = loggerFactory.CreateLogger(typeof(SetApprenticeshipLocationRadiusToTen));

            using (var logStream = new MemoryStream())
                using (var logStreamWriter = new StreamWriter(logStream))
                    using (var logCsvWriter = new CsvWriter(logStreamWriter, CultureInfo.InvariantCulture))
                    {
                        logCsvWriter.WriteField("ApprenticeshoId");
                        logCsvWriter.WriteField("ApprenticeshipLocationId");
                        logCsvWriter.NextRecord();

                        try
                        {
                            foreach (var ukprn in whitelist)
                            {
                                string continuation = null;
                                do
                                {
                                    var feedOptions = new FeedOptions()
                                    {
                                        RequestContinuation = continuation,
                                        PartitionKey        = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                                    };
                                    var queryResponse = await cosmosDbClient.CreateDocumentQuery <Apprenticeship>(apprenticehipsUri, feedOptions)
                                                        .Where(p => p.ProviderUKPRN == ukprn)
                                                        .AsDocumentQuery()
                                                        .ExecuteNextAsync <Apprenticeship>();

                                    foreach (var doc in queryResponse)
                                    {
                                        foreach (var loc in doc.ApprenticeshipLocations)
                                        {
                                            if (loc.Radius == 10)
                                            {
                                                loc.Radius    = 30;
                                                loc.UpdatedBy = nameof(SetApprenticeshipLocationRadiusToTen);

                                                logCsvWriter.WriteField(doc.id);
                                                logCsvWriter.WriteField(loc.Id);
                                                logCsvWriter.NextRecord();
                                            }
                                        }

                                        if (doc.ApprenticeshipLocations.Any(x => x.UpdatedBy == nameof(SetApprenticeshipLocationRadiusToTen)))
                                        {
                                            doc.UpdatedBy = nameof(SetApprenticeshipLocationRadiusToTen);
                                            var documentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, doc.id.ToString());
                                            await cosmosDbClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions()
                                            {
                                                PartitionKey = new Microsoft.Azure.Documents.PartitionKey(ukprn)
                                            });
                                        }
                                    }
                                    continuation = queryResponse.ResponseContinuation;
                                } while (continuation != null);
                            }
                        } catch (Exception e)
                        {
                            logger.LogError(e.Message);
                        }

                        // Upload log CSV to blob storage
                        {
                            logStreamWriter.Flush();

                            logStream.Seek(0L, SeekOrigin.Begin);

                            var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(logFileName);
                            await blob.UploadFromStreamAsync(logStream);
                        }
                    }

            async Task <ISet <int> > GetProviderWhiteList()
            {
                var blob = blobHelper.GetBlobContainer(blobContainer).GetBlockBlobReference(whitelistFileName);

                var ms = new MemoryStream();
                await blob.DownloadToStreamAsync(ms);

                ms.Seek(0L, SeekOrigin.Begin);

                var    results = new HashSet <int>();
                string line;

                using (var reader = new StreamReader(ms))
                {
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (string.IsNullOrEmpty(line))
                        {
                            continue;
                        }

                        var ukprn = int.Parse(line);
                        results.Add(ukprn);
                    }
                }
                return(results);
            }
        }
Пример #28
0
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper
            )
        {
            var venuesCollectionId = configuration["CosmosDbCollectionSettings:VenuesCollectionId"];
            var connectionString   = configuration.GetConnectionString("TribalRestore");
            var blobContainer      = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var whiteListProviders = await GetProviderWhiteList();

            var          result              = new List <ResultMessage>();
            var          venueList           = new List <Venue>();
            var          venueExportFileName = $"VenueExport-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            const string WHITE_LIST_FILE     = "ProviderWhiteList.txt";
            var          ukprnCache          = new List <int>();
            var          databaseId          = configuration["CosmosDbSettings:DatabaseId"];
            //update or insert records
            var _cosmosClient = cosmosDbHelper.GetClient();

            using (var sqlConnection = new SqlConnection(connectionString))
            {
                using (var command = sqlConnection.CreateCommand())
                {
                    command.CommandType = CommandType.Text;
                    command.CommandText = @"
                                            DECLARE @Venues TABLE
                                            (
		                                            VenueId INT NOT NULL,
		                                            ProviderId INT NOT NULL,
		                                            ProviderOwnVenueRef NVARCHAR(255)  NULL,
		                                            VenueName NVARCHAR(255) NOT NULL,
		                                            Email NVARCHAR(255) NULL,
		                                            Website NVARCHAR(255) NULL,
		                                            Fax NVARCHAR(35) NULL,
		                                            Facilities NVARCHAR(2000),
		                                            RecordStatusId INT NOT NULL,
		                                            CreatedByUserId NVARCHAR(128) NOT NULL,
		                                            CreatedDateTimeUtc DATETIME NOT NULL, 
		                                            ModifiedByUserId NVARCHAR(128) NULL,
		                                            ModifiedDateTimeUtc DATETIME NULL,
		                                            AddressId INT,
		                                            Telephone NVARCHAR(30) NULL,
		                                            BulkUploadVenueId NVARCHAR(255) NULL,
		                                            UKPRN INT NOT NULL,
		                                            AddressLine1 NVARCHAR(110) NULL,
		                                            AddressLine2 NVARCHAR(100) NULL,
		                                            County NVARCHAR(75) NULL,
		                                            Latitude Decimal(9,6) NULL,
		                                            Longitude Decimal(9,6) NULL,
		                                            Postcode NVARCHAR(30) NULL,
		                                            Town NVARCHAR(75) NULL,
		                                            source INT NOT NULL,
		                                            LocationID INT NULL
                                            )
                                            INSERT INTO @Venues
                                            (
		                                            VenueId,
		                                            ProviderId,
		                                            ProviderOwnVenueRef,
		                                            VenueName,
		                                            Email,
		                                            Website,
		                                            Fax,
		                                            Facilities,
		                                            RecordStatusId,
		                                            CreatedByUserId,
		                                            CreatedDateTimeUtc, 
		                                            ModifiedByUserId,
		                                            ModifiedDateTimeUtc,
		                                            AddressId,
		                                            Telephone,
		                                            BulkUploadVenueId,
		                                            UKPRN,
		                                            AddressLine1,
		                                            AddressLine2,
		                                            County,
		                                            Latitude,
		                                            Longitude,
		                                            Postcode,
		                                            Town,
		                                            source,
		                                            LocationID
                                            )
                                            SELECT  distinct Ven.[VenueId],
                                                    Ven.[ProviderId],
                                                    Ven.[ProviderOwnVenueRef],
                                                    Ven.[VenueName],
                                                    Ven.[Email],
                                                    Ven.[Website],
                                                    Ven.[Fax],
                                                    Ven.[Facilities],
                                                    Ven.[RecordStatusId],
                                                    Ven.[CreatedByUserId],
                                                    Ven.[CreatedDateTimeUtc],
                                                    Ven.[ModifiedByUserId],
                                                    Ven.[ModifiedDateTimeUtc],
                                                    Ven.[AddressId],
                                                    Ven.[Telephone],
                                                    Ven.[BulkUploadVenueId],
		                                            pr.Ukprn,
                                                    Ad.AddressLine1,
                                                    ad.AddressLine2,
                                                    ad.County,
                                                    ad.[Latitude],
                                                    ad.[Longitude],
		                                            ad.Postcode,
		                                            ad.Town,
		                                            1 as [Source],
		                                            NULL as LocationId
											FROM Venue Ven
                                            INNER JOIN [Address] Ad on Ad.AddressId = Ven.AddressId
                                            INNER JOIN [Provider] pr on pr.ProviderId = ven.ProviderId
                                            WHERE Ven.RecordStatusID = 2
            
											UNION ALL 

											SELECT DISTINCT  0, 
		                                            L.[ProviderId],
		                                            l.ProviderOwnLocationRef,
		                                            L.[LocationName],
		                                            L.[Email],
		                                            L.[Website],
                                                    NULL,
                                                    NULL,
		                                            L.[RecordStatusId],
                                                    L.[CreatedByUserId],
                                                    L.[CreatedDateTimeUtc],
                                                    L.[ModifiedByUserId],
                                                    L.[ModifiedDateTimeUtc],
                                                    L.[AddressId],
                                                    L.[Telephone],
                                                    NULL,
		                                            pr.Ukprn,
                                                    Ad.AddressLine1,
                                                    ad.AddressLine2,
		                                            ad.County,
		                                            ad.[Latitude],
                                                    ad.[Longitude],
                                                    ad.Postcode,
		                                            ad.Town,
		                                            2 as [Source],
		                                            L.LocationId as LocationId
                                            FROM Location l
                                            INNER JOIN Address ad on ad.AddressId = l.AddressId
                                            INNER JOIN Provider pr on pr.ProviderId = l.ProviderId
                                            WHERE l.RecordStatusId = 2

                                            SELECT * FROM @Venues
                                            ";

                    try
                    {
                        //Open connection.
                        sqlConnection.Open();

                        using (SqlDataReader dataReader = command.ExecuteReader())
                        {
                            while (dataReader.Read())
                            {
                                //Read venue
                                venueList.Add(Venue.FromDataReader(dataReader));
                            }

                            // Close the SqlDataReader.
                            dataReader.Close();
                        }

                        sqlConnection.Close();
                    }
                    catch (Exception ex)
                    {
                        log.LogError("An error occured migratiing Venues", ex);
                    }
                }
            }


            foreach (var item in venueList)
            {
                try
                {
                    if (Validate(item))
                    {
                        var cosmosVenue = await GetVenue(item.Source, item.VenueId, item.LocationID, item.UKPRN);

                        if (cosmosVenue != null)
                        {
                            //var s = UriFactory.CreateDocumentUri(databaseId, venuesCollectionId, cosmosVenue.ID.ToString());

                            if (cosmosVenue.UKPRN != item.UKPRN)
                            {
                                continue;
                            }

                            Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, venuesCollectionId);
                            var editedVenue   = new Dfc.CourseDirectory.Models.Models.Venues.Venue()
                            {
                                ID               = cosmosVenue.ID,
                                UKPRN            = item.UKPRN,
                                VenueName        = item.VenueName,
                                Address1         = item.Address.Address1,
                                Address2         = item.Address.Address2,
                                Town             = item.Address.Town,
                                County           = item.Address.County,
                                PostCode         = item.Address.Postcode,
                                Latitude         = item.Address.Latitude,
                                Longitude        = item.Address.Longitude,
                                Status           = MapVenueStatus(item),
                                UpdatedBy        = "VenueMigrator",
                                DateUpdated      = DateTime.Now,
                                VenueID          = item.VenueId,
                                ProviderID       = item.ProviderId,
                                ProvVenueID      = item.ProviderOwnVenueRef,
                                Email            = item.Email,
                                Website          = item.Website,
                                Telephone        = item.Telephone,
                                CreatedBy        = "VenueMigrator",
                                CreatedDate      = DateTime.Now,
                                LocationId       = item.LocationID,
                                TribalLocationId = item.LocationID
                            };
                            await _cosmosClient.UpsertDocumentAsync(collectionUri, editedVenue);

                            AddResultMessage(item.UKPRN, item.VenueId, item.LocationID, "Updated Record", $"Old cosmos record LocationId:{cosmosVenue.LocationId}, VenueId: {cosmosVenue.VenueID}");
                        }
                        else
                        {
                            var newVenue = new Dfc.CourseDirectory.Models.Models.Venues.Venue()
                            {
                                UKPRN            = item.UKPRN,
                                VenueName        = item.VenueName,
                                Address1         = item.Address.Address1,
                                Address2         = item.Address.Address2,
                                Town             = item.Address.Town,
                                County           = item.Address.County,
                                PostCode         = item.Address.Postcode,
                                Latitude         = item.Address.Latitude,
                                Longitude        = item.Address.Longitude,
                                Status           = MapVenueStatus(item),
                                UpdatedBy        = item.CreatedByUserId,
                                DateUpdated      = item.CreatedDateTimeUtc,
                                VenueID          = item.VenueId,
                                ProviderID       = item.ProviderId,
                                ProvVenueID      = item.ProviderOwnVenueRef,
                                Email            = item.Email,
                                Website          = item.Website,
                                Telephone        = item.Telephone,
                                CreatedDate      = DateTime.Now,
                                CreatedBy        = "VenueMigrator",
                                LocationId       = item.LocationID,
                                TribalLocationId = item.LocationID
                            };
                            await cosmosDbHelper.CreateDocumentAsync(_cosmosClient, venuesCollectionId, newVenue);

                            //Log that successfully inserted venue
                            AddResultMessage(item.UKPRN, item.VenueId, item.LocationID, "Inserted Venue");
                        }
                    }
                }
                catch (Exception ex)
                {
                    string errorMessage = $"An error occured while updating cosmos record for venue {item.VenueId}. {ex.Message}";
                    log.LogError(errorMessage, ex);
                    AddResultMessage(item.UKPRN, item.VenueId, item.LocationID, errorMessage);
                }
            }


            var resultsObjBytes = GetResultAsByteArray(result);

            await WriteResultsToBlobStorage(resultsObjBytes);

            //log completion
            log.LogInformation("Migrating Venues Complete");


            async Task <Dfc.CourseDirectory.Models.Models.Venues.Venue> GetVenue(VenueSource source, int?venueId, int?locationId, int ukprn)
            {
                switch (source)
                {
                case VenueSource.Venue:
                    return(await venueCollectionService.GetDocumentByVenueId(venueId.Value));

                case VenueSource.Location:
                    return(await venueCollectionService.GetDocumentByLocationId(locationId.Value, ukprn));

                default: return(null);
                }
            }

            CourseDirectory.Models.Models.Venues.VenueStatus MapVenueStatus(Venue venue)
            {
                //ignore record status for venues that do not have a postcode & migrate it over
                //as pending.
                if (string.IsNullOrEmpty(venue.Address?.Postcode))
                {
                    return(CourseDirectory.Models.Models.Venues.VenueStatus.Pending);
                }

                switch (venue.RecordStatusId)
                {
                case TribalRecordStatus.Pending: return(CourseDirectory.Models.Models.Venues.VenueStatus.Pending);

                case TribalRecordStatus.Live: return(CourseDirectory.Models.Models.Venues.VenueStatus.Live);

                case TribalRecordStatus.Archived: return(CourseDirectory.Models.Models.Venues.VenueStatus.Archived);

                case TribalRecordStatus.Deleted: return(CourseDirectory.Models.Models.Venues.VenueStatus.Deleted);

                default: throw new Exception("$Unable to map recordStatus to VenueStatus");
                }
            }

            byte[] GetResultAsByteArray(IList <ResultMessage> ob)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <ResultMessage>(ob);
                        }
                    return(memoryStream.ToArray());
                }
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, venueExportFileName, data);
            }

            async Task <IList <int> > GetProviderWhiteList()
            {
                var list      = new List <int>();
                var whiteList = await blobhelper.ReadFileAsync(blobContainer, WHITE_LIST_FILE);

                if (!string.IsNullOrEmpty(whiteList))
                {
                    var lines = whiteList.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
                    foreach (string line in lines)
                    {
                        if (int.TryParse(line, out int id))
                        {
                            list.Add(id);
                        }
                    }
                }
                return(list);
            }

            void AddResultMessage(int ukprn, int venueId, int?locationId, string status, string message = "")
            {
                var validateResult = new ResultMessage()
                {
                    UKPRN = ukprn, VenueId = venueId, LocationId = locationId, Status = status, Message = message
                };

                result.Add(validateResult);
            }

            bool Validate(Venue item)
            {
                //are providers on list of whitelisted providers file
                if (!whiteListProviders.Any(x => x == item.UKPRN))
                {
                    AddResultMessage(item.UKPRN, item.VenueId, item.LocationID, "Failed", $"Provider {item.ProviderId} not on whitelist, ukprn {item.UKPRN}");
                    return(false);
                }

                if (!item.Address.Latitude.HasValue || !item.Address.Longitude.HasValue)
                {
                    AddResultMessage(item.UKPRN, item.VenueId, item.LocationID, "Skiped", $"Skipped Location because Lat/Long are missing,  {item.ProviderId} not on whitelist, ukprn {item.UKPRN}");
                    return(false);
                }
                ////check to see if a record is already held for ukprn
                //if (!ukprnCache.Contains(item.UKPRN))
                //{
                //    var cosmosProvider = await providerCollectionService.ProviderExists(item.UKPRN);
                //    if (!cosmosProvider)
                //    {
                //        AddResultMessage(item.VenueId, item.LocationID, "Failed", "Unknown UKPRN");
                //        return false;
                //    }
                //    else
                //    {
                //        //provider exists - add to cache
                //        ukprnCache.Add(item.UKPRN);
                //    }
                //}

                return(true);
            }
        }
Пример #29
0
        public static async Task Run(
            string input,                              // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            [Inject] IConfigurationRoot configuration,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobHelper,
            [Inject] ILoggerFactory loggerFactory,
            [Inject] IBlobStorageHelper blobhelper)
        {
            var blobContainer               = configuration["BlobStorageSettings:Container"];
            var outputContainer             = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var databaseId                  = configuration["CosmosDbSettings:DatabaseId"];
            var apprenticeshipCollectionId  = "apprenticeship";
            var documentClient              = cosmosDbHelper.GetClient();
            var apprenticeshipCollectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
            var logger    = loggerFactory.CreateLogger(typeof(ArchiveOldMigratedApprenticeships));
            int count     = 0;
            var updatedBy = "ArchiveOldMigratedApprenticeships";


            string continuation = null;

            do
            {
                var feedOptions = new FeedOptions()
                {
                    RequestContinuation       = continuation,
                    EnableCrossPartitionQuery = true
                };

                //try/catch required as there are Apprenticeship records that are not valid (venueId is null in cosmos).
                try
                {
                    var queryResponse = await documentClient.CreateDocumentQuery <Apprenticeship>(apprenticeshipCollectionUri, feedOptions)
                                        .Where(p => p.CreatedBy == "DFC – Apprenticeship Migration Tool")
                                        .AsDocumentQuery()
                                        .ExecuteNextAsync <Apprenticeship>();

                    foreach (var doc in queryResponse)
                    {
                        if (doc.RecordStatus.HasFlag(CourseDirectory.Models.Enums.RecordStatus.MigrationPending) || doc.RecordStatus == CourseDirectory.Models.Enums.RecordStatus.Live)
                        {
                            //mark every location as arhived
                            foreach (var loc in doc.ApprenticeshipLocations)
                            {
                                loc.RecordStatus = CourseDirectory.Models.Enums.RecordStatus.Archived;
                                loc.UpdatedBy    = updatedBy;
                            }
                            doc.UpdatedBy = updatedBy;

                            var documentLink = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, doc.id.ToString());
                            await documentClient.ReplaceDocumentAsync(documentLink, doc, new RequestOptions());

                            count++;
                        }
                    }
                    continuation = queryResponse.ResponseContinuation;
                }
                catch (Exception)
                {
                    continuation = null;
                }
            }while (continuation != null);

            logger.LogInformation($"Archived {count} Apprenticeships");
            Console.WriteLine($"Archived {count} Apprenticeships");
        }
        public static async Task Run(
            string input,          // Work around https://github.com/Azure/azure-functions-vs-build-sdk/issues/168
            ILogger log,
            [Inject] IConfigurationRoot configuration,
            [Inject] IProviderCollectionService providerCollectionService,
            [Inject] ICosmosDbHelper cosmosDbHelper,
            [Inject] IBlobStorageHelper blobhelper,
            [Inject] IApprenticeReferenceDataService apprenticeReferenceDataService,
            [Inject] IApprenticeshipServiceWrapper apprenticeshipService,
            [Inject] IVenueCollectionService venueCollectionService,
            [Inject] IOnspdService onspdService

            )
        {
            var apprenticeshipCollectionId = configuration["CosmosDbCollectionSettings:ApprenticeshipCollectionId"];
            var connectionString           = configuration.GetConnectionString("TribalRestore");
            var blobContainer      = blobhelper.GetBlobContainer(configuration["BlobStorageSettings:Container"]);
            var whiteListProviders = await GetProviderWhiteList();

            var           result = new List <ApprenticeshipResultMessage>();
            var           venueExportFileName = $"ApprenticeshipExport-{DateTime.Now.ToString("dd-MM-yy HHmm")}";
            const string  WHITE_LIST_FILE     = "ProviderWhiteList-Apprenticeships.txt";
            var           ukprnCache          = new List <int>();
            var           databaseId          = configuration["CosmosDbSettings:DatabaseId"];
            var           apprenticeshipList  = new List <ApprenticeshipResult>();
            var           createdBy           = "ApprenticeshipMigrator";
            var           createdDate         = DateTime.Now;
            SemaphoreSlim semaphore           = new SemaphoreSlim(5);
            var           client = cosmosDbHelper.GetClient();

            var apprenticeshipSQL          = @"SELECT  a.ApprenticeshipId,
	                                           p.ProviderId,
	                                           a.FrameworkCode,
	                                           a.ProgType,
	                                           a.PathwayCode,
	                                           a.StandardCode,
	                                           a.[Version],
	                                           a.MarketingInformation,
	                                           a.[Url],
	                                           a.ContactEmail,
	                                           a.ContactTelephone,
	                                           a.ContactWebsite,
	                                           a.RecordStatusId,
	                                           a.CreatedByUserId,
	                                           a.CreatedDateTimeUtc,
	                                           p.Ukprn,
											   coalesce(s.StandardName,f.NasTitle) as [ApprenticeshipTitle],
                                               s.NotionalEndLevel
                                        FROM Apprenticeship a
                                        INNER JOIN Provider p on p.ProviderId = a.ProviderId
										LEFT JOIN [Standard] s on (s.StandardCode = a.StandardCode and s.Version = a.Version)
										LEFT jOIN [Framework] f on (f.FrameworkCode = a.FrameworkCode AND f.PathwayCode = a.PathwayCode AND f.ProgType = a.ProgType)
                                        WHERE a.recordStatusId=2
                                        ORDER BY ProviderId
                                        ";
            var apprenticeshipLocationsSQL = @"SELECT al.ApprenticeshipId,
	                                           al.ApprenticeshipLocationId,
	                                           l.LocationId,
	                                           a.AddressId,
	                                           a.AddressLine1,
	                                           a.AddressLine2,
	                                           a.County,
	                                           a.Postcode,
                                               a.Town,
	                                           a.Longitude,
	                                           a.Latitude,
	                                           l.Website,
	                                           l.Email,
	                                           als.CSV as DeliveryModeStr,
                                               l.Telephone,
	                                           l.LocationName,
	                                           p.ProviderId,
	                                           p.Ukprn,
                                               al.Radius
                                        FROM ApprenticeshipLocation al
                                        INNER JOIN Location l on l.LocationId = al.LocationId
                                        INNER JOIN Provider p on p.ProviderId = l.ProviderId
                                        INNER JOIN Address a ON a.AddressId = l.AddressId
                                        CROSS APPLY (SELECT STRING_AGG(DeliveryModeId,',') as CSV, 
					                                        aldm.ApprenticeshipLocationId
			                                         FROM ApprenticeshipLocationDeliveryMode aldm
		                                             WHERE ApprenticeshipLocationId = al.ApprenticeshipLocationId
			                                         GROUP BY aldm.ApprenticeshipLocationId
			                                         ) als
                                        WHERE al.RecordStatusId = 2 and 
                                              al.ApprenticeshipId = @ApprenticeshipId
                                        ORDER BY ApprenticeshipId,ApprenticeshipLocationId";

            try
            {
                using (var conn1 = new SqlConnection(connectionString))
                    using (var apprenticeshipscmd = conn1.CreateCommand())
                    {
                        await conn1.OpenAsync();

                        apprenticeshipscmd.CommandText = apprenticeshipSQL;

                        using (var apprenticeshipReader = apprenticeshipscmd.ExecuteReader())
                        {
                            while (await apprenticeshipReader.ReadAsync())
                            {
                                apprenticeshipList.Add(ApprenticeshipResult.FromDataReader(apprenticeshipReader));
                            }
                        }
                    }
            }
            catch (Exception e)
            {
                AddResultMessage(0, 0, "Failed", null, e.Message);
                log.LogError("Error occured Migrating Apprenticeships", e.Message);
            }

            await Task.WhenAll(apprenticeshipList.Select(async apprenticeship =>
            {
                var apprenticeshipErrors  = new List <string>(); //Errors Here cause apprenticeships to go into pending
                var apprenticeshipWarning = new List <string>(); //informational messages
                await semaphore.WaitAsync();
                try
                {
                    if (IsOnWhiteList(apprenticeship.UKPRN))
                    {
                        apprenticeshipErrors = new List <string>();
                        //get relevant info
                        var exisitingApprenticeship = await GetExistingApprenticeship(apprenticeship);
                        var referenceDataFramework  = await GetReferenceDataFramework(apprenticeship);
                        var referenceDataStandard   = await GetReferenceDataStandard(apprenticeship);
                        var locations      = await GetLocations(apprenticeship);
                        var cosmosVenues   = await GetCosmosVenues(locations);
                        var cosmosProvider = await GetProvider(apprenticeship);

                        //map objects for creating cosmos record
                        var locs           = MapLocations(locations, cosmosVenues);
                        var id             = exisitingApprenticeship?.id.ToString() ?? Guid.NewGuid().ToString();
                        var apprenticeType = MapApprenticeshipType(apprenticeship);

                        //check to see if framework code/standard code is valid
                        VerifiyIfStandardOrFramework(apprenticeship, referenceDataFramework, referenceDataStandard);

                        var mappedStatus         = MapApprenticeshipRecordStatus(locs);
                        var mappedApprenticeship = MapApprenticeship(locs,
                                                                     id,
                                                                     apprenticeship,
                                                                     apprenticeType,
                                                                     mappedStatus,
                                                                     referenceDataFramework?.Id.ToString(),
                                                                     referenceDataStandard?.id.ToString(),
                                                                     cosmosProvider?.id.ToString());

                        //insert record into cosmos
                        await CreateOrUpdateApprenticeshipRecord(mappedApprenticeship);

                        //log message to output
                        AddResultMessage(apprenticeship.ApprenticeshipID,
                                         apprenticeship.UKPRN,
                                         Enum.GetName(typeof(RecordStatus),
                                                      mappedApprenticeship.RecordStatus),
                                         mappedApprenticeship.ApprenticeshipTitle,
                                         string.Join("\n", apprenticeshipErrors),
                                         string.Join("\n", apprenticeshipWarning));
                    }
                    else
                    {
                        AddResultMessage(apprenticeship.ApprenticeshipID, apprenticeship.UKPRN, "Skipped", null, $"PRN {apprenticeship.UKPRN} not whitelisted");
                    }
                }
                catch (Exception e)
                {
                    AddResultMessage(apprenticeship.ApprenticeshipID, apprenticeship.UKPRN, "Failed", null, $"Exception occured creating record - {e.Message}");
                    log.LogError("Error occurred creating or updating apprenticeship record!", e);
                }
                finally
                {
                    semaphore.Release();
                }

                void VerifiyIfStandardOrFramework(ApprenticeshipResult tribalRecord, ReferenceDataFramework refDataFramework, ReferenceDateStandard refDataStandard)
                {
                    if (refDataFramework == null && refDataStandard == null)
                    {
                        apprenticeshipWarning.Add($"Standard/Framework code does not exist - framework code {tribalRecord.FrameworkCode}, pathway code: {tribalRecord.PathWayCode}, standard code: {tribalRecord.StandardCode}, version: {tribalRecord.Version}");
                    }
                }

                ApprenticeshipDTO MapApprenticeship(IList <ApprenticeshipLocationDTO> locs, string id, ApprenticeshipResult tribalRecord, ApprenticeshipType apprenticeshipTye,
                                                    RecordStatus recordStatus, string frameworkId, string standardId, string providerId)
                {
                    var cosmosApprenticeship = new ApprenticeshipDTO()
                    {
                        id = id,
                        ApprenticeshipId     = tribalRecord.ApprenticeshipID,
                        ApprenticeshipTitle  = tribalRecord.ApprenticeshipTitle,
                        ProviderId           = providerId,
                        PathWayCode          = tribalRecord.PathWayCode,
                        ProgType             = tribalRecord.ProgType,
                        ProviderUKPRN        = tribalRecord.UKPRN,
                        FrameworkId          = frameworkId,
                        StandardId           = standardId,
                        FrameworkCode        = tribalRecord.FrameworkCode,
                        StandardCode         = tribalRecord.StandardCode,
                        Version              = tribalRecord.Version,
                        MarketingInformation = tribalRecord.MarketingInformation,
                        Url = tribalRecord.Url,
                        ContactTelephone        = tribalRecord.ContactTelephone,
                        ContactEmail            = tribalRecord.ContactEmail,
                        ContactWebsite          = tribalRecord.ContactWebsite,
                        CreatedBy               = createdBy,
                        CreatedDate             = createdDate,
                        NotionalNVQLevelv2      = tribalRecord.NotionalEndLevel,
                        ApprenticeshipLocations = locs,
                        ApprenticeshipType      = apprenticeshipTye,
                        RecordStatus            = recordStatus
                    };
                    return(cosmosApprenticeship);
                }

                async Task <IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> > GetCosmosVenues(IList <ApprenticeshipLocationResult> locations)
                {
                    IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> lst = new List <Dfc.CourseDirectory.Models.Models.Venues.Venue>();
                    foreach (var s in locations)
                    {
                        var venue = await venueCollectionService.GetDocumentByLocationId(s.LocationId, s.UKPRN);
                        if (venue != null)
                        {
                            lst.Add(venue);
                        }
                    }
                    return(lst);
                }

                async Task <Provider> GetProvider(ApprenticeshipResult item)
                {
                    return(await providerCollectionService.GetDocumentByUkprn(item.UKPRN));
                }

                async Task <List <ApprenticeshipLocationResult> > GetLocations(ApprenticeshipResult item)
                {
                    using (var sqlConnection = new SqlConnection(connectionString))
                    {
                        var lst = await sqlConnection.QueryAsync <ApprenticeshipLocationResult>(apprenticeshipLocationsSQL, new { apprenticeshipId = item.ApprenticeshipID }, commandType: CommandType.Text);
                        return(lst.ToList());
                    }
                }

                async Task <ReferenceDateStandard> GetReferenceDataStandard(ApprenticeshipResult item)
                {
                    var app = await apprenticeReferenceDataService.GetStandardById(item.StandardCode ?? 0, item.Version ?? 0);
                    return(app?.Value?.Value);
                }

                async Task <ReferenceDataFramework> GetReferenceDataFramework(ApprenticeshipResult item)
                {
                    //checks for framework apprenticeship
                    var app = await apprenticeReferenceDataService.GetFrameworkByCode(item.FrameworkCode ?? 0, item.ProgType ?? 0, item.PathWayCode ?? 0);
                    return(app?.Value?.Value);
                }

                async Task <Apprenticeship> GetExistingApprenticeship(ApprenticeshipResult item)
                {
                    //fetch existing apprenticeship row.
                    return(await apprenticeshipService.GetApprenticeshipByApprenticeshipID(item.ApprenticeshipID));
                }

                async Task CreateOrUpdateApprenticeshipRecord(ApprenticeshipDTO app)
                {
                    var s             = UriFactory.CreateDocumentUri(databaseId, apprenticeshipCollectionId, app.id);
                    Uri collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, apprenticeshipCollectionId);
                    var res           = await client.UpsertDocumentAsync(collectionUri, app);
                }

                RecordStatus MapApprenticeshipRecordStatus(IList <ApprenticeshipLocationDTO> mappedLocation)
                {
                    //if there are any errors with apprenticeshipREcord, set record to migration pending.
                    if (apprenticeshipErrors.Any())
                    {
                        return(RecordStatus.MigrationPending);
                    }
                    else
                    {
                        return(RecordStatus.Live);
                    }
                }

                //Taken entirely from previous migration logic.
                ApprenticeshipLocationType GetApprenticeshipLocationType(ApprenticeshipLocationResult lo)
                {
                    var deliveryModes = lo.DeliveryModes;
                    if ((deliveryModes.Contains(1) && !deliveryModes.Contains(2) && deliveryModes.Contains(3)) ||
                        (deliveryModes.Contains(1) && deliveryModes.Contains(2) && !deliveryModes.Contains(3)) ||
                        (deliveryModes.Contains(1) && deliveryModes.Contains(2) && deliveryModes.Contains(3)))
                    {
                        return(ApprenticeshipLocationType.ClassroomBasedAndEmployerBased);
                    }
                    else if ((!deliveryModes.Contains(1) && !deliveryModes.Contains(2) && deliveryModes.Contains(3)) ||
                             (!deliveryModes.Contains(1) && deliveryModes.Contains(2) && !deliveryModes.Contains(3)) ||
                             (!deliveryModes.Contains(1) && deliveryModes.Contains(2) && deliveryModes.Contains(3)))
                    {
                        return(ApprenticeshipLocationType.ClassroomBased);
                    }
                    else if (deliveryModes.Contains(1) && !deliveryModes.Contains(2) && !deliveryModes.Contains(3))
                    {
                        return(ApprenticeshipLocationType.EmployerBased);
                    }
                    else
                    {
                        return(ApprenticeshipLocationType.Undefined);
                    }
                }

                ApprenticeshipType MapApprenticeshipType(ApprenticeshipResult tribalRecord)
                {
                    if (tribalRecord.StandardCode.HasValue)
                    {
                        return(ApprenticeshipType.StandardCode);
                    }
                    else if (tribalRecord.FrameworkCode.HasValue)
                    {
                        return(ApprenticeshipType.FrameworkCode);
                    }
                    else
                    {
                        apprenticeshipWarning.Add($"ApprenticeshipId: {tribalRecord.ApprenticeshipID} has undefined apprenticeshipType");
                        return(ApprenticeshipType.Undefined);
                    }
                }

                IList <ApprenticeshipLocationDTO> MapLocations(IList <ApprenticeshipLocationResult> locations, IList <Dfc.CourseDirectory.Models.Models.Venues.Venue> venues)
                {
                    var locationBasedApprenticeshipLocation = new List <ApprenticeshipLocationDTO>();
                    var regionBasedApprenticeshipLocation   = new List <ApprenticeshipLocationDTO>();

                    //no need to proceed
                    if (locations == null)
                    {
                        return(null);
                    }


                    //employer based apprenticeships - group all locations into regions/subregions
                    foreach (var location in locations)
                    {
                        var type = GetApprenticeshipLocationType(location);
                        if (type == ApprenticeshipLocationType.EmployerBased)
                        {
                            var allRegionsWithSubRegions = new SelectRegionModel();
                            var onspdRegionSubregion     = onspdService.GetOnspdData(new OnspdSearchCriteria(location.Postcode));
                            if (onspdRegionSubregion.IsFailure)
                            {
                                apprenticeshipWarning.Add($"LocationId: {location.LocationId} - Querying onspd failed - {onspdRegionSubregion.Error}");
                                continue;
                            }
                            else if (!onspdRegionSubregion.HasValue)
                            {
                                apprenticeshipWarning.Add($"Location:{location.LocationId} - Did not find a record for postcode: {location.Postcode}");
                                continue;
                            }

                            var selectedSubRegion = allRegionsWithSubRegions.RegionItems.SelectMany(sr => sr.SubRegion.Where(sb =>
                                                                                                                             sb.SubRegionName == onspdRegionSubregion.Value.Value.LocalAuthority ||
                                                                                                                             sb.SubRegionName == onspdRegionSubregion.Value.Value.County ||
                                                                                                                             onspdRegionSubregion.Value.Value.LocalAuthority.Contains(sb.SubRegionName)
                                                                                                                             )).FirstOrDefault();

                            if (selectedSubRegion == null)
                            {
                                apprenticeshipWarning.Add($"Location:{location.LocationId} Unable to match region with ons data api, location skipped");
                                continue;
                            }
                            else
                            {
                                var appLocation = new ApprenticeshipLocationDTO()
                                {
                                    Id            = Guid.NewGuid().ToString(),
                                    VenueId       = Guid.Empty.ToString(),
                                    TribalId      = location.ApprenticeshipLocationId,
                                    DeliveryModes = location.DeliveryModes,
                                    LocationId    = selectedSubRegion.ApiLocationId,
                                    Name          = location.LocationName,
                                    ProviderId    = location.ProviderId,
                                    ProviderUKPRN = location.UKPRN,
                                    Radius        = location.Radius,
                                    ApprenticeshipLocationType = type,
                                    LocationType   = LocationType.SubRegion,
                                    LocationGuidId = null,
                                    Regions        = new List <string> {
                                        selectedSubRegion.Id
                                    },
                                    RecordStatus = VenueStatus.Live,
                                    CreatedBy    = createdBy,
                                    CreatedDate  = createdDate,
                                    UpdatedBy    = createdBy,
                                    UpdatedDate  = createdDate
                                };

                                //region based apprenticeships
                                regionBasedApprenticeshipLocation.Add(appLocation);
                            }
                        }
                        else if (type == ApprenticeshipLocationType.ClassroomBased || type == ApprenticeshipLocationType.ClassroomBasedAndEmployerBased)
                        {
                            //venue based (location based apprenticeships)
                            var cosmosVenueItem = venues.FirstOrDefault(x => x.LocationId == location.LocationId);
                            var status          = default(VenueStatus);

                            //set status be that of what the venue status is, otherwise if venue is not found
                            //set status to pending.
                            if (cosmosVenueItem != null)
                            {
                                status = cosmosVenueItem.Status;
                            }
                            else
                            {
                                apprenticeshipWarning.Add($"LocationId: {location.LocationId} did not find a venue in cosmos, record marked as pending");
                                continue;
                            }

                            var appLocation = new ApprenticeshipLocationDTO()
                            {
                                Id       = Guid.NewGuid().ToString(),
                                VenueId  = Guid.Empty.ToString(),
                                TribalId = location.ApprenticeshipLocationId,
                                Address  = new Dfc.CourseDirectory.Models.Models.Apprenticeships.Address()
                                {
                                    Address1  = cosmosVenueItem?.Address1,
                                    Address2  = cosmosVenueItem?.Address2,
                                    County    = cosmosVenueItem?.County,
                                    Email     = cosmosVenueItem?.Email,
                                    Website   = cosmosVenueItem?.Website,
                                    Longitude = cosmosVenueItem?.Longitude,
                                    Latitude  = cosmosVenueItem?.Latitude,
                                    Postcode  = cosmosVenueItem?.PostCode,
                                    Town      = cosmosVenueItem?.Town,
                                    Phone     = cosmosVenueItem?.Telephone
                                },
                                DeliveryModes = location.DeliveryModes,
                                LocationId    = location.LocationId,
                                Name          = location.LocationName,
                                ProviderId    = location.ProviderId,
                                ProviderUKPRN = location.UKPRN,
                                Radius        = location.Radius,
                                ApprenticeshipLocationType = type,
                                LocationType   = LocationType.Venue,
                                LocationGuidId = cosmosVenueItem?.ID,
                                Regions        = null,
                                RecordStatus   = status,
                                CreatedBy      = createdBy,
                                CreatedDate    = createdDate,
                                UpdatedBy      = createdBy,
                                UpdatedDate    = createdDate
                            };
                            locationBasedApprenticeshipLocation.Add(appLocation);
                        }
                        else
                        {
                            apprenticeshipWarning.Add($"LocationId: {location.LocationId} skipped as type was unknown {type}");
                            continue;
                        }
                    }

                    //add a new location with all distinct regions.
                    if (regionBasedApprenticeshipLocation.Any(x => x.RecordStatus == VenueStatus.Live))
                    {
                        var regionLocation     = regionBasedApprenticeshipLocation.FirstOrDefault(x => x.RecordStatus == VenueStatus.Live);
                        regionLocation.Regions = regionBasedApprenticeshipLocation.Where(x => x.Regions != null).SelectMany(x => x.Regions).Distinct().ToList();
                        locationBasedApprenticeshipLocation.Add(regionLocation);
                    }

                    return(locationBasedApprenticeshipLocation);
                }
            }));

            //Log Results to blob storage
            var resultsObjBytes = GetResultAsByteArray(result);

            await WriteResultsToBlobStorage(resultsObjBytes);

            //log completion
            log.LogInformation("Migrating Apprenticeships Complete");


            async Task <IList <int> > GetProviderWhiteList()
            {
                var list      = new List <int>();
                var whiteList = await blobhelper.ReadFileAsync(blobContainer, WHITE_LIST_FILE);

                if (!string.IsNullOrEmpty(whiteList))
                {
                    var lines = whiteList.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
                    foreach (string line in lines)
                    {
                        if (int.TryParse(line, out int id))
                        {
                            list.Add(id);
                        }
                    }
                }
                return(list);
            }

            async Task WriteResultsToBlobStorage(byte[] data)
            {
                await blobhelper.UploadFile(blobContainer, venueExportFileName, data);
            }

            void AddResultMessage(int apprenticeshipId, int ukprn, string status, string apprenticeshipTitle, string message = "", string warnings = "")
            {
                lock (result)
                {
                    var validateResult = new ApprenticeshipResultMessage()
                    {
                        ApprenticeshipID = apprenticeshipId, Status = status, Message = message, UKPRN = ukprn, ApprenticeshipTitle = apprenticeshipTitle, Warnings = warnings
                    };
                    result.Add(validateResult);
                }
            }

            byte[] GetResultAsByteArray(IList <ApprenticeshipResultMessage> ob)
            {
                using (var memoryStream = new System.IO.MemoryStream())
                {
                    using (var streamWriter = new System.IO.StreamWriter(memoryStream))
                        using (var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture))
                        {
                            csvWriter.WriteRecords <ApprenticeshipResultMessage>(ob);
                            csvWriter.Flush();
                        }

                    return(memoryStream.ToArray());
                }
            }

            bool IsOnWhiteList(int ukprn)
            {
                if (!whiteListProviders.Any(x => x == ukprn))
                {
                    return(false);
                }
                else
                {
                    return(true);
                }
            }
        }