FEAT: KANVAS customer

This commit is contained in:
Frede Hundewadt 2023-04-06 13:22:02 +02:00
parent 0a96c9c640
commit 7572127425
7 changed files with 477 additions and 350 deletions

View file

@ -37,7 +37,7 @@
}
<div class="row mb-2 bg-dark text-white rounded-3 p-3">
<div class="col">
<h3>@Activity.Name - @Activity.Account</h3>
<span class="h3">@Activity.Name</span> <span>(@Activity.Account)</span>
</div>
</div>
@ -58,9 +58,16 @@ else
<label for="activityType" class="col-sm-2 col-form-label-sm">Ordre Type</label>
<div class="col-sm-4">
<InputSelect id="activityType" class="form-select bg-primary text-bg-primary" @bind-Value="@Activity.ActivityTypeEnum">
@if (Kanvas)
{
<option value="canvas" selected>Kanvas</option>
}
else
{
<option value="">&rarr; TAG MIG &larr;</option>
<option value="onSite">Besøg</option>
<option value="phone">Telefon</option>
}
</InputSelect>
<ValidationMessage For="@(() => Activity.ActivityTypeEnum)"></ValidationMessage>
</div>
@ -68,6 +75,12 @@ else
<label for="statusType" class="col-sm-2 col-form-label-sm">Status</label>
<div class="col-sm-4">
<InputSelect id="statusType" class="form-select bg-primary text-bg-primary" @bind-Value="@Activity.ActivityStatusEnum">
@if (Kanvas)
{
<option value="canvas" selected>Kanvas</option>
}
else
{
<option value="noSale">Ingen salg</option>
@if (!string.IsNullOrEmpty(Activity.VatNumber) && !string.IsNullOrWhiteSpace(Activity.Address1) && Company.HasFolded == 0)
{
@ -89,6 +102,7 @@ else
<option value="quote">Tilbud</option>
}
}
}
</InputSelect>
<ValidationMessage For="@(() => Activity.ActivityStatusEnum)"></ValidationMessage>
@if (Activity.ActivityStatusEnum == "order")
@ -156,6 +170,8 @@ else
</div>
</div>
@if (!Kanvas)
{
<div class="row g-2 mb-3">
<div class="col-sm-3 d-grid mx-auto">
@*
@ -348,7 +364,9 @@ else
</div>
</div>
</div>
}
</EditForm>
<div class="row mt-5 mb-2">
<div class="col-sm-6">
<a class="btn btn-warning" href="/advisor/customers/@Company.CompanyId">Kundekort <i class="bi-arrow-left"></i></a>

View file

@ -96,6 +96,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
private List<ProductInventoryView> CheckList { get; set; } = new();
private InvoiceListView CompanyInvoices { get; set; } = new();
private List<ReportItemView> Activities { get; set; } = new();
private bool Kanvas { get; set; }
/// <summary>
/// Page initialization
@ -114,6 +115,17 @@ public partial class AdvisorActivityCreatePage : IDisposable
SalesRep = await UserInfo.GetUserInfo();
// Fetch Customer from http
Company = await CompanyRepo.GetCompanyById(CompanyId);
if (Company.Account.StartsWith("KANVAS"))
{
Kanvas = true;
Activity.ActivityStatusEnum = "canvas";
Activity.ActivityTypeEnum = "canvas";
Activity.ActivityVisitEnum = "new";
PoFormInvalid = false;
}
else
{
if (Company.HasFolded == 1)
// Company has shut down
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
@ -126,11 +138,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
{
Company.Phone = Company.Account[..8];
}
Activity.ActivityStatusEnum = "noSale";
Activity.ActivityVisitEnum = Company.Account is "" or "NY" ? "new" : "recall";
}
// Populate base activity information
Activity.BcId = Company.BcId;
Activity.ActivityStatusEnum = "noSale";
Activity.VisitTypeEnum = Company.Account is "" or "NY" ? "new" : "recall";
Activity.CompanyId = Company.CompanyId;
Activity.SalesRepId = SalesRep.UserId;
Activity.SalesRep = SalesRep.SalesRep;
@ -169,7 +182,9 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (DraftProvider.Draft.DraftType == "order")
{
// set dropdown selection accordingly
if(Activity.ActivityTypeEnum != "phone")
Activity.ActivityTypeEnum = "onSite";
Activity.ActivityStatusEnum = "order";
PoFormInvalid = false;
}
@ -177,91 +192,50 @@ public partial class AdvisorActivityCreatePage : IDisposable
Working = false;
}
// #############################################################
// overlays
private async Task ShowVisitOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for visits");
ActivityListOverlay.Show();
Activities = await ActivityRepo.GetCustomerActivities(CompanyId);
await Task.Delay(500);
}
private async Task ShowInventoryOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for inventory");
InventoryListOverlay.Show();
Inventory = await HistoryRepo.FetchInventory(CompanyId);
Inventory = Inventory.OrderBy(x => x.Description).ToList();
await Task.Delay(500);
}
private async Task OnInventoryCallback(DraftItem item)
{
Activity.ActivityStatusEnum = "order";
DraftProvider.Draft.DraftType = "order";
DraftProvider.Draft.Items.Add(item);
StateHasChanged();
}
private async Task ShowInvoiceOverlay()
{
Logger.LogDebug("ShowInvoiceOverlay - wait for invoices");
InvoiceListOverlay.Show();
CompanyInvoices = await FetchCompanyInvoices();
await Task.Delay(500);
}
private async Task<InvoiceListView> FetchCompanyInvoices()
{
// fetch from storage
var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices");
await Task.Delay(500);
var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate");
// if we have a list and iDate was today return the list
if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) &&
DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now))
private void ShowPriceListOverlay()
{
Logger.LogDebug("fetching invoices from storage");
Logger.LogDebug("storage contains <= {}", storage);
return JsonSerializer.Deserialize<InvoiceListView>(storage);
CatalogOverlay.Show();
}
Logger.LogDebug("pulling invoices from backend");
// pull invoices
var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId);
// send invoices to storage
await Storage.SetItemAsync($"{CompanyId}-invoices", companyInvoices);
await Storage.SetItemAsync($"{CompanyId}-iDate", $"{DateTime.Now:yyyy-MM-dd}");
Logger.LogDebug(" --> return invoices from backend");
Working = false;
Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices));
return companyInvoices;
}
private void ShowOrgWarning()
{
if (OrgWarning)
return;
OrgWarning = true;
if (Company.CountryCode.ToLower() == "se" && Utils.StringToDigits(Activity.VatNumber).Length < 10 &&
Activity.ActivityStatusEnum == "order")
{
Toaster.ShowWarning("Org nummer er ufuldstændig. Skal opdateres før bestilling kan sendes. ", "ADVARSEL");
}
}
private async Task CallConfirmCheckOverlay()
{
// check if new account
if (string.IsNullOrWhiteSpace(Company.Account)
|| Company.Account.ToLower() == "ny"
|| Activity.ActivityStatusEnum.ToLower() == "quote")
|| Activity.ActivityStatusEnum.ToLower() == "quote"
|| Activity.ActivityStatusEnum.ToLower() == "canvas")
{
// proceed to create activity - as there is no product check to be done
await CreateActivity();
@ -316,38 +290,16 @@ public partial class AdvisorActivityCreatePage : IDisposable
ConfirmationCheckOverlay.Show();
}
private async Task ConfirmProductCheckCallback()
private void ShowPriceHistoryOverlay()
{
ConfirmationCheckOverlay.Hide();
await CreateActivity();
foreach (var item in CheckList)
{
item.Check = false;
if (ShowItem)
PriceOverlay.Show();
}
await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
}
private async Task WorkDateConfirmCallback()
{
await PreferenceService.SetDateConfirmed(true);
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
ConfirmWorkDate.Hide();
StateHasChanged();
}
private async Task WorkDateComponentCallback(string workDate)
{
ReportClosed = await ReportRepo.ReportExist(workDate);
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
private void ShowPriceListOverlay()
{
CatalogOverlay.Show();
}
// #############################################################
// callbacks
private async Task PriceListCallback(SelectedSku sku)
{
// get selected item
@ -360,11 +312,6 @@ public partial class AdvisorActivityCreatePage : IDisposable
StateHasChanged();
}
private void ShowPriceHistoryOverlay()
{
if (ShowItem)
PriceOverlay.Show();
}
private void PriceHistoryCallback(decimal price)
{
@ -374,24 +321,97 @@ public partial class AdvisorActivityCreatePage : IDisposable
StateHasChanged();
}
private void OnInventoryCallback(DraftItem item)
{
Activity.ActivityStatusEnum = "order";
DraftProvider.Draft.DraftType = "order";
DraftProvider.Draft.Items.Add(item);
StateHasChanged();
}
private async Task ConfirmProductCheckCallback()
{
ConfirmationCheckOverlay.Hide();
await CreateActivity();
foreach (var item in CheckList)
{
item.Check = false;
}
await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
}
private async Task WorkDateConfirmCallback()
{
await PreferenceService.SetDateConfirmed(true);
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
ConfirmWorkDate.Hide();
StateHasChanged();
}
private async Task WorkDateComponentCallback(string workDate)
{
ReportClosed = await ReportRepo.ReportExist(workDate);
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
// ################################################################################################
// fetch invoices for customer
private async Task<InvoiceListView> FetchCompanyInvoices()
{
// no need to do for kanvas entry
if (Kanvas) return new InvoiceListView();
// fetch from storage
var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices");
await Task.Delay(500);
var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate");
// if we have a list and iDate was today return the list
if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) &&
DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now))
{
Logger.LogDebug("fetching invoices from storage");
Logger.LogDebug("storage contains <= {}", storage);
return JsonSerializer.Deserialize<InvoiceListView>(storage);
}
Logger.LogDebug("pulling invoices from backend");
// pull invoices
var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId);
// send invoices to storage
await Storage.SetItemAsync($"{CompanyId}-invoices", companyInvoices);
await Storage.SetItemAsync($"{CompanyId}-iDate", $"{DateTime.Now:yyyy-MM-dd}");
Logger.LogDebug(" --> return invoices from backend");
Working = false;
Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices));
return companyInvoices;
}
// ##################################################################################################
// create activity
private async Task CreateActivity()
{
// avoid duplication
if (Working)
return;
Logger.LogDebug("view kanvas activity => {}", JsonSerializer.Serialize(Activity));
switch (Kanvas)
{
// validate customer address1
// - this is a required input
if (string.IsNullOrWhiteSpace(Activity.Address1))
{
case false when string.IsNullOrWhiteSpace(Activity.Address1):
Toaster.ShowError("Kunde adresse er ufuldstændig.");
return;
}
// validate org number
// - this is a required input
// - must validate according to country rules.
if (!VatUtils.ValidateFormat(Company.CountryCode, Activity.VatNumber))
{
case false when !VatUtils.ValidateFormat(Company.CountryCode, Activity.VatNumber):
Toaster.ShowError("Firma registreringsnummer er ikke korrekt.");
return;
}
@ -419,7 +439,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
// reset selected item
SelectedItem = new SalesItemView();
// check if phone number need to be updated
if (OldPhone != Activity.Phone)
if (!Kanvas && OldPhone != Activity.Phone)
{
Company.Phone = Activity.Phone;
Activity.OrderMessage = $"BEMÆRK: {Activity.Phone}\n{Activity.OrderMessage}";
@ -482,12 +502,29 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError(result.Message, "ORDRE FEJL");
}
private void ShowOrgWarning()
{
if (Kanvas) return;
if (OrgWarning) return;
OrgWarning = true;
if (Company.CountryCode.ToLower() == "se" && Utils.StringToDigits(Activity.VatNumber).Length < 10 &&
Activity.ActivityStatusEnum == "order")
{
Toaster.ShowWarning("Org nummer er ufuldstændig. Skal opdateres før bestilling kan sendes. ", "ADVARSEL");
}
}
// ###############################################################################
// draft functions
private async Task DeleteDraft()
{
await DraftProvider.DeleteDraftAsync();
Activity.ActivityStatusEnum = "noSale";
}
private async Task AddItem(SalesItemView salesItem)
{
ShowItem = false;
@ -513,6 +550,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
await DraftProvider.SaveChangesAsync();
}
private async Task RemoveItem(DraftItem item)
{
// remove item
@ -523,6 +561,9 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.ActivityStatusEnum = "noSale";
}
// ##################################################################################################
// form validation
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'",
@ -556,6 +597,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
@ -582,6 +624,9 @@ public partial class AdvisorActivityCreatePage : IDisposable
ActivityContext.OnValidationStateChanged += ValidationChanged;
}
// #########################################################################################################
// dispose
public void Dispose()
{
Interceptor.DisposeEvent();

View file

@ -29,7 +29,9 @@
</div>
}
<div class="row pt-2 mb-2 rounded rounded-2 bg-dark text-white">
<h3>@Company.Name @(string.IsNullOrWhiteSpace(Company.Account) ? "" : "- ")@Company.Account</h3>
<div class="col-sm-12">
<span class="h3">@Company.Name</span> <span>(@Company.Account)</span>
</div>
</div>
// erp context
<EditForm EditContext="ErpContext">
@ -89,15 +91,20 @@
<InputText id="email" class="form-control" @bind-Value="Company.Email" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Email)"></ValidationMessage>
</div>
@if (!Kanvas)
{
<div class="col-sm-4">@* ---- placeholder --- *@</div>
@* Force enable visit *@
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" @onclick="ForceActivity">Aktiver besøg</button>
</div>
@* Enable edit/save *@
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleErpEdit"><i class="bi-pencil"></i> STAM data</button>
</div>
@* Force enable visit *@
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-primary d-block" @onclick="ForceActivity">Aktiver besøg</button>
</div>
@* Save erp data *@
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-danger d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR" || ErpEditDisabled)"><i class="bi-cloud-arrow-up"></i> STAM data </button>
</div>
@ -112,12 +119,16 @@
<ValidationMessage For="@(() => Company.VatNumber)"></ValidationMessage>
</div>
</div>
@* Enable edit/save vatnumber *@
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleVatEdit"><i class="bi-pencil"></i> Moms/Org Nr.</button>
</div>
@* vat lookup *@
<div class="col-sm-3 d-grid mx-auto">
@switch (CountryCode)
{
case "dk":
<button type="button" class="btn btn-info" @onclick="OpenVatLookupModal"><i class="bi-search"></i> CVR</button>
<button type="button" class="btn btn-info" @onclick="OpenVatLookupModal" disabled="@(VatEditDisabled)"><i class="bi-search"></i> CVR</button>
break;
case "no":
<a class="btn btn-info" href="https://brreg.no/" target="_blank"><i class="bi-search"></i> brreg.no</a>
@ -127,27 +138,34 @@
break;
}
</div>
@* Enable edit/save *@
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleVatEdit"><i class="bi-pencil"></i> Moms/Org Nr.</button>
</div>
@* save vat number *@
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-warning d-block" @onclick="UpdateVatNumber" disabled="@(VatEditDisabled)"><i class="bi-cloud-arrow-up"></i> Moms/Org Nr.</button>
</div>
}
</div>
<hr class="mb-3"/>
@* activity buttons *@
<div class="row mt-3 mb-3">
<div class="col-sm-3">
@if (!Kanvas)
{
<a class="btn btn-danger d-block" href="/advisor/customers/@Company.CompanyId/invoices">Faktura</a>
}
</div>
<div class="col-sm-3">
@if (!Kanvas)
{
<a class="btn btn-warning d-block" href="/advisor/customers/@Company.CompanyId/activities">Tidl. Besøg</a>
}
</div>
<div class="col-sm-3">
@if (!Kanvas)
{
<a class="btn btn-success d-block" href="/advisor/customers/@Company.CompanyId/h/i">Produkter</a>
}
</div>
<div class="col-sm-3">
<ActivityButton ActionLink="@ActionLink"
@ -158,6 +176,8 @@
</div>
</div>
@if (!Kanvas)
{
<hr class="mb-3"/>
@* crm context - OBS note *@
<div class="row mb-2">
@ -246,16 +266,15 @@
<button type="button" class="btn btn-danger" @onclick="ToggleVisibility">@ToggleButtonText</button>
</div>
</div>
</EditForm>
@*
<div class="row mt-5">
<div class="col-sm-3">
<a class="btn btn-info" href="@($"/advisor/customers/{CompanyId}/workplaces")">Arbejdssteder</a>
</div>
</div>
*@
}
</EditForm>
}
@if (Working)

View file

@ -71,6 +71,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private ContactModal ContactPopup { get; set; } = new();
private UserManagerEditView UserInfo { get; set; } = new();
private string ToggleButtonText { get; set; } = "";
private bool Kanvas { get; set; }
protected override async Task OnInitializedAsync()
{
@ -96,16 +97,24 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = await CustomerRepo.GetCompanyById(CompanyId);
if (Company.Account.StartsWith("KANVAS"))
Kanvas = true;
// internal flag
EnableActivity = Company.ValidVat;
// override if canvas which has account property as empty string or "NY"
if (Company.Account == "NY" || Company.Account.StartsWith("KANVAS") || string.IsNullOrWhiteSpace(Company.Account))
EnableActivity = 1;
// action link passed to activity button component
ActionLink = $"/advisor/customers/{CompanyId}/activities/new"; // used when drawing visit button
// only execute if the company a 'real' customer
if (!Kanvas)
{
Logger.LogDebug("company => {}", JsonSerializer.Serialize(Company));
// toggle view button text
ToggleButtonText = Company.IsHidden == 0 ? "Udelad kunde i oversigt" : "Brug Normal Visning";
CurrentVat = Company.VatNumber;
Company.CountryCode = UserInfo.CountryCode.ToLower();
// internal flag
EnableActivity = Company.ValidVat;
// override if canvas which has account property as empty string or "NY"
if (Company.Account == "NY" || string.IsNullOrWhiteSpace(Company.Account))
EnableActivity = 1;
// visit interval init
if (Company.Interval == 0)
Company.Interval = 8;
@ -120,8 +129,6 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
NextVisit = LastVisit.AddDays(Company.Interval * 7);
// display urgency of next visit
VisitState = Utils.GetVisitState($"{NextVisit:yyyy-MM-dd}");
// action link passed to activity button component
ActionLink = $"/advisor/customers/{CompanyId}/activities/new"; // used when drawing visit button
// handle company out of business case
if (Company.HasFolded == 1)
{
@ -145,11 +152,12 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
CompanyVatAddress = PrepareVatAddress(Company);
await FetchContacts(CompanyId);
await Task.Delay(100);
await RequestErpUpdate();
}
// remove loading image
Working = false;
await Task.Delay(100);
await RequestErpUpdate();
}
private void ToggleErpEdit()
@ -414,6 +422,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
/// <param name="e"></param>
private void HandleFieldChanged(object? sender, FieldChangedEventArgs? e)
{
NextVisit = LastVisit.AddDays(Company.Interval * 7);
// avoid nesting if by assuming ValidVat is false
ValidVat = false;

View file

@ -153,7 +153,7 @@ public partial class OfficeOrderCreatePage : IDisposable
// setting up activity properties
Activity.ActivityStatusEnum = "noSale";
Activity.VisitTypeEnum = "recall";
Activity.ActivityVisitEnum = "recall";
Activity.ActivityTypeEnum = "phone";
Activity.ActivityStatusEnum = "order";
Activity.OurRef = $"T:{UserInfo.FirstName}";

View file

@ -1,15 +1,15 @@
{
"appInfo": {
"name": "Wonky Online",
"version": "0.131.0",
"version": "0.132.1",
"rc": true,
"sandBox": false,
"image": "grumpy-coder.png"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Debug",
"Default": "Information",
"System": "Information",
"Microsoft": "Information"
},
"Debug": {
@ -19,7 +19,7 @@
}
},
"apiConfig": {
"baseUrl": "https://dev.innotec.dk",
"baseUrl": "https://zeta.innotec.dk",
"catalog": "api/v2/catalog/country",
"crmCustomers": "api/v2/crm/companies",
"crmInventoryExt": "history/inventory",

View file

@ -28,160 +28,196 @@ public class ActivityDto
/// <summary>
/// Sales representative identification
/// </summary>
[Required] public string SalesRep { get; set; } = "";
[Required]
public string SalesRep { get; set; } = "";
/// <summary>
/// Sales representative entity Id
/// </summary>
[Required] public string SalesRepId { get; set; } = "";
[Required]
public string SalesRepId { get; set; } = "";
/// <summary>
/// Company countryCode (ensure correct code when office create ordre)
/// </summary>
[Required] public string CountryCode { get; set; } = "";
[Required]
public string CountryCode { get; set; } = "";
/// <summary>
/// Company entity Id
/// </summary>
[Required] public string CompanyId { get; set; } = "";
[Required]
public string CompanyId { get; set; } = "";
/// <summary>
/// Business Central entity Id
/// </summary>
public string BcId { get; set; } = "";
/// <summary>
/// Customer account
/// </summary>
public string Account { get; set; } = "";
/// <summary>
/// VAT number
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string VatNumber { get; set; } = "";
/// <summary>
/// Customer name
/// </summary>
[Required(ErrorMessage = "Navn skal udfyldes")]
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Name { get; set; } = "";
/// <summary>
/// Customer address city name
/// </summary>
[Required(ErrorMessage = "Byanvn skal udfyldes")]
[MaxLength(30, ErrorMessage = "Du kan højst bruge 30 tegn")]
public string City { get; set; } = "";
/// <summary>
/// Customer address postal code
/// </summary>
[Required(ErrorMessage = "Postnr. skal udfyldes")]
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string ZipCode { get; set; } = "";
/// <summary>
/// Customer address line 1
/// </summary>
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Address1 { get; set; } = "";
/// <summary>
/// Customer address line 2
/// </summary>
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")]
public string Address2 { get; set; } = "";
/// <summary>
/// Customer office phone
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string Phone { get; set; } = "";
/// <summary>
/// Customer mobile phone
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string Mobile { get; set; } = "";
/// <summary>
/// Customer office email
/// </summary>
[MaxLength(80, ErrorMessage = "Du kan højst bruge 80 tegn")]
public string Email { get; set; } = "";
/// <summary>
/// Customer attention description
/// </summary>
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Attention { get; set; } = "";
// Form entries
/// <summary>
/// Activity type enum as string
/// </summary>
[Required(ErrorMessage = "Vælg aktivitetstype")]
public string ActivityTypeEnum { get; set; } = "";
/// <summary>
/// Flag express order
/// </summary>
public bool Express { get; set; }
// Form entries
/// <summary>
/// Activity status enum as string
/// </summary>
[Required(ErrorMessage = "Vælg status for besøg ")]
public string ActivityStatusEnum { get; set; } = "";
/// <summary>
/// Activity type enum as string
/// </summary>
[Required(ErrorMessage = "Vælg aktivitetstype")]
public string ActivityTypeEnum { get; set; } = "";
/// <summary>
/// Visit type enum as string
/// </summary>
public string VisitTypeEnum { get; set; } = "recall";
public string ActivityVisitEnum { get; set; } = "recall";
/// <summary>
/// Activity date
/// </summary>
[Required(ErrorMessage = "Dato skal angives")]
public string ActivityDate { get; set; } = "";
/// <summary>
/// Product demonstration
/// </summary>
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")]
public string Demo { get; set; } = "";
/// <summary>
/// Our reference - system generated
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string OurRef { get; set; } = "";
/// <summary>
/// Customer reference number
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string ReferenceNumber { get; set; } = "";
/// <summary>
/// Customer reference description
/// </summary>
[MaxLength(35, ErrorMessage = "Du kan højst bruge 35 tegn")]
public string YourRef { get; set; } = "";
/// <summary>
/// Processing note to office
/// </summary>
[MaxLength(255, ErrorMessage = "Du kan højst bruge 255 tegn")]
public string OrderMessage { get; set; } = "";
/// <summary>
/// CRM note for future reference
/// </summary>
[MaxLength(255, ErrorMessage = "Du kan højst bruge 255 tegn")]
public string CrmNote { get; set; } = "";
// Delivery address form entries
/// <summary>
/// Customer delivery name
/// </summary>
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string DlvName { get; set; } = "";
/// <summary>
/// Customer delivery address line 1
/// </summary>
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string DlvAddress1 { get; set; } = "";
/// <summary>
/// Customer delivery address line 2
/// </summary>
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")]
public string DlvAddress2 { get; set; } = "";
/// <summary>
/// Customer delivery postal code
/// </summary>
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string DlvZipCode { get; set; } = "";
/// <summary>
/// Customer delivery city name
/// </summary>
[MaxLength(30, ErrorMessage = "Du kan højst bruge 30 tegn")]
public string DlvCity { get; set; } = "";
// Lines
/// <summary>
/// Order lines