swedish org number validation and storage v.0.102.5 - v.2.23.019.1209

This commit is contained in:
Frede Hundewadt 2023-01-19 12:30:07 +01:00
parent c2a9fa0210
commit cc00d397a9
7 changed files with 103 additions and 28 deletions

View file

@ -14,6 +14,9 @@
// //
using System.Globalization; using System.Globalization;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace Wonky.Client.Helpers; namespace Wonky.Client.Helpers;
@ -29,11 +32,11 @@ public class VatUtils
/// <returns></returns> /// <returns></returns>
public static bool ValidateFormat(string countryCode, string vatNumber) public static bool ValidateFormat(string countryCode, string vatNumber)
{ {
if (string.IsNullOrWhiteSpace(vatNumber) || string.IsNullOrWhiteSpace(countryCode) || !IsDigitsOnly(vatNumber)) if (string.IsNullOrWhiteSpace(vatNumber) || string.IsNullOrWhiteSpace(countryCode))
return false; return false;
var sanitisedVat = SanitizeVatNumber(vatNumber); var sanitisedVat = SanitizeVatNumber(vatNumber);
return countryCode.ToUpperInvariant() switch return countryCode.ToUpperInvariant() switch
{ {
"DK" => ValidateFormatDk(sanitisedVat), "DK" => ValidateFormatDk(sanitisedVat),
@ -104,23 +107,74 @@ public class VatUtils
// Si = int(Ci/5) + (Ci*2)MOD10) // Si = int(Ci/5) + (Ci*2)MOD10)
// https://www.skatteverket.se/skatter/mervardesskattmoms/momsregistreringsnummer.4.18e1b10334ebe8bc80002649.html // https://www.skatteverket.se/skatter/mervardesskattmoms/momsregistreringsnummer.4.18e1b10334ebe8bc80002649.html
// C11 C12 == 01 (De två sista siffrorna är alltid 01) // C11 C12 == 01 (De två sista siffrorna är alltid 01)
var vatToCheck = vatNumber; var vatToCheck = vatNumber;
if (vatToCheck.Length < 10 || long.Parse(vatToCheck) == 0) if (long.Parse(vatToCheck) == 0)
return false; return false;
switch (vatToCheck.Length)
{
// if less than 10 chars validate as SSI
case 6:
return ValidateFormatSeExt(vatToCheck);
case < 10:
return false;
}
// check digit calculation
var r = new[] { 0, 2, 4, 6, 8 } var r = new[] { 0, 2, 4, 6, 8 }
.Sum(m => (int)char.GetNumericValue(vatToCheck[m]) / 5 + .Sum(m => (int)char.GetNumericValue(vatToCheck[m]) / 5 +
(int)char.GetNumericValue(vatToCheck[m]) * 2 % 10); (int)char.GetNumericValue(vatToCheck[m]) * 2 % 10);
var c1 = new[] { 1, 3, 5, 7 }.Sum(m => (int)char.GetNumericValue(vatToCheck[m])); var c1 = new[] { 1, 3, 5, 7 }.Sum(m => (int)char.GetNumericValue(vatToCheck[m]));
var c10 = (10 - (r + c1) % 10) % 10; var c10 = (10 - (r + c1) % 10) % 10;
if (vatToCheck.Length == 10) // end check digit calculation
{ // return validation check - depending on with or with or without EU extension `01`
return $"{vatToCheck[..9]}{c10}" == vatNumber; return vatToCheck.Length == 10
} ? $"{vatToCheck[..9]}{c10}" == vatNumber
: $"{vatToCheck[..9]}{c10}01" == vatNumber;
return $"{vatToCheck[..9]}{c10}01" == vatNumber;
} }
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private static bool ValidateFormatSeExt(string data)
{
// Swedish personally held companies uses SSN number
// a relaxed validation is required as only first 6 digits is supplied
// birthday format e.g. 991231 yyMMdd
var y = int.Parse(data[..2]);
var m = int.Parse(data[2..4]);
var d = int.Parse(data[4..6]);
// this calculation is only valid within 21st century
var leap = y % 4 == 0; // 2000 was a leap year;
// day
if(d is < 1 or > 31)
return false;
// month
switch (m)
{
// feb
case 2:
{
if (leap)
return d <= 29;
return d <= 28;
}
// apr, jun, sep, nov
case 4 or 6 or 9 or 11:
return d <= 30;
// jan, mar, may, july, aug, oct, dec
case 1 or 3 or 5 or 7 or 8 or 10 or 12:
return true;
// does not exist
default:
return false;
}
}
/// <summary> /// <summary>
/// Modulus11 validator /// Modulus11 validator
/// </summary> /// </summary>
@ -147,11 +201,9 @@ public class VatUtils
/// <returns></returns> /// <returns></returns>
private static string SanitizeVatNumber(string vatNumber) private static string SanitizeVatNumber(string vatNumber)
{ {
return vatNumber.Replace(" ", "") if (string.IsNullOrWhiteSpace(vatNumber))
.Replace("-", "") return "";
.Replace("DK", "") var regexObj = new Regex(@"[^\d]");
.Replace("NO", "") return regexObj.Replace(vatNumber, "");
.Replace("SE", "")
.Replace("MVA", "");
} }
} }

View file

@ -15,6 +15,7 @@
using System.Net; using System.Net;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Blazored.Toast.Services; using Blazored.Toast.Services;
@ -86,7 +87,7 @@ namespace Wonky.Client.HttpInterceptors
if (e.Response == null || e.Response.IsSuccessStatusCode) if (e.Response == null || e.Response.IsSuccessStatusCode)
return; return;
var message = $"En fejl er opstået \n {e.Response.Content }"; var message = $"En fejl er opstået \n {JsonSerializer.Serialize(e)}";
var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri); var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri);
if (currDoc.Contains("login/")) if (currDoc.Contains("login/"))
currDoc = ""; currDoc = "";

View file

@ -189,6 +189,8 @@ public class AdvisorCustomerRepository : IAdvisorCustomerRepository
}; };
var response = await _client.PutAsJsonAsync($"{_conf.CrmCustomers}/{companyId}/vat", model, _options); var response = await _client.PutAsJsonAsync($"{_conf.CrmCustomers}/{companyId}/vat", model, _options);
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<CompanyDto>(content); return response.IsSuccessStatusCode
? JsonSerializer.Deserialize<CompanyDto>(content)
: new CompanyDto{Name = "ERROR", VatNumber = vatNumber, CrmNotes = $"FEJL: {content}"};
} }
} }

View file

@ -99,6 +99,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (Company.HasFolded == 1) if (Company.HasFolded == 1)
// Company has shutdown activities // Company has shutdown activities
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt."; Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
// variable to validate if customer needs phone number update // variable to validate if customer needs phone number update
OldPhone = Company.Phone; OldPhone = Company.Phone;
if (string.IsNullOrWhiteSpace(Company.Phone) if (string.IsNullOrWhiteSpace(Company.Phone)
@ -173,6 +174,8 @@ public partial class AdvisorActivityCreatePage : IDisposable
// fetch pDate from storage // fetch pDate from storage
var pDate = await Storage.GetItemAsync<string>($"{CompanyId}-pDate"); var pDate = await Storage.GetItemAsync<string>($"{CompanyId}-pDate");
Logger.LogDebug("pDate => {}", pDate); Logger.LogDebug("pDate => {}", pDate);
if (string.IsNullOrWhiteSpace(pStorage))
await Task.Delay(1000);
// check if product data is valid and updated today // check if product data is valid and updated today
if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}") if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}")
{ {
@ -325,6 +328,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (OldPhone != Activity.Phone) if (OldPhone != Activity.Phone)
{ {
Company.Phone = Activity.Phone; Company.Phone = Activity.Phone;
Activity.OrderMessage = $"Telefonnr. opdateret.\n{Activity.OrderMessage}";
await Companies.UpdateErpData(Company.CompanyId, Company); await Companies.UpdateErpData(Company.CompanyId, Company);
} }
// begin assembling activity // begin assembling activity

View file

@ -92,10 +92,10 @@
<ValidationMessage For="@(() => Company.Email)"></ValidationMessage> <ValidationMessage For="@(() => Company.Email)"></ValidationMessage>
</div> </div>
<div class="col-sm-2 d-grid mx-auto"> <div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0)" onclick="@ForceActivity">Aktiver besøg</button> <button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" onclick="@ForceActivity">Aktiver besøg</button>
</div> </div>
<div class="col-sm-2 d-grid mx-auto"> <div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-primary d-block" onclick="@UpdateErpData" disabled="@(Working)"><i class="bi-save"></i> STAM data </button> <button type="button" class="btn btn-primary d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR")"><i class="bi-save"></i> STAM data </button>
</div> </div>
@* account *@ @* account *@
@ -116,7 +116,18 @@
</div> </div>
@* vat lookup *@ @* vat lookup *@
<div class="col-sm-2 d-grid mx-auto"> <div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-info" @onclick="OpenVatLookupModal">Firma opslag</button> @switch (CountryCode)
{
case "dk":
<button type="button" class="btn btn-info" @onclick="OpenVatLookupModal"><i class="bi-search"></i> Firma opslag</button>
break;
case "no":
<a class="btn btn-info" href="https://brreg.no/" target="_blank"><i class="bi-search"></i> Firma opslag</a>
break;
case "se":
<a class="btn btn-info" href="https://www.allabolag.se/what/@Company.Name" target="_blank"><i class="bi-search"></i> Firma opslag</a>
break;
}
</div> </div>
@* save vat number *@ @* save vat number *@
<div class="col-sm-2 d-grid mx-auto"> <div class="col-sm-2 d-grid mx-auto">
@ -162,7 +173,7 @@
</div> </div>
@* Save CRM data button *@ @* Save CRM data button *@
<div class="col-sm-2 d-grid mx-auto"> <div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-primary" @onclick="UpdateCrmData"><i class="bi-save"></i> CRM data</button> <button type="button" class="btn btn-primary" disabled="@(Company.Name == "ERROR")" @onclick="UpdateCrmData"><i class="bi-save"></i> CRM data</button>
</div> </div>
</div> </div>
@* crm context - contacts *@ @* crm context - contacts *@

View file

@ -265,11 +265,13 @@ public partial class AdvisorCustomerViewPage : IDisposable
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}"; Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
Company.IsHidden = 0; Company.IsHidden = 0;
var result = await CompanyRepo.UpdateCrmData(CompanyId, Company); var result = await CompanyRepo.UpdateCrmData(CompanyId, Company);
if (!string.IsNullOrWhiteSpace(result.Name)) if (!string.IsNullOrWhiteSpace(result.CompanyId))
{ {
Company = result; Company = result;
StateHasChanged();
} }
Working = false; Working = false;
Toaster.ClearAll();
} }
/// <summary> /// <summary>
@ -283,11 +285,13 @@ public partial class AdvisorCustomerViewPage : IDisposable
Working = true; Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER STAM DATA"); Toaster.ShowInfo("Vent venligst ...", "OPDATERER STAM DATA");
var result = await CompanyRepo.UpdateErpData(CompanyId, Company); var result = await CompanyRepo.UpdateErpData(CompanyId, Company);
if (!string.IsNullOrWhiteSpace(result.Name)) if (!string.IsNullOrWhiteSpace(result.CompanyId))
{ {
Company = result; Company = result;
StateHasChanged();
} }
Working = false; Working = false;
Toaster.ClearAll();
} }
/// <summary> /// <summary>
@ -307,11 +311,12 @@ public partial class AdvisorCustomerViewPage : IDisposable
Working = true; Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER MOMS NUMMER"); Toaster.ShowInfo("Vent venligst ...", "OPDATERER MOMS NUMMER");
var result = await CompanyRepo.UpdateCompanyVat(CompanyId, Company.VatNumber); var result = await CompanyRepo.UpdateCompanyVat(CompanyId, Company.VatNumber);
if (!string.IsNullOrWhiteSpace(result.Name)) if (!string.IsNullOrWhiteSpace(result.CompanyId))
{ {
Company = result; Company = result;
StateHasChanged(); StateHasChanged();
} }
Toaster.ClearAll();
Working = false; Working = false;
} }

View file

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