using Sarand.BusinessMgr.Net.Standard.Base.App; using Sarand.BusinessMgr.Net.Standard.Base.Bus; using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Security; using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sarand.BusinessMgr.Net.Standard.Base.Consoles { /// /// Utilitiy function relating to a core BusinessMgr database instance. /// For instance: /// - Initialisation of new repository. /// - Bringing static (tax codes etc) and reference data up to date. /// public abstract class SbmCoreInstanceConsole_Base : SaErrorStateItem { //------------------------------------------------------------------- // Definitions. public const String col_AffilId = "AffilId"; public const String col_AssocId = "AssocId"; public const String col_Name = "Name"; public const String col_Website = "Website"; public const String col_CompanyNo = "CompanyNo"; public const String col_VatReg = "VatReg"; public const String col_FirstName = "FirstName"; public const String col_LastName = "LastName"; public const String col_Email = "Email"; public const String col_UserId = "UserId"; public const String col_UserCode = "UserCode"; /// /// Organisation and User info for Repository initialisation. /// public class RepositoryInitialisationData { public class UserData { public SysId AffiliateId { get; set; } public String FirstName { get; set; } public String LastName { get; set; } public String Email { get; set; } public SysId SecurityItemId { get; set; } public String Code { get; set; } public String Password { get; set; } public bool PasswordSet { get; set; } } public UserData User { get; private set; } = new UserData(); public void ClearUser() { User = new UserData(); } public class OrganisationData { public String Name { get; set; } public String VatReg { get; set; } public String CompanyNo { get; set; } public String Website { get; set; } public SysId AffiliateId { get; set; } public SysId AssociationId { get; set; } } public OrganisationData Organisation { get; private set; } = new OrganisationData(); public void ClearOrganisation() { Organisation = new OrganisationData(); } } //------------------------------------------------------------------- // Data members. //------------------------------------------------------------------- // Construction. protected SbmCoreInstanceConsole_Base() { } protected override void Dispose(bool disposing) { if (disposing) { } base.Dispose(disposing); } //------------------------------------------------------------------- // Properties. public String SecurityGroupCode_Super { get; } = SaSecurityConsole.GroupCode_Super; // Core instance. public abstract ISacsDatabase Database_Core { get; } public abstract SbmInternals Internals_Core { get; } public abstract DbUpdateInfo DbUpdateInfo_Core { get; } //------------------------------------------------------------------- // Utilities. //------------------------------------------------------------------- /// /// Is repository database valid. /// - Contains core tables (Affiliate, Association, Commodity etc). /// /// /// public bool IsRepositoryValid() { ClearErrorState(); HashSet tablesExpected = new HashSet("Affiliate,Association,Commodity,Transaction,Requirement,SaSecItem,SaSecLink".Split(',')); String sql = $"Select {col_Name} From sys.tables"; try { using (DbResultSet resultSet = Database_Core.ExecuteQuery(sql)) { if (resultSet != null) { HashSet tablesFound = new HashSet(); while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) { NamedValue.Map values = resultSet.GetRecordValues(); tablesFound.Add(values.GetString(col_Name)); } HashSet tablesNotFound = new HashSet(tablesExpected.Where(item => !tablesFound.ContainsCI(item))); if (tablesNotFound.Any()) return SetErrorState($"Core tables not found: {String.Join(", ", tablesNotFound)}."); } } } catch (Exception exc) { String msg = "checking repository validity"; SaLogger.Warn($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, msg)}"); return SetErrorState($"{exc.GetType().Name} {msg}"); } return OK; } //------------------------------------------------------------------- /// /// Is repository database initialised. /// - Has a valid internal association. /// - Has at least one user. /// public bool IsRepositoryInitialised() { ClearErrorState(); RepositoryInitialisationData initData = new RepositoryInitialisationData(); try { // Query first internal association. List sql = new List() { $"Select {col_AssocId}=ass.Id, {col_AffilId}=aff.Id, {col_Name}=aff.Name, {col_Website}=aff.Website, {col_CompanyNo}=aff.CompanyNo, {col_VatReg}=aff.VatReg", $"From Association ass", $"Left Join Affiliate aff On aff.Id=ass.AffIdOwner And aff.Id=ass.AffIdTarget And aff.Type<>{(int)Sbm.AffiliateType.Individual}", $"Where ass.Type={(int)Sbm.AssociationType.Internal};" }; using (DbResultSet resultSet = Database_Core.ExecuteQuery(String.Join(" ", sql))) { while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) { NamedValue.Map values = resultSet.GetRecordValues(); initData.ClearOrganisation(); initData.Organisation.Name = values.GetString(col_Name); initData.Organisation.Website = values.GetString(col_Website); initData.Organisation.CompanyNo = values.GetString(col_CompanyNo); initData.Organisation.VatReg = values.GetString(col_VatReg); initData.Organisation.AffiliateId = new SysId(values.GetLong(col_AffilId)); initData.Organisation.AssociationId = new SysId(values.GetLong(col_AssocId)); if (SysId.IsValid(initData.Organisation.AssociationId) && SysId.IsValid(initData.Organisation.AffiliateId)) break; } if (!SysId.IsValid(initData.Organisation.AssociationId)) return SetErrorState("Internal association not found."); if (!SysId.IsValid(initData.Organisation.AffiliateId)) return SetErrorState("Owner affiliate not found."); } // Query Employees for the Organisation. sql = new List() { $"Select {col_AffilId}=aff.Id, {col_FirstName}=aff.FirstName, {col_LastName}=aff.LastName, {col_UserId}=aff.UserId, {col_UserCode}=seci.Code", $"From Association ass", $"Inner Join Affiliate aff On aff.Id=ass.AffIdTarget And aff.Type={(int)Sbm.AffiliateType.Individual} And IsNull(aff.UserId,0)>0", $"Left Join SaSecItem seci On seci.Id=aff.UserId", $"Where ass.AffIdOwner={SysId.ToLong(initData.Organisation.AffiliateId)} And ass.Type={(int)Sbm.AssociationType.Member};" }; using (DbResultSet resultSet = Database_Core.ExecuteQuery(String.Join(" ", sql))) { while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) { NamedValue.Map values = resultSet.GetRecordValues(); initData.ClearUser(); initData.User.AffiliateId = new SysId(values.GetLong(col_AffilId)); initData.User.FirstName = values.GetString(col_FirstName); initData.User.LastName = values.GetString(col_LastName); initData.User.Email = values.GetString(col_Email); initData.User.SecurityItemId = new SysId(values.GetLong(col_UserId)); initData.User.Code = values.GetString(col_UserCode); if (SysId.IsValid(initData.User.AffiliateId) && SysId.IsValid(initData.User.SecurityItemId)) break; } if (!SysId.IsValid(initData.User.AffiliateId)) return SetErrorState("User affiliate not found."); if (!SysId.IsValid(initData.User.SecurityItemId)) return SetErrorState("User security item not found."); } if (Internals_Core.SecurityConsole.RequiresPopulation()) return SetErrorState("Security Database requires population."); } catch (Exception exc) { String msg = "checking repository initialisation"; SaLogger.Warn($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, msg)}"); return SetErrorState($"{exc.GetType().Name} {msg}"); } return OK; } //------------------------------------------------------------------- /// /// Validate repository initialisation data. /// public bool ValidateRepositoryInitialisionData(RepositoryInitialisationData initData) { ClearErrorState(); // Organisation name. if (StringUtil.IsBlank(initData.Organisation.Name)) return SetErrorState("Organisation name must be supplied."); if ((ulong)StringUtil.GetLength(initData.Organisation.Name) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_Name)) return SetErrorState("Organisation name exceeds max chars."); // Organisation VAT reg. if ((ulong)StringUtil.GetLength(initData.Organisation.VatReg) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_VatReg)) return SetErrorState("Company VAT reg exceeds max chars."); // Organisation Company Number. if ((ulong)StringUtil.GetLength(initData.Organisation.CompanyNo) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_CompanyNo)) return SetErrorState("Company number exceeds max chars."); // Organisation Company Number. if ((ulong)StringUtil.GetLength(initData.Organisation.Website) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_Website)) return SetErrorState("Company website address exceeds max chars."); // User first name. if (StringUtil.IsBlank(initData.User.FirstName)) return SetErrorState("User first name must be supplied."); if ((ulong)StringUtil.GetLength(initData.User.FirstName) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_FirstName)) return SetErrorState("User first name exceeds max chars."); // User last name. if (StringUtil.IsBlank(initData.User.LastName)) return SetErrorState("User last name must be supplied."); if ((ulong)StringUtil.GetLength(initData.User.LastName) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_LastName)) return SetErrorState("User last name exceeds max chars."); // User email. if (StringUtil.IsBlank(initData.User.Email)) return SetErrorState("User email must be supplied."); if ((ulong)StringUtil.GetLength(initData.User.Email) > SbmDbDfn.GetColTextLen(SbmDbDfn.ColumnIdx.Affiliate_Email) || (ulong)StringUtil.GetLength(initData.User.Email) > SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_EmailAddress)) return SetErrorState("User email exceeds max chars."); // User code. if (StringUtil.IsBlank(initData.User.Code)) return SetErrorState("User code must be supplied."); if ((ulong)StringUtil.GetLength(initData.User.Code) > SaSecurityDbDfn.GetColTextLen(SaSecurityDbDfn.ColumnIdx.SaSecItem_Code)) return SetErrorState("User code exceeds max chars."); // 'Super' security group. if (StringUtil.IsBlank(SecurityGroupCode_Super)) return SetErrorState($"Failed to identify code for 'Super' security group."); return OK; } //------------------------------------------------------------------- /// /// Initialise repository. /// public bool InitialiseRepository(RepositoryInitialisationData initData) { ClearErrorState(); if (!ValidateRepositoryInitialisionData(initData)) return false; using (DisposableCollection disposables = new DisposableCollection()) { //----------------------------------------------------------- // Organisation Contact. // Retrieve existing Organisation. SbmAffiliate affiliateOrganisation = Internals_Core.AccountMgr.GetAffiliateById(initData.Organisation.AffiliateId); if (affiliateOrganisation == null && SysId.IsValid(initData.Organisation.AffiliateId)) return SetErrorState("Identified Organisation Contact not found."); if (affiliateOrganisation != null && !affiliateOrganisation.IsOrganisation()) return SetErrorState("Identified Contact is not an organisation."); if (affiliateOrganisation == null) affiliateOrganisation = Internals_Core.AccountMgr.GetAffiliateByTypeName(Sbm.AffiliateType.AnyOrganisation, initData.Organisation.Name); if (affiliateOrganisation == null) { // Create new organisation. affiliateOrganisation = Internals_Core.AccountMgr.CreateAffiliate(Sbm.AffiliateType.Commercial); } disposables.Add(affiliateOrganisation); // Populate properties. affiliateOrganisation.Name = initData.Organisation.Name; if (!StringUtil.IsBlank(initData.Organisation.VatReg)) affiliateOrganisation.VatReg = initData.Organisation.VatReg; if (!StringUtil.IsBlank(initData.Organisation.CompanyNo)) affiliateOrganisation.CompanyNo = initData.Organisation.CompanyNo; if (!StringUtil.IsBlank(initData.Organisation.Website)) affiliateOrganisation.Website = initData.Organisation.Website; // Store Organisation Contact. if (!SysId.IsValid(affiliateOrganisation.Id)) { if (!Internals_Core.AccountMgr.InsertAffiliate(affiliateOrganisation, DbUpdateInfo_Core)) return SetErrorState($"Failed to insert Organisation contact - {Internals_Core.AccountMgr.ErrorStateMessage}"); } else { if (!Internals_Core.AccountMgr.UpdateAffiliate(affiliateOrganisation, DbUpdateInfo_Core)) return SetErrorState($"Failed to update Organisation contact - {Internals_Core.AccountMgr.ErrorStateMessage}"); } //----------------------------------------------------------- // Organisation Internal association. // Retrieve existing Internal Association for Organisation. SbmAssociation associationInternal = Internals_Core.AccountMgr.GetAssociationByOwnerTypeTarget(affiliateOrganisation.Id, Sbm.AssociationType.Internal, affiliateOrganisation.Id); disposables.Add(associationInternal); if (associationInternal != null && SysId.IsValid(initData.Organisation.AssociationId) && !associationInternal.Id.Equals(initData.Organisation.AssociationId)) // Failed to validate existing Internal Association against supplied Association Id. return SetErrorState($"Identified Internal Association (Id. {initData.Organisation.AssociationId}) doesn't match existing association ({associationInternal.Id})."); if (associationInternal == null && SysId.IsValid(initData.Organisation.AssociationId)) { // No existing Internal Association found for Organisation - retrieve specified Association. associationInternal = Internals_Core.AccountMgr.GetAssociationById(initData.Organisation.AssociationId); if (associationInternal == null) return SetErrorState($"Failed to retrieve supplied Internal Association. Id: {initData.Organisation.AssociationId}."); disposables.Add(associationInternal); if (!associationInternal.AffiliateIdOwner.Equals(affiliateOrganisation.Id)) return SetErrorState($"Supplied Internal Association has different Owner ({associationInternal.AffiliateIdOwner}) to Organisation ({affiliateOrganisation.Id})."); if (!associationInternal.AffiliateIdTarget.Equals(affiliateOrganisation.Id)) return SetErrorState($"Supplied Internal Association has different Target ({associationInternal.AffiliateIdTarget}) to Organisation ({affiliateOrganisation.Id})."); } if (associationInternal == null) associationInternal = Internals_Core.AccountMgr.CreateAssociation(affiliateOrganisation.Id, Sbm.AssociationType.Internal, affiliateOrganisation.Id); disposables.Add(associationInternal); // Store Internal Association. if (!SysId.IsValid(associationInternal.Id)) { if (!Internals_Core.AccountMgr.InsertAssociation(associationInternal, DbUpdateInfo_Core)) return SetErrorState($"Failed to insert Internal Association for Organisation - {Internals_Core.AccountMgr.ErrorStateMessage}"); } else { if (!Internals_Core.AccountMgr.UpdateAssociation(associationInternal, DbUpdateInfo_Core)) return SetErrorState($"Failed to update Internal Association for Organisation - {Internals_Core.AccountMgr.ErrorStateMessage}"); } //----------------------------------------------------------- // Security. // Core security console. SaSecurityConsole securityConsole = Internals_Core.SecurityConsole; // Initialise security data if required. if (securityConsole.RequiresPopulation() && !securityConsole.PopulateSecurityRights(DbUpdateInfo_Core)) return SetErrorState(securityConsole.ErrorStateMessage); // User Security Item. SaSecurityItem securityItemUser = null; // Retrieve supplied Security Item. if (SysId.IsValid(initData.User.SecurityItemId)) { securityItemUser = securityConsole.GetSecurityItemById(SaSecurity.ItemType.User, initData.User.SecurityItemId); if (securityItemUser == null) return SetErrorState($"Failed to retrieve supplied User Security Item. Id: {initData.User.SecurityItemId}."); disposables.Add(securityItemUser); if (!securityItemUser.Code.Equals(initData.User.Code)) return SetErrorState($"Supplied User Security Item doesn't match supplied code. Expected: {initData.User.Code}. Found: {securityItemUser.Code}."); } if (securityItemUser == null) { // Retrieve Security Item by code. securityItemUser = securityConsole.GetSecurityItemByCode(SaSecurity.ItemType.User, initData.User.Code); disposables.Add(securityItemUser); } if (securityItemUser == null) { // Create User Security Item. if (!securityConsole.CreateUser(initData.User.Code, $"{initData.User.LastName}, {initData.User.FirstName}", DbUpdateInfo_Core)) return SetErrorState($"Failed to create User Security Item - {securityConsole.ErrorStateMessage}."); securityItemUser = securityConsole.GetSecurityItemByCode(SaSecurity.ItemType.User, initData.User.Code); if (securityItemUser == null) return SetErrorState($"Failed to retrieve User Security Item after creation. Code: {initData.User.Code}."); disposables.Add(securityItemUser); } // Populate User Security Item properties. securityItemUser.EmailAddress = initData.User.Email; if (initData.User.PasswordSet) securityConsole.SetPassword(securityItemUser, initData.User.Password); if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo_Core)) return SetErrorState($"Failed to update User Security Item - {securityConsole.ErrorStateMessage}."); // Assign to 'Super' Group. SysId securityGroupIdSuper = securityConsole.GetIdForItemCode(SaSecurity.ItemType.Group, SecurityGroupCode_Super); if (!SysId.IsValid(securityGroupIdSuper)) return SetErrorState($"Failed to identify 'Super' security group. Code: '{SecurityGroupCode_Super}'."); if (!securityConsole.ItemsAreLinked(securityItemUser.Id, securityGroupIdSuper) && !securityConsole.AssignUserToGroup(securityItemUser.Code, SecurityGroupCode_Super, DbUpdateInfo_Core)) return SetErrorState($"Failed to assign User Security Item to Super Group - {securityConsole.ErrorStateMessage}."); //----------------------------------------------------------- // User Contact. SbmAffiliate affiliateUser = null; if (SysId.IsValid(initData.User.AffiliateId)) { // Retrieve supplied User Contact. affiliateUser = Internals_Core.AccountMgr.GetAffiliateById(initData.User.AffiliateId); if (affiliateUser == null) return SetErrorState($"Failed to retrieve User Contact. Id: {initData.User.AffiliateId}."); disposables.Add(affiliateUser); } if (affiliateUser == null) { // Retrieve User Contact by User Code. affiliateUser = Internals_Core.AccountMgr.GetAffiliateByUserId(securityItemUser.Id); disposables.Add(affiliateUser); } if (affiliateUser == null) { // Retrieve User Contact by name. affiliateUser = Internals_Core.AccountMgr.GetIndividualByName(initData.User.FirstName, initData.User.LastName); disposables.Add(affiliateUser); } if (affiliateUser == null) { // Create User Contact. affiliateUser = Internals_Core.AccountMgr.CreateAffiliate(Sbm.AffiliateType.Individual); disposables.Add(affiliateUser); } // Populate User Contact properties. affiliateUser.FirstName = initData.User.FirstName; affiliateUser.LastName = initData.User.LastName; affiliateUser.Email = initData.User.Email; affiliateUser.UserId = securityItemUser.Id; // Store User Contact. if (!SysId.IsValid(affiliateUser.Id)) { if (!Internals_Core.AccountMgr.InsertAffiliate(affiliateUser, DbUpdateInfo_Core)) return SetErrorState($"Failed to insert User contact - {Internals_Core.AccountMgr.ErrorStateMessage}"); } else { if (!Internals_Core.AccountMgr.UpdateAffiliate(affiliateUser, DbUpdateInfo_Core)) return SetErrorState($"Failed to update User contact - {Internals_Core.AccountMgr.ErrorStateMessage}"); } //----------------------------------------------------------- // User Member Association. // Attempt to retrieve existing Member Association. SbmAssociation associationMember = Internals_Core.AccountMgr.GetAssociationByOwnerTypeTarget(affiliateOrganisation.Id, Sbm.AssociationType.Member, affiliateUser.Id); if (associationMember == null) // Create Member Association. associationMember = Internals_Core.AccountMgr.CreateAssociation(affiliateOrganisation.Id, Sbm.AssociationType.Member, affiliateUser.Id); disposables.Add(associationMember); // Store User Association. if (!SysId.IsValid(associationMember.Id)) { if (!Internals_Core.AccountMgr.InsertAssociation(associationMember, DbUpdateInfo_Core)) return SetErrorState($"Failed to insert Member Association for User - {Internals_Core.AccountMgr.ErrorStateMessage}"); } else { if (!Internals_Core.AccountMgr.UpdateAssociation(associationMember, DbUpdateInfo_Core)) return SetErrorState($"Failed to update Member Association for User - {Internals_Core.AccountMgr.ErrorStateMessage}"); } //----------------------------------------------------------- } return OK; } //------------------------------------------------------------------- } }