Contacts page.

This commit is contained in:
2021-07-11 16:09:42 +01:00
parent 5043a629db
commit b8cfd87860
147 changed files with 1621 additions and 425 deletions
@@ -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();
//------------------------------------------------------------------- //-------------------------------------------------------------------
} }
//======================================================================= //=======================================================================
@@ -380,6 +380,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
return Json(responseData); return Json(responseData);
} }
//-------------------------------------------------------------------
// Base Ajax Response Data.
//------------------------------------------------------------------- //-------------------------------------------------------------------
public class AjaxResponse_Base public class AjaxResponse_Base
{ {
@@ -418,13 +420,30 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
} }
} }
//-------------------------------------------------------------------
// Ajax Response Data.
//------------------------------------------------------------------- //-------------------------------------------------------------------
public class AjaxResponse_Data : AjaxResponse_Base public class AjaxResponse_Data : AjaxResponse_Base
{ {
//---------------------------------------------------------------
// Construction. // Construction.
protected AjaxResponse_Data(SbmModel_Base model) 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. // Form data.
public class FormControlValue public class FormControlValue
{ {
@@ -477,6 +496,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
} }
return false; return false;
} }
//---------------------------------------------------------------
// Notification - briefly displays after response received. // Notification - briefly displays after response received.
public class NotificationData public class NotificationData
{ {
@@ -490,6 +511,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
Notification = new NotificationData() { Title = title, Message = message }; Notification = new NotificationData() { Title = title, Message = message };
return this; return this;
} }
//---------------------------------------------------------------
// Paged data. // Paged data.
public class PagingData public class PagingData
{ {
@@ -515,6 +538,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
{ {
Paging = new PagingData(model); Paging = new PagingData(model);
} }
//---------------------------------------------------------------
} }
//------------------------------------------------------------------- //-------------------------------------------------------------------
@@ -111,8 +111,9 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App
{ {
if (!systemCodeCache.ContainsKey(systemCode)) if (!systemCodeCache.ContainsKey(systemCode))
systemCodeCache.Add(systemCode, SbmSystem.GetCodesOfType(UserContext, systemCode)); systemCodeCache.Add(systemCode, SbmSystem.GetCodesOfType(UserContext, systemCode));
return systemCodeCache.GetValueOrDefault(systemCode, new List<TypeCodeDesc>()); return new List<TypeCodeDesc>(systemCodeCache.GetValueOrDefault(systemCode, new List<TypeCodeDesc>()));
} }
public List<TypeCodeDesc> AffiliateTypeCodes { get { return GetCodesOfType(Sbm.SystemCode.AffiliateType); } }
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Managers. // Managers.
@@ -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.App;
using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Core;
@@ -35,6 +36,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Properties. // Properties.
[JsonIgnore]
public SbmUserContext UserContext { get; } public SbmUserContext UserContext { get; }
//------------------------------------------------------------------- //-------------------------------------------------------------------
@@ -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 namespace Sarand.BusinessMgr.Net.Standard.Base.App
{ {
@@ -7,6 +8,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.App
{ {
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Construction. // Construction.
[JsonConstructor]
protected SbmSessionContext(ushort index, Sbm.Application application) : base(index) protected SbmSessionContext(ushort index, Sbm.Application application) : base(index)
{ {
Index = index; Index = index;
@@ -19,6 +19,8 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus
Pos, Pos,
Id, Id,
// Affiliate. // Affiliate.
Type,
Type_Code,
Type_Desc, Type_Desc,
Name, Name,
Abbrev, Abbrev,
@@ -734,7 +736,11 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus
// List parameters. // List parameters.
public enum StatusCat public enum StatusCat
{ {
Any, ActiveOnly, Any,
CurrentOnly,
OnHoldOnly,
NotDeleted,
DeletedOnly,
} }
/// <summary> /// <summary>
/// Item list parameters. /// Item list parameters.
@@ -748,6 +754,8 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Bus
public Sbm.AffiliateType AffiliateType { get; set; } = Sbm.AffiliateType.Any; public Sbm.AffiliateType AffiliateType { get; set; } = Sbm.AffiliateType.Any;
public SysId AffiliateIdParent { get; set; } public SysId AffiliateIdParent { get; set; }
public SysId UserId { get; set; } public SysId UserId { get; set; }
/// <summary>Generic search value - name, phone, website, address etc.</summary>
public String Search { get; set; }
public String Name { get; set; } public String Name { get; set; }
public String NamePartial { get; set; } public String NamePartial { get; set; }
public String PhonePartial { get; set; } public String PhonePartial { get; set; }
@@ -378,10 +378,13 @@ namespace Sarand.BusinessMgr.Net.Standard.Base.Mgr
switch ((SbmAffiliate.Column)column.Id) switch ((SbmAffiliate.Column)column.Id)
{ {
// Affiliate type description. // Affiliate type description.
case SbmAffiliate.Column.Type_Code:
case SbmAffiliate.Column.Type_Desc: case SbmAffiliate.Column.Type_Desc:
{ {
Sbm.AffiliateType affiliateType = (Sbm.AffiliateType)values.GetValue(column.DataName).AsLong(); 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; break;
@@ -1,3 +0,0 @@
<div class="sax-email-header">
<div class="sax-content">Email Footer</div>
</div>
@@ -16,6 +16,7 @@
<div class="sax-para">Follow <span style="font-weight:bold;"><a href="[#Link.RequestPassword]">this link</a></span> to set a new password against your account.</div> <div class="sax-para">Follow <span style="font-weight:bold;"><a href="[#Link.RequestPassword]">this link</a></span> to set a new password against your account.</div>
<div class="sax-para">This link will expire in [#Link.ExpiresMinutes] minutes.</div> <div class="sax-para">This link will expire in [#Link.ExpiresMinutes] minutes.</div>
<div class="sax-para">Thank you.</div> <div class="sax-para">Thank you.</div>
<div class="sax-para">Sarand BusinessMgr team</div>
</div> </div>
</div> </div>
[#Email.Footer] [#Email.Footer]
@@ -0,0 +1,3 @@
<div class="sax-email-footer">
<div class="sax-content"></div>
</div>
@@ -1,9 +1,14 @@
body html
{
font-size:10px;
}
body
{ {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: Verdana, Helvetica, Arial, sans-serif; font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 16px; font-size: 1.6rem;
line-height: 1.5; line-height: 1.5;
background-color: #fff; background-color: #fff;
} }
@@ -32,6 +37,7 @@ td
.sax-email-header .sax-content .sax-email-header .sax-content
{ {
font-size:2.3rem;
} }
.sax-email-body .sax-email-body
@@ -41,7 +47,7 @@ td
.sax-email-panel-caption .sax-email-panel-caption
{ {
margin: 12px 0; margin: 12px 0;
font-size: 40px; font-size: 3.2rem;
font-weight: bold; font-weight: bold;
color: #000; color: #000;
background-color: transparent; background-color: transparent;
@@ -60,7 +66,7 @@ td
td.sax-label td.sax-label
{ {
padding: 4px 12px; padding: 4px 12px;
font-size: 16px; font-size: 1.6rem;
text-align: right; text-align: right;
color: #808080; color: #808080;
white-space: nowrap; white-space: nowrap;
@@ -69,7 +75,7 @@ td.sax-label
td.sax-value td.sax-value
{ {
padding-top: 4px; padding-top: 4px;
font-size: 16px; font-size: 1.6rem;
} }
.sax-email-footer .sax-email-footer
@@ -81,6 +87,7 @@ td.sax-value
.sax-email-footer .sax-content .sax-email-footer .sax-content
{ {
padding:12px 24px; padding:12px 24px;
font-size:1.2rem;
} }
.sax-email-footer .sax-content .sax-footer-lower .sax-email-footer .sax-content .sax-footer-lower

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 342 B

Before

Width:  |  Height:  |  Size: 287 B

After

Width:  |  Height:  |  Size: 287 B

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 283 B

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 774 B

@@ -1,17 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>Sarand.BusinessMgr.Net.Standard</RootNamespace> <RootNamespace>Sarand.BusinessMgr.Net.Standard</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardAccounts\SacsStandardAccounts.csproj" /> <ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardAccounts\SacsStandardAccounts.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" /> <ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
</Project> </Project>
@@ -398,12 +398,18 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
columnDfn.ReadOnly = true; columnDfn.ReadOnly = true;
break; break;
// Affiliate Properties. // 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: case SbmAffiliate.Column.Type_Desc:
SetAffiliateColDfn(columnDfn, "Type", 70, SbmDbDfn.ColumnIdx.Affiliate_Type); SetAffiliateColDfn(columnDfn, "Type", 80, SbmDbDfn.ColumnIdx.Affiliate_Type, ColumnAlignment.Center);
break; break;
case SbmAffiliate.Column.Name: case SbmAffiliate.Column.Name:
SetAffiliateColDfn(columnDfn, "Name", 160, SbmDbDfn.ColumnIdx.Affiliate_Name); SetAffiliateColDfn(columnDfn, "Name", 160, SbmDbDfn.ColumnIdx.Affiliate_Name);
break;/* break;
case SbmAffiliate.Column.Abbrev: case SbmAffiliate.Column.Abbrev:
SetAffiliateColDfn(columnDfn, "Abbrev", 90, SbmDbDfn.ColumnIdx.Affiliate_Abbrev); SetAffiliateColDfn(columnDfn, "Abbrev", 90, SbmDbDfn.ColumnIdx.Affiliate_Abbrev);
break; break;
@@ -421,16 +427,16 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
break; break;
case SbmAffiliate.Column.Salutation: case SbmAffiliate.Column.Salutation:
SetAffiliateColDfn(columnDfn, "Salutation", 50, SbmDbDfn.ColumnIdx.Affiliate_Salutation); SetAffiliateColDfn(columnDfn, "Salutation", 50, SbmDbDfn.ColumnIdx.Affiliate_Salutation);
break;*/ break;
case SbmAffiliate.Column.Nature: case SbmAffiliate.Column.Nature:
SetAffiliateColDfn(columnDfn, "Nature", 120, SbmDbDfn.ColumnIdx.Affiliate_Nature); SetAffiliateColDfn(columnDfn, "Nature", 120, SbmDbDfn.ColumnIdx.Affiliate_Nature);
break;/* break;
case SbmAffiliate.Column.CompanyNo: case SbmAffiliate.Column.CompanyNo:
SetAffiliateColDfn(columnDfn, "CompanyNo", 50, SbmDbDfn.ColumnIdx.Affiliate_CompanyNo); SetAffiliateColDfn(columnDfn, "CompanyNo", 50, SbmDbDfn.ColumnIdx.Affiliate_CompanyNo);
break; break;
case SbmAffiliate.Column.VatReg: case SbmAffiliate.Column.VatReg:
SetAffiliateColDfn(columnDfn, "VatReg", 80, SbmDbDfn.ColumnIdx.Affiliate_VatReg); SetAffiliateColDfn(columnDfn, "VatReg", 80, SbmDbDfn.ColumnIdx.Affiliate_VatReg);
break;*/ break;
case SbmAffiliate.Column.Contact: case SbmAffiliate.Column.Contact:
SetAffiliateColDfn(columnDfn, "Contact", 120, SbmDbDfn.ColumnIdx.Affiliate_Contact); SetAffiliateColDfn(columnDfn, "Contact", 120, SbmDbDfn.ColumnIdx.Affiliate_Contact);
break; break;
@@ -486,12 +492,14 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
case SbmAffiliate.Column.Address_Postcode: case SbmAffiliate.Column.Address_Postcode:
SetAffiliateColDfn(columnDfn, "Postcode", 65, SbmDbDfn.ColumnIdx.Address_Postcode, ColumnAlignment.Center, DataValue.DataType.String, aliasAffiliate_AddPrim, prefixAffiliate_AddPrim); SetAffiliateColDfn(columnDfn, "Postcode", 65, SbmDbDfn.ColumnIdx.Address_Postcode, ColumnAlignment.Center, DataValue.DataType.String, aliasAffiliate_AddPrim, prefixAffiliate_AddPrim);
break; break;
//----------------------------------------------------------------- //-----------------------------------------------------------------
// Derived columns. // Derived columns.
case SbmAffiliate.Column.GroupMembers: case SbmAffiliate.Column.GroupMembers:
SetAffiliateColDfn(columnDfn, "Members", 90, SbmDbDfn.ColumnIdx.Unknown, ColumnAlignment.Right); SetAffiliateColDfn(columnDfn, "Members", 90, SbmDbDfn.ColumnIdx.Unknown, ColumnAlignment.Right);
columnDfn.DataName = colNameAffiliate_GroupMembers; columnDfn.DataName = colNameAffiliate_GroupMembers;
break; break;
//----------------------------------------------------------------- //-----------------------------------------------------------------
// Common properties. // Common properties.
case SbmAffiliate.Column.Notes: case SbmAffiliate.Column.Notes:
@@ -556,38 +564,60 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
filter.Add(colAffiliate_AffIdParent + "=" + SysId.ToString(prm.AffiliateIdParent)); filter.Add(colAffiliate_AffIdParent + "=" + SysId.ToString(prm.AffiliateIdParent));
//--------------------------------------------------------------- //---------------------------------------------------------------
// Name. // Common columns.
String colAffiliate_Name = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Name, aliasAffiliate); 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_FirstName = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_FirstName, aliasAffiliate);
String colAffiliate_LastName = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_LastName, 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<String> searchFilter = new List<string>();
foreach (String rawSearchValue in prm.Search.Split(new char[] { ' ', ',', ';' }))
{
String searchValue = DbQuery.FormatForLike(StringUtil.TrimAll(rawSearchValue));
if (StringUtil.IsEmpty(searchValue)) continue;
List<String> searchValueFilter = new List<string>();
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)) 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) + "%'"); 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.
// Partial Email.
String colAffiliate_Email = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Email, aliasAffiliate);
if (!StringUtil.IsEmpty(prm.EmailPartial)) if (!StringUtil.IsEmpty(prm.EmailPartial))
filter.Add(colAffiliate_Email + " LIKE '%" + DbQuery.FormatForLike(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. // Partial Phone - type of phone number held against addresses.
if (!StringUtil.IsEmpty(prm.PhonePartial)) if (!StringUtil.IsEmpty(prm.PhonePartial))
{ {
// Search for matches in any of the phone numbers associated with the affiliate. // Search for matches in any of the phone numbers associated with the affiliate.
String aliasAffAddPhone = "aAffAddPhone"; 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_Id = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_Id, aliasAffAddPhone);
String colAddress_Owner = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_AffIdOwner, aliasAffAddPhone); String colAddress_Owner = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_AffIdOwner, aliasAffAddPhone);
String colAddress_Phone = SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Address_Phone, 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. // Status.
if (prm.StatusCat == SbmAffiliate.StatusCat.ActiveOnly) if (prm.StatusCat == SbmAffiliate.StatusCat.CurrentOnly)
filter.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Status, aliasAffiliate) + " = '" + StatusActive + "'"); 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"); if (filter.Count < 1 && prm.IncludeDefaultFilter) filter.Add("1=1");
return filter; return filter;
@@ -868,7 +910,7 @@ namespace Sarand.BusinessMgr.Net.Standard.Dbas.Mgr
// Affiliate Properties - add columns as defined in query list. // Affiliate Properties - add columns as defined in query list.
if (columns.HasId((int)SbmAffiliate.Column.Id)) if (columns.HasId((int)SbmAffiliate.Column.Id))
query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Id, aliasAffiliate)); 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)); query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Type, aliasAffiliate));
if (columns.HasId((int)SbmAffiliate.Column.Name)) if (columns.HasId((int)SbmAffiliate.Column.Name))
query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Name, aliasAffiliate)); query.Columns.Add(SbmDbDfn.GetColumnNameQual(SbmDbDfn.ColumnIdx.Affiliate_Name, aliasAffiliate));
@@ -117,6 +117,21 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="contact.caption.contact" xml:space="preserve">
<value>Contact</value>
</data>
<data name="contact.caption.contact.create" xml:space="preserve">
<value>Add Contact</value>
</data>
<data name="contact.caption.contact.update" xml:space="preserve">
<value>Update Contact</value>
</data>
<data name="contact.caption.contacts" xml:space="preserve">
<value>Contacts</value>
</data>
<data name="copyright.symbol.year.html" xml:space="preserve">
<value>&amp;copy; {0}</value>
</data>
<data name="dialog.title.alert" xml:space="preserve"> <data name="dialog.title.alert" xml:space="preserve">
<value>Alert</value> <value>Alert</value>
</data> </data>
@@ -126,12 +141,21 @@
<data name="dialog.title.progress" xml:space="preserve"> <data name="dialog.title.progress" xml:space="preserve">
<value>Progress</value> <value>Progress</value>
</data> </data>
<data name="label.any" xml:space="preserve">
<value>Any</value>
</data>
<data name="label.associations" xml:space="preserve">
<value>Associations</value>
</data>
<data name="label.calendar" xml:space="preserve"> <data name="label.calendar" xml:space="preserve">
<value>Calendar</value> <value>Calendar</value>
</data> </data>
<data name="label.cancel" xml:space="preserve"> <data name="label.cancel" xml:space="preserve">
<value>Cancel</value> <value>Cancel</value>
</data> </data>
<data name="label.category" xml:space="preserve">
<value>Category</value>
</data>
<data name="label.change.password" xml:space="preserve"> <data name="label.change.password" xml:space="preserve">
<value>Change Password</value> <value>Change Password</value>
</data> </data>
@@ -153,9 +177,15 @@
<data name="label.contacts" xml:space="preserve"> <data name="label.contacts" xml:space="preserve">
<value>Contacts</value> <value>Contacts</value>
</data> </data>
<data name="label.create" xml:space="preserve">
<value>Create</value>
</data>
<data name="label.email" xml:space="preserve"> <data name="label.email" xml:space="preserve">
<value>Email</value> <value>Email</value>
</data> </data>
<data name="label.first" xml:space="preserve">
<value>First</value>
</data>
<data name="label.forgotten.password" xml:space="preserve"> <data name="label.forgotten.password" xml:space="preserve">
<value>Forgotten password?</value> <value>Forgotten password?</value>
</data> </data>
@@ -165,24 +195,48 @@
<data name="label.keep.signed.in" xml:space="preserve"> <data name="label.keep.signed.in" xml:space="preserve">
<value>Remember Me</value> <value>Remember Me</value>
</data> </data>
<data name="label.last" xml:space="preserve">
<value>Last</value>
</data>
<data name="label.name" xml:space="preserve"> <data name="label.name" xml:space="preserve">
<value>Name</value> <value>Name</value>
</data> </data>
<data name="label.next" xml:space="preserve">
<value>Next</value>
</data>
<data name="label.no" xml:space="preserve"> <data name="label.no" xml:space="preserve">
<value>No</value> <value>No</value>
</data> </data>
<data name="label.notes" xml:space="preserve">
<value>Notes</value>
</data>
<data name="label.ok" xml:space="preserve"> <data name="label.ok" xml:space="preserve">
<value>OK</value> <value>OK</value>
</data> </data>
<data name="label.page.info" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="label.password" xml:space="preserve"> <data name="label.password" xml:space="preserve">
<value>Password</value> <value>Password</value>
</data> </data>
<data name="label.previous" xml:space="preserve">
<value>Previous</value>
</data>
<data name="label.reference" xml:space="preserve"> <data name="label.reference" xml:space="preserve">
<value>Reference</value> <value>Reference</value>
</data> </data>
<data name="label.refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="label.search" xml:space="preserve">
<value>Search</value>
</data>
<data name="label.security" xml:space="preserve"> <data name="label.security" xml:space="preserve">
<value>Security</value> <value>Security</value>
</data> </data>
<data name="label.select" xml:space="preserve">
<value>Select</value>
</data>
<data name="label.selected.count" xml:space="preserve"> <data name="label.selected.count" xml:space="preserve">
<value>{0} selected</value> <value>{0} selected</value>
</data> </data>
@@ -195,6 +249,9 @@
<data name="label.signout" xml:space="preserve"> <data name="label.signout" xml:space="preserve">
<value>Sign-out</value> <value>Sign-out</value>
</data> </data>
<data name="label.type" xml:space="preserve">
<value>Type</value>
</data>
<data name="label.yes" xml:space="preserve"> <data name="label.yes" xml:space="preserve">
<value>Yes</value> <value>Yes</value>
</data> </data>
@@ -264,6 +321,9 @@
<data name="msg.validation.maximum.length" xml:space="preserve"> <data name="msg.validation.maximum.length" xml:space="preserve">
<value>Value must not exceed {0} characters</value> <value>Value must not exceed {0} characters</value>
</data> </data>
<data name="security.caption.change.password" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="security.caption.request.password" xml:space="preserve"> <data name="security.caption.request.password" xml:space="preserve">
<value>Request Password</value> <value>Request Password</value>
</data> </data>
@@ -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<AffiliateData_List> Affiliates { get; } = new List<AffiliateData_List>();
// Reference data.
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
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<ColumnDfn> columns = new List<ColumnDfn>()
{
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);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -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<ContactController> 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 });
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -41,62 +41,54 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
// NB Only allowed if there isn't a current user session. // NB Only allowed if there isn't a current user session.
if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed) if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
{ {
// Extract parameters and validate that they are populated.
String encryptedParams = GetRequestCookie(CookieName_KeepLoggedIn); String encryptedParams = GetRequestCookie(CookieName_KeepLoggedIn);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams); KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logPrefix = fn + " 'Keep Logged In' cookie found but ";
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", "."); String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (keepLoggedInParams.IsValid())
if (SysId.IsValid(keepLoggedInParams.SecurityItemId) && SysId.IsValid(keepLoggedInParams.AffiliateId))
{ {
// User may be defined on any one of the BusinessMgr databases - need to find out which. // 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. // Identify session contexts with a BusMgr database that hosts the specified email address.
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(keepLoggedInParams.EmailAddress); List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(keepLoggedInParams.EmailAddress);
foreach (ushort sessionContextIdx in sessionContextIdxs) foreach (ushort sessionContextIdx in sessionContextIdxs)
{ {
// Consoles and Managers must be associated with the specific context.
SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx); SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext); SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext)) using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
{ {
SaSecurityItem securityItemUser = securityConsole.GetUserByEmail(keepLoggedInParams.EmailAddress); using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(keepLoggedInParams.EmailAddress))
if (securityItemUser != null) 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.IsStatusCurrent)
if (affiliateUser != null)
{ {
if (affiliateUser.Id.Equals(keepLoggedInParams.AffiliateId)) String errorMessage = "";
if (SignUserIn(userContext, securityItemUser, affiliateUser, true, ref errorMessage))
{ {
if (affiliateUser.UserId.Equals(securityItemUser.Id)) SaLogger.Info(logPrefix + " 'Keep Logged In' cookie found." + logSuffix);
{ break;
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);
} }
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) if (ValidUserSession)
break; 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. // Redirect to sign-in if not signed-in.
@@ -4,6 +4,7 @@ using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Core.Web.Portal.App; using Sarand.BusinessMgr.Net.Core.Web.Portal.App;
using Sarand.BusinessMgr.Net.Standard.Base.App; using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus; 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.App;
using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Core;
@@ -27,7 +28,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Protected construction. // Protected construction.
protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<HomeController> logger) : base(localiser) protected SbmController_Portal(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Portal> logger) : base(localiser)
{ {
} }
@@ -50,42 +51,60 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
/// <para>- Open user session - store details against Sesion Data.</para> /// <para>- Open user session - store details against Sesion Data.</para>
/// <para>- Set 'Keep logged in' cookie if required.</para> /// <para>- Set 'Keep logged in' cookie if required.</para>
/// </summary> /// </summary>
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"; String fn = GetType().Name + ".SignUserIn";
// Validate User Account, just in case - should have been done before arriving here! // Validate Security Item and Affiliate, just in case - should have been done before arriving here!
if (affiliateUser == null) if (securityItemUser == null)
{ {
SaLogger.Error(fn + " Unable to sign account in - null user account."); SaLogger.Error(fn + " Unable to sign account in - null security item.");
errorMessage = Localiser["security.msg.signin.failed"]; errorMessage = Localiser["security.msg.signin.invalid"];
return false; return false;
} }
if (!affiliateUser.IsStatusActive) if (affiliateUser == null)
{ {
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + affiliateUser.Email); SaLogger.Error(fn + " Unable to sign account in - null affiliate.");
errorMessage = Localiser["security.msg.signin.failed"]; 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; return false;
} }
// 'Keep logged in' can be switched off by the application configuration. // 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn; keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
// Log user in. using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
affiliateUser.KeepLoggedIn_Portal = keepLoggedIn; using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
//affiliateUser.LastLoginAt = DateTime.Now;
//affiliateUser.InvalidLoginCount = 0;
//affiliateUser.LastInvalidLoginAt = DateTime.MinValue;
if (!AccountMgr.UpdateAffiliate(affiliateUser, DbUpdateInfo))
{ {
SaLogger.Warn(fn + " Failed to update account for " + affiliateUser.Email + ": " + AccountMgr.ErrorStateMessage); // Log user in.
errorMessage = Localiser["security.msg.signin.failed"]; securityItemUser.LastLogin = DateTime.Now;
return false; 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. // Create session.
OpenUserSession(sessionContext, securityItemUser, affiliateUser); OpenUserSession(userContext.SessionContext, securityItemUser, affiliateUser);
// Send the 'Keep Logged In' cookie if requested. // Send the 'Keep Logged In' cookie if requested.
if (affiliateUser.KeepLoggedIn_Portal) if (affiliateUser.KeepLoggedIn_Portal)
@@ -116,7 +135,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
{ {
// Validate user properties. // Validate user properties.
if (!SysId.IsValid(affiliateUser.Id)) return false; if (!SysId.IsValid(affiliateUser.Id)) return false;
if (StringUtil.IsEmpty(affiliateUser.Email)) return false; if (StringUtil.IsEmpty(securityItemUser.EmailAddress)) return false;
// Close previous session. // Close previous session.
CloseUserSession(); CloseUserSession();
@@ -186,10 +205,22 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
public List<String> GetValidParams() public List<String> GetValidParams()
{ {
List<String> values = new List<String>(); List<String> values = new List<String>();
if (!StringUtil.IsEmpty(EmailAddress))
values.Add("EmailAddress: " + EmailAddress);
if (SysId.IsValid(SecurityItemId))
values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId));
if (SysId.IsValid(AffiliateId)) if (SysId.IsValid(AffiliateId))
values.Add("UserAccountId: " + SysId.ToString(AffiliateId)); values.Add("UserAccountId: " + SysId.ToString(AffiliateId));
return values; return values;
} }
/// <summary>Verifiy that the email and account Ids are populated and the data hasn't expired.</summary>
public bool IsValid()
{
return
!StringUtil.IsEmpty(EmailAddress) &&
SysId.IsValid(SecurityItemId) &&
SysId.IsValid(AffiliateId);
}
} }
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams) public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
{ {
@@ -25,7 +25,7 @@ using System.Web;
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
{ {
//======================================================================= //=======================================================================
// Online Security Controller - Ajax actions. // Portal Security Controller - Ajax actions.
//======================================================================= //=======================================================================
partial class SecurityController partial class SecurityController
{ {
@@ -72,7 +72,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
return Json(responseData); return Json(responseData);
String errorMessage = ""; 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)); 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"; String fn = GetType().Name + ".GetSecurityItemForSignin";
SaSecurityItem securityItemUser = null; SaSecurityItem securityItemUser = null;
using (SaSecurityConsole securityConsole = App.CreateSecurityConsole(userContext)) using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
{ {
// Retrieve security item by email address. // Retrieve security item by email address.
securityItemUser = securityConsole.GetUserByEmail(model.EmailAddress); using (securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress))
if (securityItemUser != null)
{ {
// Validate password. if (securityItemUser != null)
if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com")))
{ {
// Record invalid login attempt. // Validate password.
securityItemUser.InvalidLoginCount++; if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@sarand.com")))
securityItemUser.LastInvalidLoginAt = DateTime.Now; {
if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) // Record invalid login attempt.
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true); securityItemUser.InvalidLoginCount++;
SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true); securityItemUser.LastInvalidLoginAt = DateTime.Now;
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
securityItemUser = null; 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"; String fn = GetType().Name + ".GetAffiliateForSignin";
SbmAffiliate affiliate = null; 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. // Identify the contact asociated with the security item.
SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params(); SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params();
prmAffiliate.UserId = securityItemUser.Id; prmAffiliate.UserId = securityItemUser.Id;
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.ActiveOnly; prmAffiliate.StatusCat = SbmAffiliate.StatusCat.CurrentOnly;
affiliate = accountMgr.GetAffiliate(prmAffiliate); using (affiliate = accountMgrForUser.GetAffiliate(prmAffiliate))
if (affiliate != null)
{ {
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; return affiliate;
} }
@@ -255,8 +259,12 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
String messageTitle = Localiser["security.caption.change.password"]; String messageTitle = Localiser["security.caption.change.password"];
// Validate referrer. // Validate referrer.
if (!ValidReferer) if (!ValidReferer) return Forbid();
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, // If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user. // 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); RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
if (requestPasswordParams.IsValid()) if (requestPasswordParams.IsValid())
{ {
// Retrieve user details and validate email address. // User may be defined on any one of the BusinessMgr databases - need to find out which.
securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId); // Identify session contexts with a BusMgr database that hosts the specified email address.
affiliateUser = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId); List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(requestPasswordParams.EmailAddress);
if (requestPasswordParams.IsValid(securityItemUser, affiliateUser)) foreach (ushort sessionContextIdx in sessionContextIdxs)
{ {
validRequestPasswordParams = true; // Consoles and Managers must be associated with the specific context.
validSessionRequired = false; sessionContextForUser = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
} userContextForUser = new SbmUserContext_Web(sessionContextForUser);
else using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContextForUser))
{ using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContextForUser))
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId) + ". Email: " + requestPasswordParams.EmailAddress); {
affiliateUser = null; // 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) if (validSessionRequired && (securityItemUser == null || affiliateUser == null) && !ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"])); return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Retrieve user details if not previously populated. // User specific Consoles and Managers if User Context specified.
if (securityItemUser == null && SessionData_Portal != null) using (SaSecurityConsole securityConsoleForUser = userContextForUser != null ? App.CreateSecurityConsole(userContextForUser) : null)
securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId); using (SbmAccountMgr accountMgrForUser = userContextForUser != null ? App.System.CreateAccountMgr(userContextForUser) : null)
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++)
{ {
// Compare history password to new value. SaSecurityConsole securityConsole = (securityConsoleForUser ?? SecurityConsole);
if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx])) 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. // Compare history password to new value.
securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx); if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx]))
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. // Cleanup. NB The Security Item and/or Affiliate aren't disposed when the method
securityItemUser.PasswordHistory.Add(oldPasswordHashed); // returns above but these objects shouldn't be carrying any disposable resources.
while (securityItemUser.PasswordHistory.Count > 10) if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null;
securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1); if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null;
if (!SecurityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo)) sessionContextForUser = null;
{ userContextForUser = null;
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);
// Construct response. // Construct response.
responseData.Success = true; responseData.Success = true;
@@ -350,10 +390,10 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
} }
// Ajax request. // Ajax request.
[HttpPost] [HttpPost]
[Route("Security/Ajax/RequestNewPassword")] [Route("Security/Ajax/RequestPassword")]
public ActionResult Ajax_RequestNewPassword(Model_Security model) 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); AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
String messageTitle = Localiser["security.caption.request.password"]; 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); SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
// Get user security item and affiliate. // 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) 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) if (affiliateUser != null)
{ {
@@ -431,9 +471,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
//------------------------------------------------------------------- //-------------------------------------------------------------------
// Password request link. // Password request link.
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe"; 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_SecurityItemId { get; } = "sei";
private static String ParamName_AffiliateId { get; } = "afi"; 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_ExpiresAt { get; } = "exp";
private static String ParamName_Token { get; } = "tok"; 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. // 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) private String GenerateRequestPasswordLink(SysId securityItemId, SysId affiliateId, String emailAddress, String requestPasswordToken)
{ {
NameValueCollection parameters = new NameValueCollection(); NameValueCollection parameters = new NameValueCollection();
parameters.Add(ParamName_EmailAddress, emailAddress);
parameters.Add(ParamName_SecurityItemId, SysId.ToString(securityItemId)); parameters.Add(ParamName_SecurityItemId, SysId.ToString(securityItemId));
parameters.Add(ParamName_AffiliateId, SysId.ToString(affiliateId)); 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_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes()), DateUtil.Format_XmlDateTime));
parameters.Add(ParamName_Token, requestPasswordToken); parameters.Add(ParamName_Token, requestPasswordToken);
String link = String link =
@@ -463,34 +503,34 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
} }
private class RequestPasswordParams private class RequestPasswordParams
{ {
public String EmailAddress { get; set; }
public SysId SecurityItemId { get; set; } public SysId SecurityItemId { get; set; }
public SysId AffiliateId { get; set; } public SysId AffiliateId { get; set; }
public String EmailAddress { get; set; }
public DateTime ExpiresAt { get; set; } public DateTime ExpiresAt { get; set; }
public String Token { get; set; } public String Token { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary> /// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<String> GetValidParams() public List<String> GetValidParams()
{ {
List<String> values = new List<String>(); List<String> values = new List<String>();
if (!StringUtil.IsEmpty(EmailAddress))
values.Add("Email: " + EmailAddress);
if (SysId.IsValid(SecurityItemId)) if (SysId.IsValid(SecurityItemId))
values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId)); values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId));
if (SysId.IsValid(AffiliateId)) if (SysId.IsValid(AffiliateId))
values.Add("AffiliateId: " + SysId.ToString(AffiliateId)); values.Add("AffiliateId: " + SysId.ToString(AffiliateId));
if (!StringUtil.IsEmpty(EmailAddress))
values.Add("Email: " + EmailAddress);
if (DateUtil.IsValid(ExpiresAt)) if (DateUtil.IsValid(ExpiresAt))
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt)); values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
if (!StringUtil.IsEmpty(Token)) if (!StringUtil.IsEmpty(Token))
values.Add("Token: " + Token); values.Add("Token: " + Token);
return values; return values;
} }
/// <summary>Verifiy that the account Id and email are populated and the data hasn't expired.</summary> /// <summary>Verifiy that the email and account Ids are populated and the data hasn't expired.</summary>
public bool IsValid() public bool IsValid()
{ {
return return
!StringUtil.IsEmpty(EmailAddress) &&
SysId.IsValid(SecurityItemId) && SysId.IsValid(SecurityItemId) &&
SysId.IsValid(AffiliateId) && SysId.IsValid(AffiliateId) &&
!StringUtil.IsEmpty(EmailAddress) &&
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now; DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
} }
/// <summary>Verify that the data is valid and the account details match.</summary> /// <summary>Verify that the data is valid and the account details match.</summary>
@@ -498,10 +538,11 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
{ {
return return
IsValid() && IsValid() &&
affiliateUser != null && securityItemUser.EmailAddress.EqualsCI(EmailAddress) &&
securityItemUser != null &&
securityItemUser.Id.Equals(SecurityItemId) && securityItemUser.Id.Equals(SecurityItemId) &&
affiliateUser.Id.Equals(AffiliateId) && affiliateUser != null &&
securityItemUser.EmailAddress.EqualsCI(EmailAddress); affiliateUser.Id.Equals(AffiliateId);
} }
} }
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams) private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
@@ -510,9 +551,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
if (!StringUtil.IsEmpty(encryptedParams)) if (!StringUtil.IsEmpty(encryptedParams))
{ {
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword, Base64Key_RequestPassword); 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.SecurityItemId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_SecurityItemId)));
requestPasswordParams.AffiliateId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_AffiliateId))); 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.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
requestPasswordParams.Token = linkParams.Get(ParamName_Token); requestPasswordParams.Token = linkParams.Get(ParamName_Token);
} }
@@ -2,8 +2,11 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App; 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.Core.Web.Portal.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus; 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.App;
using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Security; using Sarand.Net.Standard.Base.Security;
@@ -71,24 +74,43 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams); RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams);
if (requestPasswordParams.IsValid()) if (requestPasswordParams.IsValid())
{ {
String logPrefix = fn + " 'Request Password' parameters supplied but ";
String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", "."); String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", ".");
using (SaSecurityItem securityItemUser = SecurityConsole.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId)) // User may be defined on any one of the BusinessMgr databases - need to find out which.
using (SbmAffiliate userAccount = AccountMgr.GetAffiliateById(requestPasswordParams.AffiliateId)) // Identify session contexts with a BusMgr database that hosts the specified email address.
List<ushort> 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; if (securityItemUser.Id.Equals(requestPasswordParams.SecurityItemId) && affiliateUser.UserId.Equals(securityItemUser.Id))
validSessionRequired = false; {
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);
} }
} }
} }
@@ -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();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -36,4 +36,10 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Views\Commodity\" />
<Folder Include="Views\Association\" />
<Folder Include="Views\Transactions\" />
</ItemGroup>
</Project> </Project>
@@ -0,0 +1,21 @@
@model SbmModel_Portal
@inject SbmLocaliser Localiser
<script src="~/js/sbm-resultsPaging.js?@Model.HrefVersion"></script>
<script type="text/javascript">
sbm.resultsPaging.msg.pageInfo = '@Localiser.GetJavascriptText("label.page.info","{0}","{1}")';
</script>
@* Results paging *@
<div class="sbm-results-paging hidden">
@Html.HiddenFor(model => model.Paging.PageNo, new { @id = "", @class = "PagingPageNo" })
@Html.HiddenFor(model => model.Paging.PageLength, new { @id = "", @class = "PagingPageLength" })
<div class="iblock sbm-action sbm-paging-first"><img src="~/img/action/first24.png" alt="@(Localiser["label.first"])" title="@(Localiser["label.first"])" /></div>
<div class="iblock sbm-action sbm-paging-prev"><img src="~/img/action/prev24.png" alt="@(Localiser["label.previous"])" title="@(Localiser["label.previous"])" /></div>
<div class="iblock sbm-paging-info"></div>
<div class="iblock sbm-action sbm-paging-next"><img src="~/img/action/next24.png" alt="@(Localiser["label.next"])" title="@(Localiser["label.next"])" /></div>
<div class="iblock sbm-action sbm-paging-last"><img src="~/img/action/last24.png" alt="@(Localiser["label.last"])" title="@(Localiser["label.last"])" /></div>
</div>
@@ -0,0 +1,116 @@
@model Model_Contact
@inject SbmLocaliser Localiser
@{
/* Affiliates - Index */
ViewData["Title"] = "Affiliates";
}
<div class="sbm-page-affiliates">
<script type="text/javascript">
sbm.lbl.edit = 'Edit';
sbm.lbl.activate = 'Activate';
</script>
<div class="row">
<div class="col-md-5 row">
<div class="col">
@* Filter Panel *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateFilter", @class = "sbm-form", @onSubmit = "return false;" }))
{
<div class="sbm-panel-filter">
@* Header - Page caption, paging controls, actions. *@
<div class="sbm-header">
<div class="row">
<div class="col"><div class="sbm-caption">@Localiser["contact.caption.contacts"]</div></div>
<div class="fixed row">
<div class="col">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="fixed row">
<div class="sbm-action sbm-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="sbm-action sbm-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
</div>
</div>
</div>
</div>
<div class="sbm-content">
@* Affiliate filter - name etc. *@
<div class="row pb4">
<div class="col-sm-5 sbm-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-7">
@Html.TextBoxFor(model => model.Filter.AffiliateSearch, new { @id = "", @class = "FilterAffiliate", @maxlength = "32" })
</div>
</div>
@* Affiliate Category *@
<div class="row pb4">
<div class="col-sm-5 sbm-label text-left text-sm-right">@Localiser["label.category"]</div>
<div class="col-sm-7">
@Html.DropDownListFor(model => model.Filter.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.any"], new { @id = "", @class = "FilterAffiliateType sbm-select" })
</div>
</div>
</div>
</div>
}
</div>
@* Separate filter from results when alondside each other. *@
<div class="fixed"><div class="d-none d-md-block" style="width:8px;"></div></div>
</div>
<div class="col-md-7">
@* Results Panel *@
<div class="sbm-panel-results">
<div class="sbm-panel-messages"></div>
<div class="sbm-panel-item-list"></div>
@* element templates *@
<div class="sbm-panel-item-template">
@* Affiliate item template *@
<div class="sbm-panel-item Affiliate">
<div class="sbm-header row">
<div class="col">
@* Affiliate caption *@
<div class="sbm-caption"><div class="sbm-action sbm-item-update AffiliateName"></div></div>
</div>
<div class="fixed sbm-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sbm-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbm-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sbm-content">
<div class="row">
<div class="col-sm-4"><div class="AffiliateType"></div></div>
<div class="col-sm-4"><div class=""></div></div>
<div class="col-sm-4 text-left text-sm-right"><div class=""></div></div>
</div>
<div class="row">
<div class="col-sm-4"><div class=""></div></div>
<div class="col-sm-4"><div class="AffiliateAssociationCount sbm-action-text">@Localiser["label.associations"]:&nbsp;<span class="AssociationCount"></span></div></div>
<div class="col-sm-4 text-left text-sm-right"><div class="StatusDesc"></div></div>
</div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sbm-dropdown-menu Affiliate">
<li class="sbm-dropdown-item sbm-action"><div class="sbm-label">XXX</div></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-AffiliateDialog.cshtml" />
@@ -0,0 +1,56 @@
@model Model_Contact
@inject SbmLocaliser Localiser
@{
/* Affiliates - Affiliate dialog panel */
}
<script type="text/javascript">
sbm.dlg.title.createAffiliate = '@Localiser.GetJavascriptText("contact.caption.contact.create")';
sbm.dlg.title.updateAffiliate = '@Localiser.GetJavascriptText("contact.caption.contact.update")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmAffiliateId", @class = "sbm-form" }))
{
@Html.HiddenFor(model => model.AffiliateId, new { @class = "AffiliateId" })
}
<div id="dlgAffiliate" class="sbm-dialog" title="">
@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 *@
<div class="sbm-form-control AffiliateType">
<div class="sbm-label">
<label for="cbAffiliateType"><span><span class="required">*</span> @Localiser["label.type"]</span></label>
</div>
<div class="sbm-control">
@Html.DropDownListFor(model => model.AffiliateType, Model.GetAffiliateTypeCodesForSelect(), Localiser["label.select"], new { @id = "cbAffiliateType", @class = "AffiliateType sbm-select" })
</div>
<div class="sbm-message"></div>
</div>
@* Affiliate Organisation Name *@
<div class="sbm-form-control AffiliateName">
<div class="sbm-label">
<label for="ifAffiliateName"><span><span class="required">*</span> @Localiser["label.name"]</span></label>
</div>
<div class="sbm-control">
@Html.TextBoxFor(model => model.AffiliateName, new { @id = "ifAffiliateName", @class = "AffiliateName sbm-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Name) })
</div>
<div class="sbm-message"></div>
</div>
@* Affiliate Notes *@
<div class="sbm-form-control AffiliateNotes">
<div class="sbm-label">
<label for="ifAffiliateNotes"><span><span class="required">*</span> @Localiser["label.notes"]</span></label>
</div>
<div class="sbm-control">
@Html.TextAreaFor(model => model.AffiliateNotes, new { @id = "ifAffiliateNotes", @class = "AffiliateNotes sbm-textarea", @maxlength = @Model.GetControlMaxLength(SbmDbDfn.ColumnIdx.Affiliate_Notes) })
</div>
<div class="sbm-message"></div>
</div>
}
</div>
@@ -120,10 +120,10 @@
@Localiser["label.reference"] @Localiser["label.reference"]
</a> </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Contacts');">@Localiser["label.contacts"]</div> <div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Contact');">@Localiser["label.contacts"]</div>
</div> </div>
<div class="dropdown-menu" aria-labelledby="navbarDropdown"> <div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Commodities');">@Localiser["label.commodities"]</div> <div class="dropdown-item text-dark sbm-action" onclick="sbm.goToPage('Commodity');">@Localiser["label.commodities"]</div>
</div> </div>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
@@ -16,7 +16,7 @@
"Send": { "Send": {
"Using": 2, "Using": 2,
"UserName": "businessmgr@sarand.com", "UserName": "businessmgr@sarand.com",
"Password": "sarand-busmgr-5apP1re" "Password": "sarand-busmgr-5apPh1re"
}, },
"Message": { "Message": {
"AddressFrom": "DoNotReply@SaBusinessMgr.com" "AddressFrom": "DoNotReply@SaBusinessMgr.com"
@@ -1,5 +1,5 @@
/* /*
Base stylesheet. BusMgr Portal - Base stylesheet.
*/ */
body body
@@ -19,10 +19,9 @@ div, button, input, select, textarea, table, tr, td
font-size: 1rem; font-size: 1rem;
} }
:focus, input:focus, textarea:focus, select:focus, button:focus :focus, button:focus
{ {
outline: none; outline: none;
border-color: inherit;
box-shadow: 0 0 0 2px #80808040 inset; box-shadow: 0 0 0 2px #80808040 inset;
} }
@@ -122,10 +121,15 @@ input
textarea textarea
{ {
overflow-y:scroll;
resize: none; resize: none;
} }
select
{
margin: 0;
padding: 3px 2px 1px 2px;
}
.bold, b .bold, b
{ {
font-weight: bold !important; font-weight: bold !important;
@@ -389,7 +393,9 @@ td.action
{ {
-moz-box-shadow: 2px 2px 3px #ccc; -moz-box-shadow: 2px 2px 3px #ccc;
-webkit-box-shadow: 2px 2px 3px #ccc; -webkit-box-shadow: 2px 2px 3px #ccc;
box-shadow: 2px 2px 3px #ccc; box-shadow: 2px 2px 3px #ccc; /* For IE 8 */
-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc')"; /* For IE 5.5 - 7 */
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc');
} }
/* Bootstrap */ /* Bootstrap */
@@ -1,5 +1,5 @@
/* /*
Data stylesheet. BusMgr Portal - Data stylesheet.
*/ */
/* Filter panel */ /* Filter panel */
@@ -8,10 +8,9 @@
{ {
margin: 8px auto; margin: 8px auto;
background-color: #f8f8f8; background-color: #f8f8f8;
border: solid 2px var(--color-primary); border: solid 1px #979797;
border-radius: 6px; border-radius: 6px;
-webkit-border-radius: 6px; overflow: hidden;
-moz-border-radius: 6px;
} }
@media (min-width: 576px) @media (min-width: 576px)
@@ -21,56 +20,60 @@
} }
} }
.sbm-panel-filter > .sbm-header .sbm-panel-filter .sbm-header
{ {
padding: 2px 8px; padding: 1px 8px 2px 8px;
min-height: 24px; min-height: 24px;
color: #f0f0f0; color: #f0f0f0;
background-color: var(--color-primary); background-color: #979797;
border-bottom: solid 1px var(--color-primary); border-bottom: solid 1px #979797;
} }
.sbm-panel-filter > .sbm-header .sbm-caption .sbm-panel-filter .sbm-header .sbm-caption
{ {
margin-top: 4px; margin-top: 4px;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: bold; font-weight: bold;
} }
.sbm-panel-filter > .sbm-header .sbm-action .sbm-panel-filter .sbm-header .sbm-action
{ {
margin: 2px 0 0 8px; margin: 2px 0 0 8px;
border-radius: 4px; border-radius: 4px;
} }
.sbm-panel-filter > .sbm-header .sbm-action:hover .sbm-panel-filter .sbm-header .sbm-action:hover
{ {
background-color: #b0b0b0; background-color: #b0b0b0;
box-shadow: 0 0 4px 2px #80808080; box-shadow: 0 0 4px 2px #80808080;
} }
.sbm-panel-filter > .sbm-content .sbm-panel-filter .sbm-content
{ {
margin: 4px 8px; margin: 4px 8px;
padding: 4px 8px; padding: 4px 8px;
background-color: #f8f8f8;
} }
.sbm-panel-filter > .sbm-content .sbm-control .sbm-panel-filter .sbm-content .sbm-label
{ {
margin-bottom: 4px; padding-top: 2px;
} padding-right: 12px;
.sbm-panel-filter > .sbm-content .sbm-label
{
padding: 2px 12px 2px 0;
text-align: right; text-align: right;
} }
.sbm-panel-filter > .sbm-content .sbm-button .sbm-panel-filter .sbm-content input[type="text"],
.sbm-panel-filter .sbm-content select
{
min-width: 75%;
max-width: 90%;
}
.sbm-panel-filter .sbm-content .sbm-button
{ {
min-width: auto; min-width: auto;
margin-left: 4px; margin-left: 4px;
padding: 4px 6px 2px 6px; padding: 4px 6px;
} }
/* Paging */ /* Paging */
@@ -88,8 +91,7 @@
.sbm-panel-filter .sbm-header .sbm-results-paging .sbm-action .sbm-panel-filter .sbm-header .sbm-results-paging .sbm-action
{ {
margin: 2px 1px 0 1px; margin: 2px 1px 0 1px;
background-color: #a0a0a0; background-color: #b0b0b0;
opacity: 0.8;
box-shadow: 0 0 0 1px #40404080 inset; box-shadow: 0 0 0 1px #40404080 inset;
} }
@@ -131,8 +133,7 @@
.sbm-panel-item .sbm-panel-item
{ {
margin: 8px auto; margin: 8px auto;
background-color: #f8f8f8; border: solid 1px #979797;
border: solid 2px #979797;
border-radius: 6px; border-radius: 6px;
-webkit-border-radius: 6px; -webkit-border-radius: 6px;
-moz-border-radius: 6px; -moz-border-radius: 6px;
@@ -161,7 +162,7 @@
.sbm-panel-item > .sbm-header .sbm-caption .sbm-action:hover .sbm-panel-item > .sbm-header .sbm-caption .sbm-action:hover
{ {
color: var(--color-primary); color: var(--color-light);
} }
.sbm-panel-item > .sbm-header .sbm-actions .sbm-action .sbm-panel-item > .sbm-header .sbm-actions .sbm-action
@@ -177,7 +178,7 @@
.sbm-panel-item > .sbm-content .sbm-panel-item > .sbm-content
{ {
margin: 6px 8px; margin: 4px 8px;
padding: 4px 4px; padding: 4px 4px;
} }
@@ -186,46 +187,38 @@
font-size: 0.8rem; font-size: 0.8rem;
} }
/* Profile Sports */ @media (min-width: 576px)
.sbm-panel-item.ProfileSport,
.sbm-panel-item.ProfileSport > .sbm-header
{ {
border-color: var(--color-alt2); .sbm-panel-filter
{
}
} }
/* Organisations */ /* Item statuses */
.sbm-panel-item.Organisation, .sbm-status-awa
.sbm-panel-item.Organisation > .sbm-header
{ {
border-color: var(--color-alt3); color: darkorange;
} }
/* Articles */ .sbm-status-atv
.sbm-panel-item.Article,
.sbm-panel-item.Article > .sbm-header
{ {
border-color: var(--color-article); color: green;
} }
.sbm-panel-item.Article > .sbm-header .sbm-status-ina
{ {
background-color: var(--color-article-pale); color: darkred;
} }
/* Opportunitys */ .sbm-status-hld
.sbm-panel-item.Opportunity,
.sbm-panel-item.Opportunity > .sbm-header
{ {
border-color: var(--color-opportunity); color: dodgerblue;
} }
.sbm-panel-item.Opportunity > .sbm-header .sbm-status-del
{ {
background-color: var(--color-opportunity-pale); color: purple;
} }
/* The End */ /* The End */
@@ -1,5 +1,5 @@
/* /*
Form stylesheet. BusMgr Portal - Form stylesheet.
*/ */
/* Panels */ /* Panels */
@@ -7,20 +7,20 @@
.sbm-panel-info, .sbm-panel-form .sbm-panel-info, .sbm-panel-form
{ {
max-width: 720px; max-width: 720px;
margin: 16px auto; margin: 24px auto;
padding: 0; padding: 0;
background-color: #f8f8f8; background-color: #fff;
border: solid 2px #606060; border: solid 2px #979797;
border-color: var(--color-primary); border-color: var(--color-primary);
border-radius: 6px; border-radius: 4px;
-webkit-border-radius: 6px; -webkit-border-radius: 4px;
-moz-border-radius: 6px; -moz-border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
.sbm-panel-form.security .sbm-panel-form.security
{ {
max-width: 560px; max-width: 520px;
} }
@media (min-width: 576px) @media (min-width: 576px)
@@ -40,24 +40,14 @@
border-color: var(--color-primary); border-color: var(--color-primary);
} }
.sbm-panel-form > .sbm-header
{
background-color: var(--color-primary);
}
.sbm-panel-info > .sbm-header .sbm-caption, .sbm-panel-info > .sbm-header .sbm-caption,
.sbm-panel-form > .sbm-header .sbm-caption .sbm-panel-form > .sbm-header .sbm-caption
{ {
font-size: 1.25rem; font-size: 1.5rem;
font-weight: bold; font-weight: bold;
color: var(--color-primary-dark); color: var(--color-primary-dark);
} }
.sbm-panel-form > .sbm-header .sbm-caption
{
color: #f0f0f0;
}
.sbm-panel-info > .sbm-header .sbm-action img:hover, .sbm-panel-info > .sbm-header .sbm-action img:hover,
.sbm-panel-form > .sbm-header .sbm-action img:hover .sbm-panel-form > .sbm-header .sbm-action img:hover
{ {
@@ -66,29 +56,18 @@
box-shadow: 2px 2px 4px 0 #80808080; box-shadow: 2px 2px 4px 0 #80808080;
} }
.sbm-panel-info > .sbm-header .dropdown-menu,
.sbm-panel-form > .sbm-header .dropdown-menu
{
padding: 4px 8px;
}
.sbm-panel-info > .sbm-header ul.nav,
.sbm-panel-form > .sbm-header ul.nav
{
margin: 0;
}
.sbm-panel-info > .sbm-header .dropdown-menu .sbm-action,
.sbm-panel-form > .sbm-header .dropdown-menu .sbm-action
{
font-size: .8rem;
}
.sbm-panel-info > .sbm-content, .sbm-panel-info > .sbm-content,
.sbm-panel-form > .sbm-content .sbm-panel-form > .sbm-content
{ {
max-height: 400px;
padding: 12px 12px; padding: 12px 12px;
background-color: #f8f8f8; background-color: #f8f8f8;
overflow-y: auto;
}
.sbm-panel-info > .sbm-content
{
padding: 8px 12px;
} }
.sbm-panel-section.sbm-edit .sbm-panel-info, .sbm-panel-section.sbm-edit .sbm-panel-info,
@@ -128,20 +107,13 @@
{ {
} }
.sbm-form .sbm-form-control .sbm-caption,
.sbm-form .sbm-form-control .sbm-label .sbm-form .sbm-form-control .sbm-label
{ {
margin: 0 0 4px 0; margin: 0 0 8px 0;
font-size: 1rem; font-size: 1rem;
line-height: 1.25; line-height: 1.25;
} }
.sbm-form .sbm-form-control .sbm-caption
{
font-size: 1.1rem;
font-weight: bold;
}
.sbm-form .sbm-form-control .sbm-label-text .sbm-form .sbm-form-control .sbm-label-text
{ {
font-size: 1rem; font-size: 1rem;
@@ -158,19 +130,39 @@
.sbm-form .sbm-form-control .sbm-form .sbm-form-control
{ {
margin: 0 0 12px 0; margin: 0 0 16px 0;
}
.sbm-form .sbm-form-control .sbm-label
{
margin: 0 0 8px 0;
font-size: 1rem;
line-height: 1.25;
}
.sbm-form .sbm-form-control .sbm-label-text
{
font-size: 1rem;
line-height: 1.25;
}
.sbm-form .sbm-form-control .sbm-note
{
font-size: 0.9rem;
line-height: 1.2;
} }
.sbm-form .sbm-input, .sbm-form .sbm-textarea, .sbm-form .sbm-select .sbm-form .sbm-input, .sbm-form .sbm-textarea, .sbm-form .sbm-select
{ {
padding: 3px 8px 0 8px; padding: 3px 8px 4px 8px;
width: 100%; width: 100%;
min-height: 30px;
font-size: 1rem; font-size: 1rem;
line-height: 1.3; line-height: 1.25;
background-color: #fff; background-color: #fff;
border: 1px solid #595959; border: 1px solid #595959;
border-radius: 3px; border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
outline: none; outline: none;
} }
@@ -181,8 +173,8 @@
.sbm-form .sbm-textarea, .sbm-form .sbm-select[multiple="multiple"] .sbm-form .sbm-textarea, .sbm-form .sbm-select[multiple="multiple"]
{ {
border-top-right-radius: 0; border-top-right-radius: 2px;
border-bottom-right-radius: 0; border-bottom-right-radius: 2px;
overflow-y: scroll; overflow-y: scroll;
} }
@@ -193,10 +185,8 @@
.sbm-form .sbm-form-control.sbm-error .sbm-input, .sbm-form .sbm-form-control.sbm-error .sbm-textarea, .sbm-form .sbm-form-control.sbm-error .sbm-select .sbm-form .sbm-form-control.sbm-error .sbm-input, .sbm-form .sbm-form-control.sbm-error .sbm-textarea, .sbm-form .sbm-form-control.sbm-error .sbm-select
{ {
color: #606060; color: #DC143C;
color: var(--color-primary); border-color: #DC143C;
border-color: #606060;
border-color: var(--color-primary);
} }
/* Control messages */ /* Control messages */
@@ -206,7 +196,7 @@
display: none; display: none;
margin-top: 8px; margin-top: 8px;
padding-left: 24px; padding-left: 24px;
font-size: 16px; font-size: 1.00rem;
background-position: left top; background-position: left top;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
@@ -226,16 +216,13 @@
{ {
height: auto; height: auto;
width: auto; width: auto;
min-width: 120px; min-width: 80px;
padding: 4px 8px; padding: 4px 2px;
font-size: 16px; font-size: 1rem;
font-weight: bold;
text-align: center; text-align: center;
color: #606060; color: #fff;
color: var(--color-primary); background-color: #d4af37;
background-color: #DFD2E1; border: 1px solid #d4af37;
border: solid 1px #606060;
border-color: var(--color-primary);
border-radius: 4px; border-radius: 4px;
} }
@@ -243,7 +230,6 @@
.sbm-form input[type="button"].sbm-button:hover, .sbm-form input[type="button"].sbm-button:hover,
.sbm-form input[type="submit"].sbm-button:hover .sbm-form input[type="submit"].sbm-button:hover
{ {
background-color: #EFE8F0;
} }
/* Password strength */ /* Password strength */
@@ -254,7 +240,8 @@
height: 18px; height: 18px;
margin: 6px 24px; margin: 6px 24px;
border-color: #595959; border-color: #595959;
box-shadow: none; -moz-box-shadow: none;
-webkit-box-shadow: none;
overflow: hidden; overflow: hidden;
} }
@@ -1,35 +1,20 @@
/* /*
Main stylesheet. BusMgr Portal - Main stylesheet.
*/ */
/* Global variables */ /* Global variables */
:root :root
{ {
/* /* Colour analysis: https://www.colorhexa.com/ */
#556B2F Dark Olive Green /* #4b0082 - Indigo. Ref. https://www.crispedge.com/color/4b0082 */
Ref. https://www.crispedge.com/color/556B2F --color-primary: #4b0082;
Darker: #445525, #33401c. Lighter: #768858, #99a682, #a6c275, #b7ce90. --color-light: #9b71b9;
*/ --color-pale: #ebe2f1;
--color-primary: #556B2F;
--color-primary-dark: #425324;
--color-primary-light: #a0ac8b;
--color-primary-pale: #eceee7;
/* Tetrad/Square colors */ /* Tetrad/Square colors */
--color-alt1: #2f6b63; --color-alt1: #820a00;
--color-alt2: #452f6b; --color-alt2: #378200;
--color-alt3: #6b2f37; --color-alt3: #007882;
/* Activity colours */
--color-article-dark: #7339ac;
--color-article: #9966CC;
--color-article-light: #b38cd9;
--color-article-pale: #decdee;
--color-opportunity-dark: #8f6b24;
--color-opportunity: #CC9933;
--color-opportunity-light: #dbb870;
--color-opportunity-pale: #f3e7d0;
/* Specific colours */
--color-dialog: var(--color-alt3);
} }
/* Document */ /* Document */
@@ -49,13 +34,36 @@ html
body body
{ {
font-family: Verdana, Helvetica, Arial, Lucida, sans-serif; font-family: Arial, Verdana, Helvetica, Lucida, sans-serif;
background-color: #e9edf6;
} }
body > .container body > .container
{ {
padding: 0 1%; padding: 0 0.5%;
}
@media (min-width: 992px)
{
.container
{
max-width: 1200px;
}
}
@media (min-width: 768px)
{
.container
{
max-width: 1200px;
}
}
@media (min-width: 576px)
{
.container
{
max-width: 1200px;
}
} }
/* Sticky footer styles */ /* Sticky footer styles */
@@ -69,7 +77,7 @@ html
body body
{ {
/* Margin bottom by footer height */ /* Margin bottom by footer height */
/*margin-bottom: 60px;*/ margin-bottom: 60px;
} }
.footer .footer
@@ -77,6 +85,7 @@ body
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
padding: 0 2%;
white-space: nowrap; white-space: nowrap;
/* Set the fixed height of the footer here */ /* Set the fixed height of the footer here */
height: 60px; height: 60px;
@@ -113,11 +122,14 @@ button.accept-policy
line-height: inherit; line-height: inherit;
} }
/* Information */ /* Page caption */
.sbm-page-caption
.sbm-para
{ {
margin: 8px 0; margin: 0;
padding: 16px 24px;
font-size: 48px;
font-weight: bold;
color: #000;
} }
/* Actions */ /* Actions */
@@ -125,8 +137,7 @@ button.accept-policy
a, a:hover a, a:hover
{ {
text-decoration: underline; text-decoration: underline;
color: #606060; color: #d4af37;
color: var(--color-primary);
} }
a:focus a:focus
@@ -135,11 +146,16 @@ a:focus
outline-width: thin; outline-width: thin;
} }
.sbm-action .sbm-action, .sbm-action-text
{ {
cursor: pointer; cursor: pointer;
} }
.sbm-action-text:hover
{
text-decoration: underline;
}
.sbm-action-link .sbm-action-link
{ {
display: inline-block; display: inline-block;
@@ -169,11 +185,11 @@ a:focus
padding: 12px 12px; padding: 12px 12px;
min-width: 100px; min-width: 100px;
color: #404040; color: #404040;
background-color: #b7ce90; background-color: #e0e0e0;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
border-radius: 12px; border-radius: 20px;
cursor: pointer; cursor: pointer;
transition: all .5s ease-in-out; transition: all .5s ease-in-out;
outline: none; outline: none;
@@ -185,12 +201,18 @@ a:focus
.sbm-button-action:hover .sbm-button-action:hover
{ {
background-color: #a6c275; background-color: #b0b0b0;
}
.sbm-button-action:focus
{
outline: none;
box-shadow: 0 0 4px 2px #808080;
} }
.sbm-button-cta:hover .sbm-button-cta:hover
{ {
background: #a6c275; background: #808080 !important;
} }
/* --- Notification --- */ /* --- Notification --- */
@@ -254,7 +276,6 @@ a:focus
} }
/* --- Modal Please wait --- */ /* --- Modal Please wait --- */
#sbm-please-wait #sbm-please-wait
{ {
display: none; display: none;
@@ -277,7 +298,6 @@ a:focus
} }
/* Dialogs */ /* Dialogs */
.sbm-dialog .sbm-dialog
{ {
display: none; display: none;
@@ -287,21 +307,25 @@ a:focus
{ {
position: fixed; position: fixed;
padding: 0; padding: 0;
background-color: #e9edf6; }
.ui-dialog .ui-dialog-titlebar
{
padding: 6px 12px;
}
.ui-dialog .ui-dialog-content
{
padding: 6px 12px;
} }
.ui-dialog.ui-widget.ui-widget-content .ui-dialog.ui-widget.ui-widget-content
{ {
border-width: 2px; border-width: 2px;
border-color: #606060; border-color: #d4af37;
border-color: var(--color-dialog); -moz-box-shadow: 4px 4px 6px #ccc;
-moz-box-shadow: 4px 4px 6px var(--color-dialog); -webkit-box-shadow: 4px 4px 6px #ccc;
-webkit-box-shadow: 4px 4px 6px var(--color-dialog); overflow: hidden;
}
.ui-dialog.ui-widget.sbm-dialog-border-thin.ui-widget-content
{
border-width: 1px;
} }
.ui-dialog.ui-widget.ui-widget-content.ui-corner-all .ui-dialog.ui-widget.ui-widget-content.ui-corner-all
@@ -309,68 +333,41 @@ a:focus
border-radius: 6px; border-radius: 6px;
} }
.ui-dialog.ui-widget.sbm-dialog-rounding-none.ui-widget-content.ui-corner-all
{
border-radius: 0;
}
.ui-dialog.ui-widget .ui-widget-header .ui-dialog.ui-widget .ui-widget-header
{ {
font-size: 1.1rem; font-size: 1.1rem;
color: #f0f0f0; color: #fff;
background-color: #606060; background-color: #d4af37;
border-color: var(--color-dialog); border-color: #d4af37;
background-color: #606060;
background-color: var(--color-dialog);
} }
.ui-dialog.ui-widget .ui-widget-header.ui-corner-all .ui-widget-content
{ {
border-radius: 0; font-size: 16px;
}
.ui-dialog.sbm-dialog-titlebar-hide .ui-dialog-titlebar
{
display: none;
}
.ui-dialog.dialog-no-close .ui-dialog-titlebar-close
{
display: none;
}
.ui-dialog.ui-widget .ui-dialog-content.ui-widget-content
{
padding: 4px 8px 0 8px;
font-size: 1rem;
color: #000; color: #000;
} }
.ui-dialog .ui-dialog-buttonpane.ui-widget-content .ui-dialog .ui-dialog-titlebar.ui-corner-all
{ {
margin-top: 0; border-bottom-left-radius: 0;
padding: 4px 16px 4px 16px; border-bottom-right-radius: 0;
background-color: transparent;
border-color: #606060;
border-color: var(--color-dialog);
} }
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-dialog.no-close .ui-dialog-titlebar-close
{ {
float: none; /*display: none;*/
width: 100%; }
text-align: right;
.ui-dialog .ui-dialog-buttonpane
{
border-color: #d4af37;
} }
.ui-dialog .ui-dialog-buttonpane button .ui-dialog .ui-dialog-buttonpane button
{ {
min-width: 60px;
padding: 6px 16px 4px 16px;
color: #fff; color: #fff;
background-color: #606060; background-color: #d4af37;
background-color: var(--color-dialog); border-color: #d4af37;
border-color: #606060;
border-color: var(--color-dialog);
outline: none; outline: none;
} }
@@ -388,8 +385,7 @@ a:focus
.ui-datepicker .ui-datepicker-header .ui-datepicker .ui-datepicker-header
{ {
background-color: #fff; background-color: #fff;
border: 1px solid #606060; border: 1px solid #d4af37;
border-color: var(--color-dialog);
} }
/* The End */ /* The End */
Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Some files were not shown because too many files have changed in this diff Show More