diff --git a/SaBusMgrCoreWeb/Web/App/SbmSessionData_Web.cs b/SaBusMgrCoreWeb/Web/App/SbmSessionData_Web.cs index 5a337e8..b2056f9 100644 --- a/SaBusMgrCoreWeb/Web/App/SbmSessionData_Web.cs +++ b/SaBusMgrCoreWeb/Web/App/SbmSessionData_Web.cs @@ -33,6 +33,19 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App } } + //------------------------------------------------------------------- + // Contacts. + public class ContactsData + { + public class FilterData + { + public uint PageNo { get; set; } = 1; + public ushort PageLength { get; set; } = 16; + } + public FilterData Filter { get; } = new FilterData(); + } + public ContactsData Contacts { get; } = new ContactsData(); + //------------------------------------------------------------------- } //======================================================================= diff --git a/SaBusMgrCoreWeb/Web/Controllers/SbmController_Base.cs b/SaBusMgrCoreWeb/Web/Controllers/SbmController_Base.cs index 7d4c87c..294c0f3 100644 --- a/SaBusMgrCoreWeb/Web/Controllers/SbmController_Base.cs +++ b/SaBusMgrCoreWeb/Web/Controllers/SbmController_Base.cs @@ -380,6 +380,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers return Json(responseData); } + //------------------------------------------------------------------- + // Base Ajax Response Data. //------------------------------------------------------------------- public class AjaxResponse_Base { @@ -418,13 +420,30 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers } } + //------------------------------------------------------------------- + // Ajax Response Data. //------------------------------------------------------------------- public class AjaxResponse_Data : AjaxResponse_Base { + //--------------------------------------------------------------- // Construction. protected AjaxResponse_Data(SbmModel_Base model) { } + + //--------------------------------------------------------------- + public class ItemData + { + public String Id { get; set; } + public String Notes { get; set; } + public String StatusCode { get; set; } + public String StatusDesc { get; set; } + public Boolean Awaiting { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Awaiting)); } } + public Boolean Active { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Active)); } } + public Boolean Deleted { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Deleted)); } } + } + + //--------------------------------------------------------------- // Form data. public class FormControlValue { @@ -477,6 +496,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers } return false; } + + //--------------------------------------------------------------- // Notification - briefly displays after response received. public class NotificationData { @@ -490,6 +511,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers Notification = new NotificationData() { Title = title, Message = message }; return this; } + + //--------------------------------------------------------------- // Paged data. public class PagingData { @@ -515,6 +538,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers { Paging = new PagingData(model); } + + //--------------------------------------------------------------- } //------------------------------------------------------------------- diff --git a/SaBusMgrStandardBase/Base/App/SbmInternals.cs b/SaBusMgrStandardBase/Base/App/SbmInternals.cs index 3d3c28f..a183e89 100644 --- a/SaBusMgrStandardBase/Base/App/SbmInternals.cs +++ b/SaBusMgrStandardBase/Base/App/SbmInternals.cs @@ -111,8 +111,9 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App { if (!systemCodeCache.ContainsKey(systemCode)) systemCodeCache.Add(systemCode, SbmSystem.GetCodesOfType(UserContext, systemCode)); - return systemCodeCache.GetValueOrDefault(systemCode, new List()); + return new List(systemCodeCache.GetValueOrDefault(systemCode, new List())); } + public List AffiliateTypeCodes { get { return GetCodesOfType(Sbm.SystemCode.AffiliateType); } } //------------------------------------------------------------------- // Managers. diff --git a/SaBusMgrStandardBase/Base/App/SbmSecurityProfile.cs b/SaBusMgrStandardBase/Base/App/SbmSecurityProfile.cs index b8bad32..2d56087 100644 --- a/SaBusMgrStandardBase/Base/App/SbmSecurityProfile.cs +++ b/SaBusMgrStandardBase/Base/App/SbmSecurityProfile.cs @@ -1,4 +1,5 @@ -using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Newtonsoft.Json; +using Sarand.BusinessMgr.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Core; @@ -35,6 +36,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App //------------------------------------------------------------------- // Properties. + [JsonIgnore] public SbmUserContext UserContext { get; } //------------------------------------------------------------------- diff --git a/SaBusMgrStandardBase/Base/App/SbmSessionContext.cs b/SaBusMgrStandardBase/Base/App/SbmSessionContext.cs index 7f1f5ed..07602ad 100644 --- a/SaBusMgrStandardBase/Base/App/SbmSessionContext.cs +++ b/SaBusMgrStandardBase/Base/App/SbmSessionContext.cs @@ -1,4 +1,5 @@ -using Sarand.Net.Standard.Base.App; +using Newtonsoft.Json; +using Sarand.Net.Standard.Base.App; namespace Sarand.BusinessMgr.Net.Standard.Base.App { @@ -7,6 +8,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App { //------------------------------------------------------------------- // Construction. + [JsonConstructor] protected SbmSessionContext(ushort index, Sbm.Application application) : base(index) { Index = index; diff --git a/SaBusMgrStandardBase/Base/Bus/SbmAffiliate.cs b/SaBusMgrStandardBase/Base/Bus/SbmAffiliate.cs index 32b5b50..f2def08 100644 --- a/SaBusMgrStandardBase/Base/Bus/SbmAffiliate.cs +++ b/SaBusMgrStandardBase/Base/Bus/SbmAffiliate.cs @@ -19,6 +19,8 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus Pos, Id, // Affiliate. + Type, + Type_Code, Type_Desc, Name, Abbrev, @@ -734,7 +736,11 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus // List parameters. public enum StatusCat { - Any, ActiveOnly, + Any, + CurrentOnly, + OnHoldOnly, + NotDeleted, + DeletedOnly, } /// /// Item list parameters. @@ -748,6 +754,8 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus public Sbm.AffiliateType AffiliateType { get; set; } = Sbm.AffiliateType.Any; public SysId AffiliateIdParent { get; set; } public SysId UserId { get; set; } + /// Generic search value - name, phone, website, address etc. + public String Search { get; set; } public String Name { get; set; } public String NamePartial { get; set; } public String PhonePartial { get; set; } diff --git a/SaBusMgrStandardBase/Base/Mgr/SbmAccountMgr_Affiliate.cs b/SaBusMgrStandardBase/Base/Mgr/SbmAccountMgr_Affiliate.cs index 15749b7..73f5ff7 100644 --- a/SaBusMgrStandardBase/Base/Mgr/SbmAccountMgr_Affiliate.cs +++ b/SaBusMgrStandardBase/Base/Mgr/SbmAccountMgr_Affiliate.cs @@ -378,10 +378,13 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Mgr switch ((SbmAffiliate.Column)column.Id) { // Affiliate type description. + case SbmAffiliate.Column.Type_Code: case SbmAffiliate.Column.Type_Desc: { Sbm.AffiliateType affiliateType = (Sbm.AffiliateType)values.GetValue(column.DataName).AsLong(); - value = new DataValue(AffiliateTypes.GetDescForType(affiliateType, affiliateType.ToString())); + if (column.Id == (int)SbmAffiliate.Column.Type_Desc) + value = new DataValue(AffiliateTypes.GetDescForType(affiliateType, affiliateType.ToString())); + else value = new DataValue(AffiliateTypes.GetCodeForType(affiliateType, affiliateType.ToString())); } break; diff --git a/SaBusMgrStandardBase/Resource/Email/Shared/sbm-email-footer.html b/SaBusMgrStandardBase/Resource/Email/Shared/sbm-email-footer.html deleted file mode 100644 index 1c5016a..0000000 --- a/SaBusMgrStandardBase/Resource/Email/Shared/sbm-email-footer.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
Email Footer
-
diff --git a/SaBusMgrStandardBase/Resource/Email/Security/sbm-email-request-password.html b/SaBusMgrStandardBase/Resources/Email/Security/sbm-email-request-password.html similarity index 92% rename from SaBusMgrStandardBase/Resource/Email/Security/sbm-email-request-password.html rename to SaBusMgrStandardBase/Resources/Email/Security/sbm-email-request-password.html index 3f1f8b3..c56a29d 100644 --- a/SaBusMgrStandardBase/Resource/Email/Security/sbm-email-request-password.html +++ b/SaBusMgrStandardBase/Resources/Email/Security/sbm-email-request-password.html @@ -16,6 +16,7 @@
Follow this link to set a new password against your account.
This link will expire in [#Link.ExpiresMinutes] minutes.
Thank you.
+
Sarand BusinessMgr team
[#Email.Footer] diff --git a/SaBusMgrStandardBase/Resources/Email/Shared/sbm-email-footer.html b/SaBusMgrStandardBase/Resources/Email/Shared/sbm-email-footer.html new file mode 100644 index 0000000..37d3726 --- /dev/null +++ b/SaBusMgrStandardBase/Resources/Email/Shared/sbm-email-footer.html @@ -0,0 +1,3 @@ + diff --git a/SaBusMgrStandardBase/Resource/Email/Shared/sbm-email-header.html b/SaBusMgrStandardBase/Resources/Email/Shared/sbm-email-header.html similarity index 100% rename from SaBusMgrStandardBase/Resource/Email/Shared/sbm-email-header.html rename to SaBusMgrStandardBase/Resources/Email/Shared/sbm-email-header.html diff --git a/SaBusMgrStandardBase/Resource/Email/Style/sbm-email-main.css b/SaBusMgrStandardBase/Resources/Email/Style/sbm-email-main.css similarity index 88% rename from SaBusMgrStandardBase/Resource/Email/Style/sbm-email-main.css rename to SaBusMgrStandardBase/Resources/Email/Style/sbm-email-main.css index 0b5b642..53572ba 100644 --- a/SaBusMgrStandardBase/Resource/Email/Style/sbm-email-main.css +++ b/SaBusMgrStandardBase/Resources/Email/Style/sbm-email-main.css @@ -1,9 +1,14 @@ -body +html +{ + font-size:10px; +} + +body { margin: 0; padding: 0; font-family: Verdana, Helvetica, Arial, sans-serif; - font-size: 16px; + font-size: 1.6rem; line-height: 1.5; background-color: #fff; } @@ -32,6 +37,7 @@ td .sax-email-header .sax-content { + font-size:2.3rem; } .sax-email-body @@ -41,7 +47,7 @@ td .sax-email-panel-caption { margin: 12px 0; - font-size: 40px; + font-size: 3.2rem; font-weight: bold; color: #000; background-color: transparent; @@ -60,7 +66,7 @@ td td.sax-label { padding: 4px 12px; - font-size: 16px; + font-size: 1.6rem; text-align: right; color: #808080; white-space: nowrap; @@ -69,7 +75,7 @@ td.sax-label td.sax-value { padding-top: 4px; - font-size: 16px; + font-size: 1.6rem; } .sax-email-footer @@ -81,6 +87,7 @@ td.sax-value .sax-email-footer .sax-content { padding:12px 24px; + font-size:1.2rem; } .sax-email-footer .sax-content .sax-footer-lower diff --git a/SaBusMgrStandardBase/Resource/html/sbm-base.css b/SaBusMgrStandardBase/Resources/html/sbm-base.css similarity index 100% rename from SaBusMgrStandardBase/Resource/html/sbm-base.css rename to SaBusMgrStandardBase/Resources/html/sbm-base.css diff --git a/SaBusMgrStandardBase/Resource/html/sbm-base.js b/SaBusMgrStandardBase/Resources/html/sbm-base.js similarity index 100% rename from SaBusMgrStandardBase/Resource/html/sbm-base.js rename to SaBusMgrStandardBase/Resources/html/sbm-base.js diff --git a/SaBusMgrStandardBase/Resource/img/arrow-down-black-16.png b/SaBusMgrStandardBase/Resources/img/arrow-down-black-16.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/arrow-down-black-16.png rename to SaBusMgrStandardBase/Resources/img/arrow-down-black-16.png diff --git a/SaBusMgrStandardBase/Resource/img/arrow-down-black-24.png b/SaBusMgrStandardBase/Resources/img/arrow-down-black-24.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/arrow-down-black-24.png rename to SaBusMgrStandardBase/Resources/img/arrow-down-black-24.png diff --git a/SaBusMgrStandardBase/Resource/img/arrow-up-black-16.png b/SaBusMgrStandardBase/Resources/img/arrow-up-black-16.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/arrow-up-black-16.png rename to SaBusMgrStandardBase/Resources/img/arrow-up-black-16.png diff --git a/SaBusMgrStandardBase/Resource/img/arrow-up-black-24.png b/SaBusMgrStandardBase/Resources/img/arrow-up-black-24.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/arrow-up-black-24.png rename to SaBusMgrStandardBase/Resources/img/arrow-up-black-24.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-150.ico b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-150.ico similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-150.ico rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-150.ico diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-150.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-150.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-150.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-150.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-32.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-32.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-32.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-32.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-40.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-40.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-40.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-40.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-420.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-420.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-420.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-420.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64-150.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64-150.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64-150.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64-150.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64.ico b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64.ico similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64.ico rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64.ico diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-side-64.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-side-64.png diff --git a/SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-top-120.png b/SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-top-120.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/sapphire/sapphire01-top-120.png rename to SaBusMgrStandardBase/Resources/img/sapphire/sapphire01-top-120.png diff --git a/SaBusMgrStandardBase/Resource/img/star-amber-16.png b/SaBusMgrStandardBase/Resources/img/star-amber-16.png similarity index 100% rename from SaBusMgrStandardBase/Resource/img/star-amber-16.png rename to SaBusMgrStandardBase/Resources/img/star-amber-16.png diff --git a/SaBusMgrStandardBase/Resource/jquery/jquery-1.12.1.min.js b/SaBusMgrStandardBase/Resources/jquery/jquery-1.12.1.min.js similarity index 100% rename from SaBusMgrStandardBase/Resource/jquery/jquery-1.12.1.min.js rename to SaBusMgrStandardBase/Resources/jquery/jquery-1.12.1.min.js diff --git a/SaBusMgrStandardBase/Resource/jquery/jquery-ui-1.12.1.min.css b/SaBusMgrStandardBase/Resources/jquery/jquery-ui-1.12.1.min.css similarity index 100% rename from SaBusMgrStandardBase/Resource/jquery/jquery-ui-1.12.1.min.css rename to SaBusMgrStandardBase/Resources/jquery/jquery-ui-1.12.1.min.css diff --git a/SaBusMgrStandardBase/Resource/jquery/jquery-ui-1.12.1.min.js b/SaBusMgrStandardBase/Resources/jquery/jquery-ui-1.12.1.min.js similarity index 100% rename from SaBusMgrStandardBase/Resource/jquery/jquery-ui-1.12.1.min.js rename to SaBusMgrStandardBase/Resources/jquery/jquery-ui-1.12.1.min.js diff --git a/SaBusMgrStandardBase/SaBusMgrStandardBase.csproj b/SaBusMgrStandardBase/SaBusMgrStandardBase.csproj index c84fe10..5c28877 100644 --- a/SaBusMgrStandardBase/SaBusMgrStandardBase.csproj +++ b/SaBusMgrStandardBase/SaBusMgrStandardBase.csproj @@ -1,17 +1,21 @@ - - netstandard2.1 - Sarand.BusinessMgr.Net.Standard - + + netstandard2.1 + Sarand.BusinessMgr.Net.Standard + - - - + + + - - - - + + + + + + + + diff --git a/SaBusMgrStandardDbas/Dbas/Mgr/SbmAccountMgr_Dbas-Affiliate.cs b/SaBusMgrStandardDbas/Dbas/Mgr/SbmAccountMgr_Dbas-Affiliate.cs index e60bf58..e2f9e20 100644 --- a/SaBusMgrStandardDbas/Dbas/Mgr/SbmAccountMgr_Dbas-Affiliate.cs +++ b/SaBusMgrStandardDbas/Dbas/Mgr/SbmAccountMgr_Dbas-Affiliate.cs @@ -398,12 +398,18 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr columnDfn.ReadOnly = true; break; // Affiliate Properties. + case SbmAffiliate.Column.Type: + SetAffiliateColDfn(columnDfn, "Type", 60, SbmDbDfn.ColumnIdx.Affiliate_Type, ColumnAlignment.Center, DataValue.DataType.Long); + break; + case SbmAffiliate.Column.Type_Code: + SetAffiliateColDfn(columnDfn, "Type", 70, SbmDbDfn.ColumnIdx.Affiliate_Type, ColumnAlignment.Center); + break; case SbmAffiliate.Column.Type_Desc: - SetAffiliateColDfn(columnDfn, "Type", 70, SbmDbDfn.ColumnIdx.Affiliate_Type); + SetAffiliateColDfn(columnDfn, "Type", 80, SbmDbDfn.ColumnIdx.Affiliate_Type, ColumnAlignment.Center); break; case SbmAffiliate.Column.Name: SetAffiliateColDfn(columnDfn, "Name", 160, SbmDbDfn.ColumnIdx.Affiliate_Name); - break;/* + break; case SbmAffiliate.Column.Abbrev: SetAffiliateColDfn(columnDfn, "Abbrev", 90, SbmDbDfn.ColumnIdx.Affiliate_Abbrev); break; @@ -421,16 +427,16 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr break; case SbmAffiliate.Column.Salutation: SetAffiliateColDfn(columnDfn, "Salutation", 50, SbmDbDfn.ColumnIdx.Affiliate_Salutation); - break;*/ + break; case SbmAffiliate.Column.Nature: SetAffiliateColDfn(columnDfn, "Nature", 120, SbmDbDfn.ColumnIdx.Affiliate_Nature); - break;/* + break; case SbmAffiliate.Column.CompanyNo: SetAffiliateColDfn(columnDfn, "CompanyNo", 50, SbmDbDfn.ColumnIdx.Affiliate_CompanyNo); break; case SbmAffiliate.Column.VatReg: SetAffiliateColDfn(columnDfn, "VatReg", 80, SbmDbDfn.ColumnIdx.Affiliate_VatReg); - break;*/ + break; case SbmAffiliate.Column.Contact: SetAffiliateColDfn(columnDfn, "Contact", 120, SbmDbDfn.ColumnIdx.Affiliate_Contact); break; @@ -486,12 +492,14 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr case SbmAffiliate.Column.Address_Postcode: SetAffiliateColDfn(columnDfn, "Postcode", 65, SbmDbDfn.ColumnIdx.Address_Postcode, ColumnAlignment.Center, DataValue.DataType.String, aliasAffiliate_AddPrim, prefixAffiliate_AddPrim); break; + //----------------------------------------------------------------- // Derived columns. case SbmAffiliate.Column.GroupMembers: SetAffiliateColDfn(columnDfn, "Members", 90, SbmDbDfn.ColumnIdx.Unknown, ColumnAlignment.Right); columnDfn.DataName = colNameAffiliate_GroupMembers; break; + //----------------------------------------------------------------- // Common properties. case SbmAffiliate.Column.Notes: @@ -556,38 +564,60 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr filter.Add(colAffiliate_AffIdParent + "=" + SysId.ToString(prm.AffiliateIdParent)); //--------------------------------------------------------------- - // Name. + // Common columns. String colAffiliate_Name = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Name, aliasAffiliate); - if (!StringUtil.IsEmpty(prm.Name)) - filter.Add(colAffiliate_Name + "='" + DbQuery.FormatForCondition(prm.Name) + "'"); - // Partial Name with FirstName &LastName. String colAffiliate_FirstName = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_FirstName, aliasAffiliate); String colAffiliate_LastName = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_LastName, aliasAffiliate); + String colAffiliate_PhoneMain = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneMain, aliasAffiliate); + String colAffiliate_PhoneAlt = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneAlt, aliasAffiliate); + String colAffiliate_PhoneFax = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneFax); + String colAffiliate_PhoneMobile = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneMobile); + String colAffiliate_PhoneDirect = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneDirect); + String colAffiliate_Website = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Website, aliasAffiliate); + String colAffiliate_Email = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Email, aliasAffiliate); + String colAffiliate_Notes = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Notes, aliasAffiliate); + + //--------------------------------------------------------------- + // Generic search. + if (!StringUtil.IsEmpty(prm.Search)) + { + List searchFilter = new List(); + foreach (String rawSearchValue in prm.Search.Split(new char[] { ' ', ',', ';' })) + { + String searchValue = DbQuery.FormatForLike(StringUtil.TrimAll(rawSearchValue)); + if (StringUtil.IsEmpty(searchValue)) continue; + List searchValueFilter = new List(); + searchValueFilter.Add(colAffiliate_Name + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_FirstName + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_LastName + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_PhoneMain + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_PhoneAlt + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_Website + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_Email + " LIKE '%" + searchValue + "%'"); + searchValueFilter.Add(colAffiliate_Notes + " LIKE '%" + searchValue + "%'"); + searchFilter.Add(searchValueFilter.Concat(" OR ", "(", ")")); + } + filter.AddRange(searchFilter); + } + + //--------------------------------------------------------------- + // Name. + if (!StringUtil.IsEmpty(prm.Name)) + filter.Add(colAffiliate_Name + "='" + DbQuery.FormatForCondition(prm.Name) + "'"); + // Partial Name with FirstName & LastName. if (!StringUtil.IsEmpty(prm.NamePartial)) filter.Add(colAffiliate_Name + " LIKE '%" + DbQuery.FormatForLike(prm.NamePartial) + "%' OR " + colAffiliate_FirstName + " LIKE '%" + DbQuery.FormatForLike(prm.NamePartial) + "%' OR " + colAffiliate_LastName + " LIKE '%" + DbQuery.FormatForLike(prm.NamePartial) + "%'"); - //--------------------------------------------------------------- - // Partial Email. - String colAffiliate_Email = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Email, aliasAffiliate); + // Partial Email. if (!StringUtil.IsEmpty(prm.EmailPartial)) filter.Add(colAffiliate_Email + " LIKE '%" + DbQuery.FormatForLike(prm.EmailPartial) + "%'"); - // Partial Notes. - String colAffiliate_Notes = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Notes, aliasAffiliate); - if (!StringUtil.IsEmpty(prm.EmailPartial)) - filter.Add(colAffiliate_Notes + " LIKE '%" + DbQuery.FormatForLike(prm.NotesPartial) + "%'"); - //--------------------------------------------------------------- // Partial Phone - type of phone number held against addresses. if (!StringUtil.IsEmpty(prm.PhonePartial)) { // Search for matches in any of the phone numbers associated with the affiliate. String aliasAffAddPhone = "aAffAddPhone"; - String colAffiliate_PhoneMain = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneMain); - String colAffiliate_PhoneAlt = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneAlt); - String colAffiliate_PhoneFax = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneFax); - String colAffiliate_PhoneMobile = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneMobile); - String colAffiliate_PhoneDirect = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_PhoneDirect); String colAddress_Id = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_Id, aliasAffAddPhone); String colAddress_Owner = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_AffIdOwner, aliasAffAddPhone); String colAddress_Phone = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_Phone, aliasAffAddPhone); @@ -746,9 +776,21 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr } //--------------------------------------------------------------- + // Common properties. + + // Partial Notes. + if (!StringUtil.IsEmpty(prm.EmailPartial)) + filter.Add(colAffiliate_Notes + " LIKE '%" + DbQuery.FormatForLike(prm.NotesPartial) + "%'"); + // Status. - if (prm.StatusCat == SbmAffiliate.StatusCat.ActiveOnly) - filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + " = '" + StatusActive + "'"); + if (prm.StatusCat == SbmAffiliate.StatusCat.CurrentOnly) + filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + "='" + StatusCurrent + "'"); + else if (prm.StatusCat == SbmAffiliate.StatusCat.OnHoldOnly) + filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + "='" + SacsSystem.GetStatusCode(SaStatus.OnHold) + "'"); + else if (prm.StatusCat == SbmAffiliate.StatusCat.NotDeleted) + filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + "!='" + SacsSystem.GetStatusCode(SaStatus.Deleted) + "'"); + else if (prm.StatusCat == SbmAffiliate.StatusCat.DeletedOnly) + filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + "='" + SacsSystem.GetStatusCode(SaStatus.Deleted) + "'"); if (filter.Count < 1 && prm.IncludeDefaultFilter) filter.Add("1=1"); return filter; @@ -868,7 +910,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr // Affiliate Properties - add columns as defined in query list. if (columns.HasId((int)SbmAffiliate.Column.Id)) query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Id, aliasAffiliate)); - if (columns.HasId((int)SbmAffiliate.Column.Type_Desc)) + if (columns.HasId((int)SbmAffiliate.Column.Type) || columns.HasId((int)SbmAffiliate.Column.Type_Code) || columns.HasId((int)SbmAffiliate.Column.Type_Desc)) query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Type, aliasAffiliate)); if (columns.HasId((int)SbmAffiliate.Column.Name)) query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Name, aliasAffiliate)); diff --git a/SaBusinessMgrCoreWebPortal/Localisation/SbmPortalProperties.resx b/SaBusinessMgrCoreWebPortal/Localisation/SbmPortalProperties.resx index 694f73e..99031dd 100644 --- a/SaBusinessMgrCoreWebPortal/Localisation/SbmPortalProperties.resx +++ b/SaBusinessMgrCoreWebPortal/Localisation/SbmPortalProperties.resx @@ -117,6 +117,21 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Contact + + + Add Contact + + + Update Contact + + + Contacts + + + &copy; {0} + Alert @@ -126,12 +141,21 @@ Progress + + Any + + + Associations + Calendar Cancel + + Category + Change Password @@ -153,9 +177,15 @@ Contacts + + Create + Email + + First + Forgotten password? @@ -165,24 +195,48 @@ Remember Me + + Last + Name + + Next + No + + Notes + OK + + Page {0} of {1} + Password + + Previous + Reference + + Refresh + + + Search + Security + + Select + {0} selected @@ -195,6 +249,9 @@ Sign-out + + Type + Yes @@ -264,6 +321,9 @@ Value must not exceed {0} characters + + Change Password + Request Password diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController-Ajax.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController-Ajax.cs new file mode 100644 index 0000000..4c6706d --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController-Ajax.cs @@ -0,0 +1,332 @@ +using Microsoft.AspNetCore.Mvc; +using Sarand.BusinessMgr.Net.Core.Web.App; +using Sarand.BusinessMgr.Net.Core.Web.Helpers; +using Sarand.BusinessMgr.Net.Core.Web.Portal.Models; +using Sarand.BusinessMgr.Net.Standard.Base.App; +using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Sarand.BusinessMgr.Net.Standard.Base.Consoles; +using Sarand.BusinessMgr.Net.Standard.Base.Helpers; +using Sarand.BusinessMgr.Net.Standard.Base.Mgr; +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.Email; +using Sarand.Net.Standard.Base.Helpers; +using Sarand.Net.Standard.Base.Security; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Threading.Tasks; +using System.Web; + +namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers +{ + //======================================================================= + // Portal Contact Controller - Ajax actions. + //======================================================================= + partial class ContactController + { + //------------------------------------------------------------------- + // Affiliate data. + private class AffiliateData_List : AjaxResponse_Data.ItemData + { + public String DisplayName { get; set; } + public String TypeCode { get; set; } + public String TypeDesc { get; set; } + // Address. + public String AddressSummary { get; set; } + } + private class AffiliateData_Full : AffiliateData_List + { + // Affiliate properties. + public String PhoneMain { get; set; } = ""; + public String PhoneAlt { get; set; } = ""; + public String PhoneFax { get; set; } = ""; + public String Email { get; set; } = ""; + public String Website { get; set; } = ""; + // Ogranisation. + public SysId AffiliateIdParent { get; set; } = new SysId(); + public String Name { get; set; } = ""; + public String Abbr { get; set; } = ""; + public String Nature { get; set; } = ""; + public String Contact { get; set; } = ""; + public String CompanyNo { get; set; } = ""; + public String VatReg { get; set; } = ""; + public String PhoneDirect { get; set; } = ""; + // Individual. + public String Title { get; set; } = ""; + public String Initials { get; set; } = ""; + public String LastName { get; set; } = ""; + public String FirstName { get; set; } = ""; + public String Salutation { get; set; } = ""; + public String PhoneMobile { get; set; } = ""; + public byte BirthDay { get; set; } = 0; + public byte BirthMonth { get; set; } = 0; + public short BirthYear { get; set; } = 0; + public String MaritalStatus { get; set; } = ""; + public String Spouse { get; set; } = ""; + public String Children { get; set; } = ""; + public String Hometown { get; set; } = ""; + public SysId UserId { get; set; } = new SysId(); + } + private void PopulateAffiliateResponseData(SbmAffiliate affiliate, AffiliateData_List data) + { + data.Id = SysId.ToString(affiliate.Id); + data.DisplayName = affiliate.GetDisplayName(); + data.TypeCode = Internals.AffiliateTypeCodes.GetCodeForType(affiliate.AffiliateType); + data.TypeDesc = Internals.AffiliateTypeCodes.GetDescForType(affiliate.AffiliateType); + // Common properties. + data.Notes = affiliate.Notes; + data.StatusCode = affiliate.StatusCode; + data.StatusDesc = affiliate.StatusDesc; + } + private void PopulateAffiliateResponseData(SbmAffiliate affiliate, AffiliateData_Full data) + { + PopulateAffiliateResponseData(affiliate, data as AffiliateData_List); + // Affiliate properties. + data.PhoneMain = affiliate.PhoneMain; + data.PhoneAlt = affiliate.PhoneAlt; + data.PhoneFax = affiliate.PhoneFax; + data.Email = affiliate.Email; + data.Website = affiliate.Website; + // Ogranisation. + data.AffiliateIdParent = affiliate.AffiliateIdParent; + data.Name = affiliate.Name; + data.Abbr = affiliate.Abbr; + data.Nature = affiliate.Nature; + data.Contact = affiliate.Contact; + data.CompanyNo = affiliate.CompanyNo; + data.VatReg = affiliate.VatReg; + data.PhoneDirect = affiliate.PhoneDirect; + // Individual. + data.Title = affiliate.Title; + data.Initials = affiliate.Initials; + data.LastName = affiliate.LastName; + data.FirstName = affiliate.FirstName; + data.Salutation = affiliate.Salutation; + data.PhoneMobile = affiliate.PhoneMobile; + data.BirthDay = affiliate.BirthDay; + data.BirthMonth = affiliate.BirthMonth; + data.BirthYear = affiliate.BirthYear; + data.MaritalStatus = affiliate.MaritalStatus; + data.Spouse = affiliate.Spouse; + data.Children = affiliate.Children; + data.Hometown = affiliate.Hometown; + data.UserId = affiliate.UserId; + } + + //------------------------------------------------------------------- + // Contact list. + //------------------------------------------------------------------- + private class AjaxResponse_ContactList : AjaxResponse_Data + { + // Construction. + public AjaxResponse_ContactList(SbmModel_Portal model) : base(model) { } + // Affiliate list. + public List Affiliates { get; } = new List(); + // Reference data. + public List Statuses { get; } = new List(); + public String DefaultStatusCode { get; set; } + } + // Ajax request. + [HttpPost] + [Route("Contact/Ajax/List")] + [Route("Contact/Ajax/GetList")] + public IActionResult Ajax_ContactList(Model_Contact model) + { + String fn = GetType().Name + ".Ajax_ContactList"; + AjaxResponse_ContactList responseData = new AjaxResponse_ContactList(model); + String messageTitle = Localiser["contact.caption.contacts"]; + + // Validate session and referrer. + if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); + if (!ValidReferer) return Forbid(); + RefreshModel(model); + + // Reference data - statuses. + foreach (SaStatus status in SbmAffiliate.ValidStatuses.FindAll(item => item != SaStatus.Deleted)) + responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status)); + responseData.DefaultStatusCode = SacsSystem.GetStatusCode(SbmAffiliate.DefaultStatus); + + // Item filter. + SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params(); + prmAffiliate.Search = model.Filter.AffiliateSearch; + prmAffiliate.AffiliateType = model.GetFilterAffiliateType(); + prmAffiliate.StatusCat = SbmAffiliate.StatusCat.NotDeleted; + prmAffiliate.SortColumns.Add(AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Name)); + prmAffiliate.SortColumns.Add(AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.LastName)); + model.Paging.ItemTotal = (ulong)AccountMgr.GetAffiliateCount(prmAffiliate); + model.PopulatePagingTo(prmAffiliate.ResultsPaging); + + // Item columns. + ColumnDfn colId = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Id); + ColumnDfn colName = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Name); + ColumnDfn colAffiliateType = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Type); + ColumnDfn colPhoneMain = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.PhoneMain); + ColumnDfn colPhoneAlt = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.PhoneAlt); + ColumnDfn colNotes = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Notes); + ColumnDfn colStatus = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.Status); + ColumnDfn colCreatedOn = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.CreateOn_Date); + ColumnDfn colUpdatedOn = AccountMgr.GetAffiliateColumnDfn(SbmAffiliate.Column.UpdateOn_Date); + List columns = new List() + { + colId, + colName, + colAffiliateType, + colPhoneMain, + colPhoneAlt, + colNotes, colStatus, colCreatedOn, colUpdatedOn + }; + + // Query items. + using (DbResultSet resultSet = AccountMgr.ExecuteAffiliateQuery(columns, prmAffiliate)) + { + while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) + { + NamedValue.Map values = resultSet.GetRecordValues(); + Sbm.AffiliateType affiliateType = (Sbm.AffiliateType)values.GetLong(colAffiliateType.DataName); + AffiliateData_List affiliateData = new AffiliateData_List() + { + Id = values.GetString(colId.DataName), + DisplayName = values.GetString(colName.DataName), + TypeCode = Internals.AffiliateTypeCodes.GetCodeForType(affiliateType), + TypeDesc = Internals.AffiliateTypeCodes.GetDescForType(affiliateType), + Notes = values.GetString(colNotes.DataName), + StatusCode = values.GetString(colStatus.DataName), + StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName)) + }; + responseData.Affiliates.Add(affiliateData); + } + } + if (model.Paging.IsPaged) + model.Paging.ItemCount = (ushort)responseData.Affiliates.Count; + + // Complete response. + responseData.SetPaging(model); + responseData.Success = true; + return Json(responseData); + } + + //------------------------------------------------------------------- + // Get Affiliate ajax request. + //------------------------------------------------------------------- + private class AjaxResponse_Affiliate : AjaxResponse_Data + { + // Construction. + public AjaxResponse_Affiliate(SbmModel_Portal model) : base(model) { } + // Affiliate details. + public AffiliateData_Full affiliate { get; } = new AffiliateData_Full(); + } + //------------------------------------------------------------------- + [HttpPost] + [Route("Contact/Ajax/Item")] + [Route("Contact/Ajax/GetItem")] + public IActionResult Ajax_GetItem(Model_Contact model) + { + String fn = GetType().Name + ".Ajax_GetItem"; + String messageTitle = Localiser["affiliate.caption.affiliate"]; + AjaxResponse_Affiliate responseData = new AjaxResponse_Affiliate(model); + + // Validate session. + if (!ValidUserSession) + return GetAjaxResult_InvalidSession(responseData); + + // Identify item. + SysId affiliateId = model.AffiliateId.ToSysId(); + if (!SysId.IsValid(affiliateId)) + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"])); + + // Retrieve item. + SbmAffiliate affiliate = AccountMgr.GetAffiliateById(affiliateId); + if (affiliate == null) + { + SaLogger.Warn(fn + " Failed to retrieve affiliate for Id: '" + model.AffiliateId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } + + // Construct response. + PopulateAffiliateResponseData(affiliate, responseData.affiliate); + + // Complete response. + responseData.Success = true; + + return Json(responseData); + } + + //------------------------------------------------------------------- + // Store Affiliate ajax request. + //------------------------------------------------------------------- + [HttpPost] + [Route("Contact/Ajax/Store")] + [Route("Contact/Ajax/StoreItem")] + public IActionResult Ajax_StoreItem(Model_Contact model) + { + String fn = GetType().Name + ".Ajax_StoreItem"; + String messageTitle = Localiser["affiliate.caption.affiliate"]; + AjaxResponse_Affiliate responseData = new AjaxResponse_Affiliate(model); + + // Validate session. + if (!ValidUserSession) + return GetAjaxResult_InvalidSession(responseData); + + // Validate item properties. + model.ValidateAffiliateData(responseData); + if (responseData.HasControlErrors()) + return Json(responseData); + + // Retrieve existing item. + SysId affiliateId = model.AffiliateId.ToSysId(); + SbmAffiliate affiliate = AccountMgr.GetAffiliateById(affiliateId); + if (affiliate == null && SysId.IsValid(affiliateId)) + { + SaLogger.Warn(fn + " Failed to retrieve affiliate for Id: '" + model.AffiliateId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } + + // Create new item if required. + if (affiliate == null) + affiliate = AccountMgr.CreateAffiliate(model.GetAffiliateType()); + + // Populate item properties. + if (model.AffiliateName != null) + affiliate.Name = model.AffiliateName; + // Common properties. + if (model.AffiliateNotes != null) + affiliate.Notes = model.AffiliateNotes; + if (model.AffiliateStatus != null) + affiliate.Status = model.GetAffiliateStatus(); + + // Store item. + if (!SysId.IsValid(affiliate.Id)) + { + if (!AccountMgr.InsertAffiliate(affiliate, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to insert affiliate: " + AccountMgr.ErrorStateMessage); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); + } + } + else + { + if (!AccountMgr.UpdateAffiliate(affiliate, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to update affiliate [Id:" + SysId.ToString(affiliate.Id) + "]: " + AccountMgr.ErrorStateMessage); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); + } + } + + // Construct response. + PopulateAffiliateResponseData(affiliate, responseData.affiliate); + + // Complete response. + responseData.Success = true; + + return Json(responseData); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController.cs new file mode 100644 index 0000000..f0e42b0 --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/ContactController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Sarand.BusinessMgr.Net.Core.Web.App; +using Sarand.BusinessMgr.Net.Core.Web.Helpers; +using Sarand.BusinessMgr.Net.Core.Web.Portal.Models; +using Sarand.BusinessMgr.Net.Standard.Base.App; +using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Sarand.BusinessMgr.Net.Standard.Base.Mgr; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Security; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers +{ + //======================================================================= + public partial class ContactController : SbmController_Portal + { + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + public ContactController(SbmLocaliser localiser, ILogger logger) : + base(localiser, logger) + { + } + + //------------------------------------------------------------------- + [HttpGet] + public IActionResult Index() + { + String fn = GetType().Name + ".Index"; + + if (!ValidUserSession) + return RedirectToAction("", ""); + + // Contact page. + Model_Contact model = new Model_Contact(Localiser); + model.Paging.PageNo = SessionData.Contacts.Filter.PageNo; + model.Paging.PageLength = SessionData.Contacts.Filter.PageLength; + return View(model); + } + + //------------------------------------------------------------------- + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorViewModel(Localiser) { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/HomeController.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/HomeController.cs index 1c671eb..f68c4f4 100644 --- a/SaBusinessMgrCoreWebPortal/Portal/Controllers/HomeController.cs +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/HomeController.cs @@ -41,62 +41,54 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers // NB Only allowed if there isn't a current user session. if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed) { + // Extract parameters and validate that they are populated. String encryptedParams = GetRequestCookie(CookieName_KeepLoggedIn); KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams); + String logPrefix = fn + " 'Keep Logged In' cookie found but "; String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", "."); - - if (SysId.IsValid(keepLoggedInParams.SecurityItemId) && SysId.IsValid(keepLoggedInParams.AffiliateId)) + if (keepLoggedInParams.IsValid()) { // User may be defined on any one of the BusinessMgr databases - need to find out which. // Identify session contexts with a BusMgr database that hosts the specified email address. List sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(keepLoggedInParams.EmailAddress); foreach (ushort sessionContextIdx in sessionContextIdxs) { + // Consoles and Managers must be associated with the specific context. SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx); SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext); - - using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext)) + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext)) { - SaSecurityItem securityItemUser = securityConsole.GetUserByEmail(keepLoggedInParams.EmailAddress); - if (securityItemUser != null) + using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(keepLoggedInParams.EmailAddress)) + using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateById(keepLoggedInParams.AffiliateId)) { - if (securityItemUser.Id.Equals(keepLoggedInParams.SecurityItemId)) + if (securityItemUser != null && affiliateUser != null) { - using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext)) + if (securityItemUser.Id.Equals(keepLoggedInParams.SecurityItemId) && affiliateUser.UserId.Equals(securityItemUser.Id)) { - SbmAffiliate affiliateUser = accountMgr.GetAffiliateById(keepLoggedInParams.AffiliateId); - if (affiliateUser != null) + if (affiliateUser.IsStatusCurrent) { - if (affiliateUser.Id.Equals(keepLoggedInParams.AffiliateId)) + String errorMessage = ""; + if (SignUserIn(userContext, securityItemUser, affiliateUser, true, ref errorMessage)) { - if (affiliateUser.UserId.Equals(securityItemUser.Id)) - { - if (affiliateUser.IsStatusActive) - { - String errorMessage = ""; - if (SignUserIn(sessionContext, securityItemUser, affiliateUser, true, ref errorMessage)) - SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix); - else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix); - } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate is not active." + logSuffix); - } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate User Id does not match security item." + logSuffix); + SaLogger.Info(logPrefix + " 'Keep Logged In' cookie found." + logSuffix); + break; } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate does nto match Id." + logSuffix); + else SaLogger.Warn(logPrefix + "failed to sign user in: " + errorMessage + "." + logSuffix); } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Affiliate not found." + logSuffix); + else SaLogger.Warn(logPrefix + "Affiliate is not active." + logSuffix); } + else SaLogger.Warn(logPrefix + "Security Item and Affiliate are inconsistent. " + logSuffix); } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item does not match Id." + logSuffix); + else SaLogger.Warn(logPrefix + "Security Item or Affiliate not found. " + logSuffix); } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item not found by email." + logSuffix); } - if (ValidUserSession) break; } } - else SaLogger.Warn(fn + " Keep Logged In cookie found but Security Item or Affiliate Ids missing." + logSuffix); + else if (!StringUtil.IsEmpty(encryptedParams)) + SaLogger.Warn(fn + " Keep Logged In cookie found but with incomplete details." + logSuffix); } // Redirect to sign-in if not signed-in. diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SbmController_Portal.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SbmController_Portal.cs index 0c002f6..8220b57 100644 --- a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SbmController_Portal.cs +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SbmController_Portal.cs @@ -4,6 +4,7 @@ using Sarand.BusinessMgr.Net.Core.Web.Controllers; using Sarand.BusinessMgr.Net.Core.Web.Portal.App; using Sarand.BusinessMgr.Net.Standard.Base.App; using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Sarand.BusinessMgr.Net.Standard.Base.Mgr; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Core; @@ -27,7 +28,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers //------------------------------------------------------------------- // Protected construction. - protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger logger) : base(localiser) + protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger logger) : base(localiser) { } @@ -50,42 +51,60 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers /// - Open user session - store details against Sesion Data. /// - Set 'Keep logged in' cookie if required. ///
- protected bool SignUserIn(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, bool keepLoggedIn, ref String errorMessage) + protected bool SignUserIn(SbmUserContext userContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, bool keepLoggedIn, ref String errorMessage) { String fn = GetType().Name + ".SignUserIn"; - // Validate User Account, just in case - should have been done before arriving here! - if (affiliateUser == null) + // Validate Security Item and Affiliate, just in case - should have been done before arriving here! + if (securityItemUser == null) { - SaLogger.Error(fn + " Unable to sign account in - null user account."); - errorMessage = Localiser["security.msg.signin.failed"]; + SaLogger.Error(fn + " Unable to sign account in - null security item."); + errorMessage = Localiser["security.msg.signin.invalid"]; return false; } - if (!affiliateUser.IsStatusActive) + if (affiliateUser == null) { - SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + affiliateUser.Email); - errorMessage = Localiser["security.msg.signin.failed"]; + SaLogger.Error(fn + " Unable to sign account in - null affiliate."); + errorMessage = Localiser["security.msg.signin.invalid"]; + return false; + } + if (!affiliateUser.IsStatusCurrent) + { + SaLogger.Error(fn + " Unable to sign account in - affiliate is not active. Email: " + securityItemUser.EmailAddress); + errorMessage = Localiser["security.msg.signin.invalid"]; return false; } // 'Keep logged in' can be switched off by the application configuration. keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn; - // Log user in. - affiliateUser.KeepLoggedIn_Portal = keepLoggedIn; - //affiliateUser.LastLoginAt = DateTime.Now; - //affiliateUser.InvalidLoginCount = 0; - //affiliateUser.LastInvalidLoginAt = DateTime.MinValue; - if (!AccountMgr.UpdateAffiliate(affiliateUser, DbUpdateInfo)) + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext)) { - SaLogger.Warn(fn + " Failed to update account for " + affiliateUser.Email + ": " + AccountMgr.ErrorStateMessage); - errorMessage = Localiser["security.msg.signin.failed"]; - return false; + // Log user in. + securityItemUser.LastLogin = DateTime.Now; + securityItemUser.InvalidLoginCount = 0; + securityItemUser.LastInvalidLoginAt = DateTime.MinValue; + if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to update security item for " + securityItemUser.EmailAddress + ": " + securityConsoleForUser.ErrorStateMessage); + errorMessage = Localiser["security.msg.signin.invalid"]; + return false; + } + + // Update affiliate. + affiliateUser.KeepLoggedIn_Portal = keepLoggedIn; + if (!accountMgrForUser.UpdateAffiliate(affiliateUser, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to update affiliate for " + securityItemUser.EmailAddress + ": " + accountMgrForUser.ErrorStateMessage); + errorMessage = Localiser["security.msg.signin.invalid"]; + return false; + } + SaLogger.Info(fn + " User session opened for " + securityItemUser.EmailAddress, true); } - SaLogger.Info(fn + " User session opened for " + affiliateUser.Email, true); // Create session. - OpenUserSession(sessionContext, securityItemUser, affiliateUser); + OpenUserSession(userContext.SessionContext, securityItemUser, affiliateUser); // Send the 'Keep Logged In' cookie if requested. if (affiliateUser.KeepLoggedIn_Portal) @@ -116,7 +135,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers { // Validate user properties. if (!SysId.IsValid(affiliateUser.Id)) return false; - if (StringUtil.IsEmpty(affiliateUser.Email)) return false; + if (StringUtil.IsEmpty(securityItemUser.EmailAddress)) return false; // Close previous session. CloseUserSession(); @@ -186,10 +205,22 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers public List GetValidParams() { List values = new List(); + if (!StringUtil.IsEmpty(EmailAddress)) + values.Add("EmailAddress: " + EmailAddress); + if (SysId.IsValid(SecurityItemId)) + values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId)); if (SysId.IsValid(AffiliateId)) values.Add("UserAccountId: " + SysId.ToString(AffiliateId)); return values; } + /// Verifiy that the email and account Ids are populated and the data hasn't expired. + public bool IsValid() + { + return + !StringUtil.IsEmpty(EmailAddress) && + SysId.IsValid(SecurityItemId) && + SysId.IsValid(AffiliateId); + } } public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams) { diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController-Ajax.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController-Ajax.cs index fd0e046..ac863f4 100644 --- a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController-Ajax.cs +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController-Ajax.cs @@ -25,7 +25,7 @@ using System.Web; namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers { //======================================================================= - // Online Security Controller - Ajax actions. + // Portal Security Controller - Ajax actions. //======================================================================= partial class SecurityController { @@ -72,7 +72,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers return Json(responseData); String errorMessage = ""; - if (!SignUserIn(sessionContext, securityItemUser, affiliateUser, model.KeepMeSignedIn, ref errorMessage)) + if (!SignUserIn(userContext, securityItemUser, affiliateUser, model.KeepMeSignedIn, ref errorMessage)) return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage)); } } @@ -122,23 +122,25 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers { String fn = GetType().Name + ".GetSecurityItemForSignin"; SaSecurityItem securityItemUser = null; - using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext)) + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext)) { // Retrieve security item by email address. - securityItemUser = securityConsole.GetUserByEmail(model.EmailAddress); - if (securityItemUser != null) + using (securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress)) { - // Validate password. - if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com"))) + if (securityItemUser != null) { - // Record invalid login attempt. - securityItemUser.InvalidLoginCount++; - securityItemUser.LastInvalidLoginAt = DateTime.Now; - if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) - SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true); - SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true); - responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); - securityItemUser = null; + // Validate password. + if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com"))) + { + // Record invalid login attempt. + securityItemUser.InvalidLoginCount++; + securityItemUser.LastInvalidLoginAt = DateTime.Now; + if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) + SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true); + SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true); + responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); + securityItemUser = null; + } } } } @@ -150,18 +152,20 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers { String fn = GetType().Name + ".GetAffiliateForSignin"; SbmAffiliate affiliate = null; - using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext)) { // Identify the contact asociated with the security item. SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params(); prmAffiliate.UserId = securityItemUser.Id; - prmAffiliate.StatusCat = SbmAffiliate.StatusCat.ActiveOnly; - affiliate = accountMgr.GetAffiliate(prmAffiliate); - if (affiliate != null) + prmAffiliate.StatusCat = SbmAffiliate.StatusCat.CurrentOnly; + using (affiliate = accountMgrForUser.GetAffiliate(prmAffiliate)) { + if (affiliate != null) + { + } + if (affiliate == null && !responseData.HasControlErrors()) + responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); } - if (affiliate == null && !responseData.HasControlErrors()) - responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); } return affiliate; } @@ -255,8 +259,12 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers String messageTitle = Localiser["security.caption.change.password"]; // Validate referrer. - if (!ValidReferer) - return Forbid(); + if (!ValidReferer) return Forbid(); + + // If user is logged in, session Consoles and Managers can be used. + // If 'Request Password' parameters are specified, Consoles and Managers must be associated with the specific database for the user. + SbmSessionContext sessionContextForUser = null; + SbmUserContext_Web userContextForUser = null; // If 'Request Password' encrypted params (from email link) are present, // Session is not required and password can be set directly against the user. @@ -267,68 +275,100 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe); if (requestPasswordParams.IsValid()) { - // Retrieve user details and validate email address. - securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId); - affiliateUser = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId); - if (requestPasswordParams.IsValid(securityItemUser, affiliateUser)) + // User may be defined on any one of the BusinessMgr databases - need to find out which. + // Identify session contexts with a BusMgr database that hosts the specified email address. + List sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(requestPasswordParams.EmailAddress); + foreach (ushort sessionContextIdx in sessionContextIdxs) { - validRequestPasswordParams = true; - validSessionRequired = false; - } - else - { - SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId) + ". Email: " + requestPasswordParams.EmailAddress); - affiliateUser = null; + // Consoles and Managers must be associated with the specific context. + sessionContextForUser = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx); + userContextForUser = new SbmUserContext_Web(sessionContextForUser); + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContextForUser)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContextForUser)) + { + // Retrieve user details and validate email address. + securityItemUser = securityConsoleForUser.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId); + affiliateUser = accountMgrForUser.GetAffiliateById(requestPasswordParams.AffiliateId); + if (requestPasswordParams.IsValid(securityItemUser, affiliateUser)) + { + validRequestPasswordParams = true; + validSessionRequired = false; + break; + } + else + { + SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Email: " + requestPasswordParams.EmailAddress + ". SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId)); + if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null; + if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null; + sessionContextForUser = null; + userContextForUser = null; + } + } } } - // User must have a valid session. + // User must have a valid session if security item and affiliate not idenfied via the 'Request Password' paramaters. if (validSessionRequired && (securityItemUser == null || affiliateUser == null) && !ValidUserSession) return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"])); - // Retrieve user details if not previously populated. - if (securityItemUser == null && SessionData_Portal != null) - securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId); - if (securityItemUser == null) - return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve security item details.")); - if (affiliateUser == null && SessionData_Portal != null) - affiliateUser = AccountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId); - if (affiliateUser == null) - return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve affiliate details.")); - - // Validate password data. - model.ValidateChangePasswordData(securityItemUser, affiliateUser, responseData); - if (responseData.HasControlErrors()) - return Json(responseData); - - // Note old password. - String oldPasswordHashed = securityItemUser.PasswordHash; - - // Store password. - securityItemUser.PasswordHash = SbmPasswordHelper.GetPasswordHash(securityItemUser, model.Password); - securityItemUser.PasswordChangedAt = DateTime.Now; - // Remove new password from history if present. - for (int passwordHistoryIdx = 0; passwordHistoryIdx < securityItemUser.PasswordHistory.Count; passwordHistoryIdx++) + // User specific Consoles and Managers if User Context specified. + using (SaSecurityConsole securityConsoleForUser = userContextForUser != null ? App.CreateSecurityConsole(userContextForUser) : null) + using (SbmAccountMgr accountMgrForUser = userContextForUser != null ? App.System.CreateAccountMgr(userContextForUser) : null) { - // Compare history password to new value. - if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx])) + SaSecurityConsole securityConsole = (securityConsoleForUser ?? SecurityConsole); + SbmAccountMgr accountMgr = (accountMgrForUser ?? AccountMgr); + + // Retrieve user details if not previously populated. + if (securityItemUser == null && SessionData_Portal != null) + securityItemUser = (securityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId)); + if (securityItemUser == null) + return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve security item details.")); + if (affiliateUser == null && SessionData_Portal != null) + affiliateUser = accountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId); + if (affiliateUser == null) + return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve affiliate details.")); + + // Validate password data. + model.ValidateChangePasswordData(securityItemUser, affiliateUser, responseData); + if (responseData.HasControlErrors()) + return Json(responseData); + + // Note old password. + String oldPasswordHashed = securityItemUser.PasswordHash; + + // Store password. + securityItemUser.PasswordHash = SbmPasswordHelper.GetPasswordHash(securityItemUser, model.Password); + securityItemUser.PasswordChangedAt = DateTime.Now; + // Remove new password from history if present. + for (int passwordHistoryIdx = 0; passwordHistoryIdx < securityItemUser.PasswordHistory.Count; passwordHistoryIdx++) { - // New password found in history - remove it. - securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx); - passwordHistoryIdx--; + // Compare history password to new value. + if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx])) + { + // New password found in history - remove it. + securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx); + passwordHistoryIdx--; + } } + securityItemUser.PasswordHistory.Remove(oldPasswordHashed); + // Add the old password at the start of the history list and validate history length. + securityItemUser.PasswordHistory.Add(oldPasswordHashed); + while (securityItemUser.PasswordHistory.Count > 10) + securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1); + if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to change password for " + securityItemUser.EmailAddress + ": " + securityConsole.ErrorStateMessage); + return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"])); + } + SaLogger.Info(fn + " Password changed for " + securityItemUser.EmailAddress, true); } - securityItemUser.PasswordHistory.Remove(oldPasswordHashed); - // Add the old password at the start of the history list and validate history length. - securityItemUser.PasswordHistory.Add(oldPasswordHashed); - while (securityItemUser.PasswordHistory.Count > 10) - securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1); - if (!SecurityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) - { - SaLogger.Warn(fn + " Failed to change password for " + securityItemUser.EmailAddress + ": " + AccountMgr.ErrorStateMessage); - return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"])); - } - SaLogger.Info(fn + " Password changed for " + securityItemUser.EmailAddress, true); + + // Cleanup. NB The Security Item and/or Affiliate aren't disposed when the method + // returns above but these objects shouldn't be carrying any disposable resources. + if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null; + if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null; + sessionContextForUser = null; + userContextForUser = null; // Construct response. responseData.Success = true; @@ -350,10 +390,10 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers } // Ajax request. [HttpPost] - [Route("Security/Ajax/RequestNewPassword")] - public ActionResult Ajax_RequestNewPassword(Model_Security model) + [Route("Security/Ajax/RequestPassword")] + public ActionResult Ajax_RequestPassword(Model_Security model) { - String fn = GetType().Name + ".Ajax_RequestNewPassword"; + String fn = GetType().Name + ".Ajax_RequestPassword"; AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model); String messageTitle = Localiser["security.caption.request.password"]; @@ -372,15 +412,15 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext); // Get user security item and affiliate. - using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext)) + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext)) { - using (SaSecurityItem securityItemUser = securityConsole.GetUserByEmail(model.EmailAddress)) + using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress)) { if (securityItemUser != null) { - using (SbmAccountMgr accountMgr = App.System.CreateAccountMgr(userContext)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext)) { - using (SbmAffiliate affiliateUser = accountMgr.GetAffiliateByUserId(securityItemUser.Id)) + using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateByUserId(securityItemUser.Id)) { if (affiliateUser != null) { @@ -431,9 +471,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers //------------------------------------------------------------------- // Password request link. private static String ParamName_RequestPasswordEncrypted { get; } = "rpe"; + private static String ParamName_EmailAddress { get; } = "ema"; private static String ParamName_SecurityItemId { get; } = "sei"; private static String ParamName_AffiliateId { get; } = "afi"; - private static String ParamName_EmailAddress { get; } = "ema"; private static String ParamName_ExpiresAt { get; } = "exp"; private static String ParamName_Token { get; } = "tok"; // NB IV value is generated from the key and fixed so that it doesn't change when the application restarts. @@ -444,9 +484,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers private String GenerateRequestPasswordLink(SysId securityItemId, SysId affiliateId, String emailAddress, String requestPasswordToken) { NameValueCollection parameters = new NameValueCollection(); + parameters.Add(ParamName_EmailAddress, emailAddress); parameters.Add(ParamName_SecurityItemId, SysId.ToString(securityItemId)); parameters.Add(ParamName_AffiliateId, SysId.ToString(affiliateId)); - parameters.Add(ParamName_EmailAddress, emailAddress); parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes()), DateUtil.Format_XmlDateTime)); parameters.Add(ParamName_Token, requestPasswordToken); String link = @@ -463,34 +503,34 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers } private class RequestPasswordParams { + public String EmailAddress { get; set; } public SysId SecurityItemId { get; set; } public SysId AffiliateId { get; set; } - public String EmailAddress { get; set; } public DateTime ExpiresAt { get; set; } public String Token { get; set; } /// Retrieve the list of valid parameters in printable format [Name: Value]. public List GetValidParams() { List values = new List(); + if (!StringUtil.IsEmpty(EmailAddress)) + values.Add("Email: " + EmailAddress); if (SysId.IsValid(SecurityItemId)) values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId)); if (SysId.IsValid(AffiliateId)) values.Add("AffiliateId: " + SysId.ToString(AffiliateId)); - if (!StringUtil.IsEmpty(EmailAddress)) - values.Add("Email: " + EmailAddress); if (DateUtil.IsValid(ExpiresAt)) values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt)); if (!StringUtil.IsEmpty(Token)) values.Add("Token: " + Token); return values; } - /// Verifiy that the account Id and email are populated and the data hasn't expired. + /// Verifiy that the email and account Ids are populated and the data hasn't expired. public bool IsValid() { return + !StringUtil.IsEmpty(EmailAddress) && SysId.IsValid(SecurityItemId) && SysId.IsValid(AffiliateId) && - !StringUtil.IsEmpty(EmailAddress) && DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now; } /// Verify that the data is valid and the account details match. @@ -498,10 +538,11 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers { return IsValid() && - affiliateUser != null && + securityItemUser.EmailAddress.EqualsCI(EmailAddress) && + securityItemUser != null && securityItemUser.Id.Equals(SecurityItemId) && - affiliateUser.Id.Equals(AffiliateId) && - securityItemUser.EmailAddress.EqualsCI(EmailAddress); + affiliateUser != null && + affiliateUser.Id.Equals(AffiliateId); } } private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams) @@ -510,9 +551,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers if (!StringUtil.IsEmpty(encryptedParams)) { NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword, Base64Key_RequestPassword); + requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress); requestPasswordParams.SecurityItemId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_SecurityItemId))); requestPasswordParams.AffiliateId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_AffiliateId))); - requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress); requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt)); requestPasswordParams.Token = linkParams.Get(ParamName_Token); } diff --git a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController.cs b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController.cs index 4c82373..2e3280b 100644 --- a/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController.cs +++ b/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController.cs @@ -2,8 +2,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Sarand.BusinessMgr.Net.Core.Web.App; +using Sarand.BusinessMgr.Net.Core.Web.Helpers; using Sarand.BusinessMgr.Net.Core.Web.Portal.Models; +using Sarand.BusinessMgr.Net.Standard.Base.App; using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Sarand.BusinessMgr.Net.Standard.Base.Mgr; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Security; @@ -71,24 +74,43 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams); if (requestPasswordParams.IsValid()) { + String logPrefix = fn + " 'Request Password' parameters supplied but "; String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", "."); - using (SaSecurityItem securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId)) - using (SbmAffiliate userAccount = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId)) + // User may be defined on any one of the BusinessMgr databases - need to find out which. + // Identify session contexts with a BusMgr database that hosts the specified email address. + List sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(requestPasswordParams.EmailAddress); + foreach (ushort sessionContextIdx in sessionContextIdxs) { - if (securityItemUser != null && userAccount != null) + // Consoles and Managers must be associated with the specific context. + SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx); + SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext); + using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext)) + using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext)) { - if (userAccount.IsStatusActive) + using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(requestPasswordParams.EmailAddress)) + using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateById(requestPasswordParams.AffiliateId)) { - if (requestPasswordParams.IsValid(securityItemUser, userAccount)) + if (securityItemUser != null && affiliateUser != null) { - model.rpe = encryptedParams; - validSessionRequired = false; + if (securityItemUser.Id.Equals(requestPasswordParams.SecurityItemId) && affiliateUser.UserId.Equals(securityItemUser.Id)) + { + if (affiliateUser.IsStatusCurrent) + { + if (requestPasswordParams.IsValid(securityItemUser, affiliateUser)) + { + model.rpe = encryptedParams; + validSessionRequired = false; + break; + } + else SaLogger.Warn(logPrefix + "Security Item and Affiliate are incompatible with parameters. " + logSuffix); + } + else SaLogger.Warn(logPrefix + "Affiliate is not active. " + logSuffix); + } + else SaLogger.Warn(logPrefix + "Security Item and Affiliate are inconsistent. " + logSuffix); } - else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix); + else SaLogger.Warn(logPrefix + "Security Item or Affiliate not found. " + logSuffix); } - else SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not active. " + logSuffix); } - else SaLogger.Warn(fn + " 'Request Password' parameters supplied but security item or affiliate not found. " + logSuffix); } } } diff --git a/SaBusinessMgrCoreWebPortal/Portal/Models/Model_Contact.cs b/SaBusinessMgrCoreWebPortal/Portal/Models/Model_Contact.cs new file mode 100644 index 0000000..4bc1c8c --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Portal/Models/Model_Contact.cs @@ -0,0 +1,104 @@ +using Sarand.BusinessMgr.Net.Core.Web.App; +using Sarand.BusinessMgr.Net.Core.Web.Controllers; +using Sarand.BusinessMgr.Net.Standard.Base.App; +using Sarand.BusinessMgr.Net.Standard.Base.Bus; +using Sarand.BusinessMgr.Net.Standard.Base.Helpers; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Helpers; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Models +{ + //======================================================================= + public class Model_Contact : SbmModel_Portal + { + //------------------------------------------------------------------- + // Construction. + public Model_Contact(SbmLocaliser localiser) : + base(localiser) + { + AdditionalFiles_JS.Add("js/sbm-contact.js"); + } + + //------------------------------------------------------------------- + // Filter properties. + public class FilterData + { + public String AffiliateSearch { get; set; } + public String AffiliateType { get; set; } + } + public FilterData Filter { get; } = new FilterData(); + public Sbm.AffiliateType GetFilterAffiliateType() + { + return (Sbm.AffiliateType)Internals.AffiliateTypeCodes.GetTypeForCode(Filter.AffiliateType); + } + + //------------------------------------------------------------------- + // Affiliate properties. + public String AffiliateId { get; set; } + public String AffiliateType{ get; set; } + public Sbm.AffiliateType GetAffiliateType() + { + return (Sbm.AffiliateType)Internals.AffiliateTypeCodes.GetTypeForCode(AffiliateType); + } + public String AffiliateName { get; set; } + // Common properties. + public String AffiliateNotes { get; set; } + public String AffiliateStatus { get; set; } + public SaStatus GetAffiliateStatus() + { + return SacsSystem.GetStatusForCode(AffiliateStatus); + } + + //------------------------------------------------------------------- + // Validation. + //------------------------------------------------------------------- + // Affiliate validation. + public bool ValidateAffiliateData(SbmController_Base.AjaxResponse_Data responseData) + { + // Affiliate Type. + if (AffiliateType != null) + { + if (StringUtil.IsEmpty(AffiliateType)) + responseData.SetControlError(nameof(AffiliateType), Localiser["msg.validation.mandatory.value"]); + } + + // Affiliate Name. + if (AffiliateName != null) + { + responseData.SetControlValue(nameof(AffiliateName), AffiliateName); + if (StringUtil.IsEmpty(AffiliateName)) + responseData.SetControlError(nameof(AffiliateName), Localiser["msg.validation.mandatory.value"]); + if (StringUtil.GetLength(AffiliateName) > GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name)) + responseData.SetControlError(nameof(AffiliateName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name)]); + } + + // Affiliate Notes. + if (AffiliateNotes != null) + { + responseData.SetControlValue(nameof(AffiliateNotes), AffiliateNotes); + if (!StringUtil.IsEmpty(AffiliateNotes) && StringUtil.GetLength(AffiliateNotes) > GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes)) + responseData.SetControlError(nameof(AffiliateNotes), Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes)]); + } + + // Affiliate status. + if (AffiliateStatus != null) + { + if (StringUtil.IsEmpty(AffiliateStatus)) + responseData.SetControlError(nameof(AffiliateStatus), Localiser["msg.validation.mandatory.value"]); + else if (!SbmAffiliate.ValidStatuses.Contains(GetAffiliateStatus())) + responseData.SetControlError(nameof(AffiliateStatus), Localiser["msg.validation.invalid.value"]); + } + + return !responseData.HasControlErrors(); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaBusinessMgrCoreWebPortal/SaBusinessMgrCoreWebPortal.csproj b/SaBusinessMgrCoreWebPortal/SaBusinessMgrCoreWebPortal.csproj index 7295d67..6c1ec28 100644 --- a/SaBusinessMgrCoreWebPortal/SaBusinessMgrCoreWebPortal.csproj +++ b/SaBusinessMgrCoreWebPortal/SaBusinessMgrCoreWebPortal.csproj @@ -36,4 +36,10 @@ + + + + + + diff --git a/SaBusinessMgrCoreWebPortal/Views/Common/ResultsPaging.cshtml b/SaBusinessMgrCoreWebPortal/Views/Common/ResultsPaging.cshtml new file mode 100644 index 0000000..2914d83 --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Views/Common/ResultsPaging.cshtml @@ -0,0 +1,21 @@ +@model SbmModel_Portal +@inject SbmLocaliser Localiser + + + + +@* Results paging *@ + diff --git a/SaBusinessMgrCoreWebPortal/Views/Contact/Index.cshtml b/SaBusinessMgrCoreWebPortal/Views/Contact/Index.cshtml new file mode 100644 index 0000000..8f83627 --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Views/Contact/Index.cshtml @@ -0,0 +1,116 @@ +@model Model_Contact +@inject SbmLocaliser Localiser +@{ + /* Affiliates - Index */ + ViewData["Title"] = "Affiliates"; +} + +
+ + + +
+ +
+ +
+ + @* Filter Panel *@ + @using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateFilter", @class = "sbm-form", @onSubmit = "return false;" })) + { +
+ @* Header - Page caption, paging controls, actions. *@ +
+
+
@Localiser["contact.caption.contacts"]
+
+
+ +
+
+
@(Localiser[
+
@(Localiser[
+
+
+
+
+
+ @* Affiliate filter - name etc. *@ +
+
@Localiser["label.search"]
+
+ @Html.TextBoxFor(model => model.Filter.AffiliateSearch, new { @id = "", @class = "FilterAffiliate", @maxlength = "32" }) +
+
+ @* Affiliate Category *@ +
+
@Localiser["label.category"]
+
+ @Html.DropDownListFor(model => model.Filter.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.any"], new { @id = "", @class = "FilterAffiliateType sbm-select" }) +
+
+
+
+ } + +
+ + @* Separate filter from results when alondside each other. *@ +
+ +
+ +
+ + @* Results Panel *@ +
+
+
+ + @* element templates *@ +
+ @* Affiliate item template *@ +
+
+
+ @* Affiliate caption *@ +
+
+
+ @* Burger menu *@ + +
+
+
+
+
+
+
+
+
+
+
@Localiser["label.associations"]: 
+
+
+
+
+ @* Burger dropdown item template - ul is ignored *@ +
    +
  • XXX
  • +
+
+
+ +
+
+
+ + diff --git a/SaBusinessMgrCoreWebPortal/Views/Contact/Panel-AffiliateDialog.cshtml b/SaBusinessMgrCoreWebPortal/Views/Contact/Panel-AffiliateDialog.cshtml new file mode 100644 index 0000000..60bbd48 --- /dev/null +++ b/SaBusinessMgrCoreWebPortal/Views/Contact/Panel-AffiliateDialog.cshtml @@ -0,0 +1,56 @@ +@model Model_Contact +@inject SbmLocaliser Localiser +@{ + /* Affiliates - Affiliate dialog panel */ +} + + +@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateId", @class = "sbm-form" })) +{ + @Html.HiddenFor(model => model.AffiliateId, new { @class = "AffiliateId" }) +} +
+ + @using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliate", @class = "sbm-form", @onSubmit = "return false;" })) + { + @Html.HiddenFor(model => model.AffiliateId, new { @id = "", @class = "AffiliateId" }) + + @* Affiliate Type *@ +
+
+ +
+
+ @Html.DropDownListFor(model => model.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.select"], new { @id = "cbAffiliateType", @class = "AffiliateType sbm-select" }) +
+
+
+ + @* Affiliate Organisation Name *@ +
+
+ +
+
+ @Html.TextBoxFor(model => model.AffiliateName, new { @id = "ifAffiliateName", @class = "AffiliateName sbm-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name) }) +
+
+
+ + @* Affiliate Notes *@ +
+
+ +
+
+ @Html.TextAreaFor(model => model.AffiliateNotes, new { @id = "ifAffiliateNotes", @class = "AffiliateNotes sbm-textarea", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes) }) +
+
+
+ + } + +
diff --git a/SaBusinessMgrCoreWebPortal/Views/Shared/_Layout.cshtml b/SaBusinessMgrCoreWebPortal/Views/Shared/_Layout.cshtml index 0059dbb..e15b104 100644 --- a/SaBusinessMgrCoreWebPortal/Views/Shared/_Layout.cshtml +++ b/SaBusinessMgrCoreWebPortal/Views/Shared/_Layout.cshtml @@ -120,10 +120,10 @@ @Localiser["label.reference"]