using EnumsNET; using Microsoft.AspNetCore.Http; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Helpers; using Sarand.Net.Standard.Base.Utils; using Sarand.Net.Standard.Proc.Excel; using Sarand.Net.Standard.Proc.Procs; using Saxonite.Net.Core.Web.Base.Procs.Data; using Saxonite.Net.Standard.Base.App; using Saxonite.Net.Standard.Base.Procs; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Saxonite.Net.Core.Web.Admin.Procs { //======================================================================= public class SaxOrganisationUploader : SaxProcess_Admin { //------------------------------------------------------------------- // Definitions. // Process configuration. public new class Config : SaxProcess_Admin.Config { public Config(SaxSessionContext sessionContext, DbUpdateInfo dbUpdateInfo, IFormFile dataFile) : base(sessionContext, dbUpdateInfo) { this.DataFile = new UploadFileData(dataFile); } // Properties. public UploadFileData DataFile { get; } public String SheetNameOrNumber { get; set; } public uint HeaderRowPos { get; set; } public uint FirstDataRowPos { get; set; } public bool ValidateOnly { get; set; } public bool AllowCreateOrganisation { get; set; } public bool RequireOrganisationIdForUpdate { get; set; } } // Supported content types. private static HashSet validContentTypes { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) { SacsContentTypeHelper.ContentType_Excel_Legacy, /* xls */ SacsContentTypeHelper.ContentType_Excel_Open, /* xlsx */ SacsContentTypeHelper.ContentType_Excel_MacroEnabled, /* xlsm */ SacsContentTypeHelper.ContentType_Excel_BinaryMacroEnabled /* xlsb */ }; /// Allowed content types. public static List ValidContentTypes { get { return new List(validContentTypes); } } //------------------------------------------------------------------- // Organisation data profile. private enum OrganisationProperty { // Organisation. OrganisationId, OrganisationName, OrganisationType, OrganisationCategory, Sport, Sports, Region, // Address. AddressLine1, AddressLine2, AddressTown, AddressProvince, AddressPostcode, // Contact personal. ContactName, ContactRole, ContactNameRole, ContactEmail, // Contact. ContactPhone, ContactWeb, ContactMap, ContactWiki, ContactFacebook, ContactInstagram, ContactTwitter, } private class OrganisationPropertyDfn { public OrganisationPropertyDfn(OrganisationProperty property, String columnHeaders) { this.Property = property; foreach (String value in Regex.Split(columnHeaders, "(\\s*,\\s*)+", RegexOptions.None)) if (!StringUtil.IsBlank(value)) this.ColumnHeaders.Add(StringUtil.TrimWhitespace(value)); } public OrganisationProperty Property { get; } public HashSet ColumnHeaders { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); public uint ColumnPos { get; set; } public bool IsValid { get { return ColumnPos > 0; } } } private class OrganisationPropertyData { public OrganisationPropertyData(OrganisationProperty property, DataValue value) { this.Property = property; this.Value = value; } public OrganisationProperty Property { get; } public DataValue Value { get; } } private class OrganisationDataProfile { public OrganisationDataProfile() { // Property columns. AddProperty(OrganisationProperty.OrganisationId, "Id, Organisation Id"); AddProperty(OrganisationProperty.OrganisationName, "Name, Organisation Name"); AddProperty(OrganisationProperty.OrganisationType, "Type, Organisation Type"); AddProperty(OrganisationProperty.OrganisationCategory, "Category, Organisation Category"); AddProperty(OrganisationProperty.Sport, "Sport"); AddProperty(OrganisationProperty.Sports, "Sports"); AddProperty(OrganisationProperty.Region, "Region"); // Address. AddProperty(OrganisationProperty.AddressLine1, "Address Line 1, Line 1, Street"); AddProperty(OrganisationProperty.AddressLine2, "Address Line 2, Line 2"); AddProperty(OrganisationProperty.AddressTown, "Town,City,Address Town,Address City"); AddProperty(OrganisationProperty.AddressProvince, "Province, County, Address Province, Address County"); AddProperty(OrganisationProperty.AddressPostcode, "Postcode, Post code, Postal code, Zip code"); // Contact personal. AddProperty(OrganisationProperty.ContactName, "Contact, Contact Name"); AddProperty(OrganisationProperty.ContactRole, "Role, Contact Role"); AddProperty(OrganisationProperty.ContactNameRole, "Contact & Role, Contact Name & Role"); AddProperty(OrganisationProperty.ContactEmail, "Contact Email"); // Contact. AddProperty(OrganisationProperty.ContactPhone, "Phone"); AddProperty(OrganisationProperty.ContactWeb, "Web, Web Link, Website"); AddProperty(OrganisationProperty.ContactMap, "Web Map, Map Link"); AddProperty(OrganisationProperty.ContactWiki, "Wiki, Wikipedia, Wiki Link"); AddProperty(OrganisationProperty.ContactFacebook, "Facebook"); AddProperty(OrganisationProperty.ContactInstagram, "Instagram"); AddProperty(OrganisationProperty.ContactTwitter, "X, Twitter"); } public Dictionary Properties { get; } = new Dictionary(); private void AddProperty(OrganisationProperty property, String columnHeaders) { Properties.Add(property, new OrganisationPropertyDfn(property, columnHeaders)); } public bool IsValidProperty(OrganisationProperty property) { OrganisationPropertyDfn propertyDfn = Properties[property]; return propertyDfn != null && propertyDfn.IsValid; } public uint FirstDataColPos { get { return Properties.Values.Where(item => item.ColumnPos > 0).Any() ? Properties.Values.Where(item => item.ColumnPos > 0).Select(item => item.ColumnPos).Min() : 1; } } public uint LastDataColPos { get { return Properties.Values.Where(item => item.ColumnPos > 0).Any() ? Properties.Values.Where(item => item.ColumnPos > 0).Select(item => item.ColumnPos).Max() : FirstDataColPos; } } } // Processing control data. private class ProcessControlData { public ProcessControlData(Config config) { this.config = config; } public Config config { get; } public SaMsSpreadsheet spreadsheet { get; set; } public OrganisationDataProfile dataProfile { get; } = new OrganisationDataProfile(); public List organisationsData { get; } = new List(); } //------------------------------------------------------------------- // Data members. private ProcessControlData control { get; } //------------------------------------------------------------------- // Construction. public SaxOrganisationUploader(Config config) : base(config) { control = new ProcessControlData(config); } //------------------------------------------------------------------- // Properties. public override string ProcessDescription => "Organisation Upload"; //------------------------------------------------------------------- // Run the process. protected override bool Run() { // Verify file. if (control.config.DataFile == null) { AddErrorMessage("Data file must be supplied."); SaLogger.Warn(SysUtil.Fn + " " + GetErrorMessage()); return OK; } else if (!validContentTypes.Contains(control.config.DataFile.ContentType)) { AddErrorMessage("Unsupported content type: " + control.config.DataFile.ContentType + "."); SaLogger.Warn(SysUtil.Fn + " " + GetErrorMessage()); } AddInfoMessage("Data file: " + control.config.DataFile.FileName); AddInfoMessage("Sheet: " + control.config.SheetNameOrNumber); try { if (OK) { // Open spreadsheet. using (SaMsSpreadsheet_POI spreadsheet = new SaMsSpreadsheet_POI()) using (Stream stream = new MemoryStream()) { stream.Write(control.config.DataFile.FileData, 0, (int)control.config.DataFile.Length); if (!spreadsheet.OpenWorkbook(stream)) { // Failed to open workbook. String msg = "Failed to open workbook - " + spreadsheet.ErrorStateMessage; AddErrorMessage(msg); SaLogger.Warn(SysUtil.Fn + " " + msg); } if (OK && !spreadsheet.OpenWorksheet(1)) { // Failed to open workbook. String msg = "Failed to open worksheet - " + spreadsheet.ErrorStateMessage; AddErrorMessage(msg); SaLogger.Warn(SysUtil.Fn + " " + msg); } if (OK) { control.spreadsheet = spreadsheet; PopulateDataProfile(); } if (OK) LoadOrganisationsData(); if (OK) ValidateOrganisationData(); // Clean-up. control.spreadsheet = null; } } } catch (Exception exc) { String msg = exc.GetType().Name + " uploading organisation data."; AddErrorMessage(msg); SaLogger.Warn(SysUtil.Fn + " " + msg + " " + exc.Message + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t")); } return OK; } //------------------------------------------------------------------- /// /// Assign organisation properties to spreadsheet columns. /// private bool PopulateDataProfile() { // Column headings. List columnHeadings = new List(); for (uint colPos = 1; colPos <= 100; colPos++) { columnHeadings.Add(control.spreadsheet.GetCellText(colPos, control.config.HeaderRowPos)); } columnHeadings.RemoveTrailingBlanks(); SaLogger.Info(SysUtil.Fn + " Column headings: " + columnHeadings.Concat(", ")); // Populate header columns in profile. for (uint colPos = 1; colPos <= columnHeadings.Count; colPos++) { String columnHeading = columnHeadings[(int)(colPos - 1)]; if (StringUtil.IsBlank(columnHeading)) continue; List propertyDfns = control.dataProfile.Properties.Values.Where(item => item.ColumnHeaders.ContainsCI(columnHeading)).ToList(); if (propertyDfns.Any()) { // Retrieve the first matching property. OrganisationPropertyDfn propertyDfn = propertyDfns[0]; if (propertyDfn.ColumnPos > 0) { AddWarningMessage("Property '" + propertyDfn.Property.GetName() + "' associated with multiple columns."); continue; } // Assign incoming data column to the first property matched by header. propertyDfn.ColumnPos = colPos; if (propertyDfns.Count > 1) AddWarningMessage("Multiple properties associated with organisation column '" + columnHeading + "'."); } else AddWarningMessage("No properties associated with organisation column '" + columnHeading + "'."); } if (!control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationId) && !control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationName)) return AddErrorMessage("Organisation Id or Name column must be present"); return OK; } //------------------------------------------------------------------- /// /// Load the data from the incoming spreadsheet. /// private bool LoadOrganisationsData() { // Reset organisation data. control.organisationsData.Clear(); for (uint rowPos = control.config.FirstDataRowPos; rowPos <= control.spreadsheet.LastRowPos; rowPos++) { List values = control.spreadsheet.GetRowValues(rowPos, (int)control.dataProfile.LastDataColPos); foreach (OrganisationPropertyDfn property in control.dataProfile.Properties.Values) { if (property.ColumnPos < 1) continue; if (property.ColumnPos > values.Count) continue; control.organisationsData.Add(new OrganisationPropertyData(property.Property, values[(int)(property.ColumnPos-1)])); } } return OK; } //------------------------------------------------------------------- private bool ValidateOrganisationData() { List organisationProperties = new List(); return OK; } //------------------------------------------------------------------- } //======================================================================= }