313 lines
12 KiB
C#
313 lines
12 KiB
C#
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<String> validContentTypes { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
|
SacsContentTypeHelper.ContentType_Excel_Legacy, /* xls */
|
|
SacsContentTypeHelper.ContentType_Excel_Open, /* xlsx */
|
|
SacsContentTypeHelper.ContentType_Excel_MacroEnabled, /* xlsm */
|
|
SacsContentTypeHelper.ContentType_Excel_BinaryMacroEnabled /* xlsb */
|
|
};
|
|
/// <summary>Allowed content types.</summary>
|
|
public static List<String> ValidContentTypes { get { return new List<String>(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<String> ColumnHeaders { get; } = new HashSet<string>(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<OrganisationProperty, OrganisationPropertyDfn> Properties { get; } = new Dictionary<OrganisationProperty, OrganisationPropertyDfn>();
|
|
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<OrganisationPropertyData> organisationsData { get; } = new List<OrganisationPropertyData>();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Assign organisation properties to spreadsheet columns.
|
|
/// </summary>
|
|
private bool PopulateDataProfile()
|
|
{
|
|
// Column headings.
|
|
List<String> columnHeadings = new List<string>();
|
|
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<OrganisationPropertyDfn> 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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Load the data from the incoming spreadsheet.
|
|
/// </summary>
|
|
private bool LoadOrganisationsData()
|
|
{
|
|
// Reset organisation data.
|
|
control.organisationsData.Clear();
|
|
|
|
for (uint rowPos = control.config.FirstDataRowPos; rowPos <= control.spreadsheet.LastRowPos; rowPos++)
|
|
{
|
|
List<DataValue> 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<OrganisationPropertyData> organisationProperties = new List<OrganisationPropertyData>();
|
|
return OK;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|